This document describes how to set up a development loop for people interested in contributing to Swift.
If you are only interested in building the toolchain as a one-off, there are a couple of differences:
Create a directory for the whole project:
mkdir swift-project
cd swift-project
Warning
Make sure the absolute path to yourswift-project
directory does not contain spaces, since that might cause issues during the build step.
Clone the sources:
git clone git@github.com:apple/swift.git swift
cd swift
utils/update-checkout --clone-with-ssh
git clone https://github.com/apple/swift.git swift
cd swift
utils/update-checkout --clone
Note
If you've already forked the project on GitHub at this stage, do not clone your fork to start off. We describe how to setup your fork in a subsection below.
Double-check that swift
's sibling directories are present.
ls ..
This should list directories like llvm-project
, swiftpm
and so on.
Checkout the right branch/tag:
If you are building the toolchain for local development, you can skip this
step, as Step 2 will checkout swift
's main
branch and matching
branches for other projects.
If you are building the toolchain as a one-off, it is more likely that you
want a specific branch or a tag, often corresponding to a specific release
or a specific snapshot. You can update the branch/tag for all repositories
as follows:
utils/update-checkout --scheme mybranchname
# OR
utils/update-checkout --tag mytagname
Detailed branching information, including names for release branches, can be found in Branches.md.
Note
The commands used in the rest of this guide assumes that the absolute path to your working directory is something like/path/to/swift-project/swift
. Double-check that runningpwd
prints a path ending withswift
.
update-checkout
failed, double-check that the absolute path to your
working directory does not have non-ASCII characters.update-checkout
, double-check that swift
is the only
repository inside the swift-project
directory. Otherwise,
update-checkout
may not clone the necessary dependencies.brew install cmake ninja sccache
brew bundle
The latest Linux dependencies are listed in the respective Dockerfiles:
Note that a prebuilt Swift release toolchain
is installed and added to the PATH
in all these Docker containers: it is
recommended that you do the same, in order to build the portions of the Swift
compiler written in Swift.
To install Sccache (optional):
If you're not building within a Docker container:
sudo snap install sccache --candidate --classic
If you're building within a Docker container, you'll have to install
sccache
manually, since snap
is not available in environments
without systemd
:
SCCACHE_VERSION=v0.3.0
curl -L "https://github.com/mozilla/sccache/releases/download/${SCCACHE_VERSION}/sccache-${SCCACHE_VERSION}-$(uname -m)-unknown-linux-musl.tar.gz" -o sccache.tar.gz
tar xzpvf sccache.tar.gz
sudo cp "sccache-${SCCACHE_VERSION}-$(uname -m)-unknown-linux-musl/sccache" /usr/local/bin
sudo chmod +x /usr/local/bin/sccache
Note
LLDB currently requires at leastswig-1.3.40
but will successfully build with version 2 shipped with Ubuntu.
cmake --version
; this should be at least 3.19.6 (3.24.2 if you want to use Xcode for editing on macOS).python3 --version
; this should be at least 3.6.ninja --version
; check that this succeeds.sccache --version
; check
that this succeeds.Note
If you are running on Apple Silicon hardware (M1, M2, etc), ensure you have the native arm64 build of these dependencies installed and configured in your PATH.e.g. running
file $(which python3)
should print "arm64".If it prints "x86_64", you are running Python in compatibility mode (Rosetta), and building Swift will fail. Running
uname -m
should also print "arm64", otherwise your terminal is running in Rosetta mode.
At this point, it is worthwhile to pause for a moment to understand what the different tools do:
On macOS and Windows, IDEs (Xcode and Visual Studio resp.) serve as an easy way to install development dependencies such as a C++ compiler, a linker, header files, etc. The IDE's build system need not be used to build Swift. On Linux, these dependencies are installed by the distribution's package manager.
CMake is a cross-platform build system for C and C++. It forms the core infrastructure used to configure builds of Swift and its companion projects.
Ninja is a low-level build system that can be used to build the project, as an alternative to Xcode's build system. Ninja is somewhat faster, especially for incremental builds, and supports more build environments.
Sccache is a caching tool: If you ever delete your build directory and rebuild from scratch (i.e. do a "clean build"), Sccache can accelerate the new build significantly. There are few things more satisfying than seeing Sccache cut through build times.
Note Sccache defaults to a cache size of 10GB, which is relatively small compared to build artifacts. You can bump it up, say, by setting
export SCCACHE_CACHE_SIZE="50G"
in your dotfile(s).
utils/update-checkout
is a script to help you work with all the individual
git repositories together, instead of manually cloning/updating each one.
utils/build-script
(we will introduce this shortly)
is a high-level automation script that handles configuration (via CMake),
building (via Ninja), caching (via Sccache), running tests and more.
Pro Tip: Most tools support
--help
flags describing the options they support. Additionally, both Clang and the Swift compiler have hidden flags (clang --help-hidden
/swiftc --help-hidden
) and frontend flags (clang -cc1 --help
/swiftc -frontend --help
) and the Swift compiler even has hidden frontend flags (swiftc -frontend --help-hidden
). Sneaky!
Phew, that's a lot to digest! Now let's proceed to the actual build itself!
Build the toolchain with optimizations, debuginfo, and assertions, using Ninja:
macOS:
utils/build-script --skip-build-benchmarks \
--skip-ios --skip-watchos --skip-tvos --swift-darwin-supported-archs "$(uname -m)" \
--sccache --release-debuginfo --swift-disable-dead-stripping \
--bootstrapping=hosttools
Linux:
utils/build-script --release-debuginfo
--xctest
to the invocation.If you installed and want to use Sccache, add --sccache
to the invocation.
If you want to use a debugger such as LLDB on compiler sources, add
--debug-swift
to the invocation: a fruitful debugging experience warrants
non-optimized code besides debug information.
This will create a directory swift-project/build/Ninja-RelWithDebInfoAssert
containing the Swift compiler and standard library and clang/LLVM build artifacts.
If the build fails, see Troubleshooting build issues.
In the following sections, for simplicity, we will assume that you are using a
Ninja-RelWithDebInfoAssert
build on macOS, unless explicitly mentioned otherwise.
You will need to slightly tweak the paths for other build configurations.
git checkout
to change the branch only
for swift
(often to a release branch), leading to an unsupported
configuration. See Step 4 of Cloning the Project
on how to fix this.build-script
in
the log. While build-script
should work with paths containing spaces,
sometimes bugs do slip through, such as #55883.
If this is the case, please file a bug report and change the path
to work around it.build-script
invocation doesn't have typos. You can compare
the flags you passed against the supported flags listed by
utils/build-script --help
.build-script
building compiler with –xcode
utils/update-checkout --dump-hashes
.If you are building the toolchain for development and submitting patches, you will need to setup a GitHub fork.
First fork the apple/swift
repository,
using the "Fork" button in the web UI, near the top-right. This will create a
repository username/swift
for your GitHub username. Next, add it as a remote:
# Using 'my-remote' as a placeholder name.
# If you set up SSH in step 2
git remote add my-remote git@github.com:username/swift.git
# If you used HTTPS in step 2
git remote add my-remote https://github.com/username/swift.git
Finally, create a new branch.
# Using 'my-branch' as a placeholder name
git checkout -b my-branch
git push --set-upstream my-remote my-branch
This workflow enables you to edit, build, run, and debug in Xcode. The following steps assume that you have already built the toolchain with Ninja.
Note
A seamless LLDB debugging experience requires that yourbuild-script
invocation for Ninja is tuned to produce build rules for the debug variant of the component you intend to debug.
Generate the Xcode project with
utils/build-script --swift-darwin-supported-archs "$(uname -m)" --xcode --clean
This can take a few minutes due to metaprogrammed sources that depend on LLVM tools that are built from source.
Create an empty Xcode workspace.
Add build/Xcode-*/swift-macosx-*/Swift.xcodeproj
to the workspace. If Xcode
prompts to autocreate schemes, select Manually Manage Schemes and don't
create any schemes just yet.
This project includes the sources for almost everything in the repository,
including the compiler, standard library and runtime. If you intend to work on
a compiler subcomponent that is written in Swift and has a Package.swift
file (e.g. lib/ASTGen
), first choose Product > Scheme > Manage Schemes...
and select the Autocreate schemes checkbox, then add the package directory
to the workspace by choosing File > Add Files to "<workspace name>". Xcode
will automatically create schemes for package manifest.
Create an Xcode project using the External Build System template, and add it to the workspace.
Create a target in the new project, using the External Build System template.
In the Info pane of the target settings, set
ninja
executable (the output of
which ninja
on the command line)bin/swift-frontend
is the compiler)build/Ninja-*/swift-macosx-*
directoryCreate a scheme in the workspace, making sure to select the target you just created. Be extra careful not to choose a target from the generated Xcode project you added to the workspace.
Spot-check your target in the settings for the Build scheme action.
If the target is executable, adjust the settings for the Run scheme action:
build/Ninja-*/swift-macosx-*/bin
(e.g. swift-frontend
).path/to/file.swift -typecheck
for
bin/swift-frontend
).Follow the previous steps to create more targets and schemes per your line of work.
The structure of the generated Xcode project is distinct from the underlying
organization of the files on disk, and does not adapt to changes in the file
system, such as file/directory additions/deletions/renames. Over the course of
multiple update-checkout
rounds, the resulting divergence is likely to begin
affecting your editing experience. To fix this, regenerate the project by
running the invocation from the first step.
You can also use other editors and IDEs to work on Swift.
CLion supports CMake and Ninja. In order to configure it properly, build the swift project first using the build-script
, then open the swift
directory with CLion and proceed to project settings (cmd + ,
).
In project settings, locate Build, Execution, Deployment > CMake
. You will need to create a new profile named RelWithDebInfoAssert
(or Debug
if going to point it at the debug build). Enter the following information:
RelWithDebInfoAssert
or Debug
CMAKE_BUILD_TYPE
so should be e.g. RelWithDebInfoAssert
or Debug
RelWithDebInfoAssert
is a good one to work withbuild-script
had used here, so CLion understands the build configuration. You can get the full list of CMake arguments from build-script
by providing the -n
dry-run flag; look for the last cmake
command with a -G Ninja
. Here is a minimal list of what you should provide to CLion here for this setting:
-D SWIFT_PATH_TO_CMARK_BUILD=SOME_PATH/swift-project/build/Ninja-RelWithDebInfoAssert/cmark-macosx-arm64 -D LLVM_DIR=SOME_PATH/swift-project/build/Ninja-RelWithDebInfoAssert/llvm-macosx-arm64/lib/cmake/llvm -D Clang_DIR=SOME_PATH/swift-project/build/Ninja-RelWithDebInfoAssert/llvm-macosx-arm64/lib/cmake/clang -D CMAKE_BUILD_TYPE=RelWithDebInfoAssert -D SWIFT_PATH_TO_SWIFT_SYNTAX_SOURCE=SOME_PATH/swift-project/swift-syntax -G Ninja -S .
SOME_PATH
to the path where your swift-project
directory isRelWithDebInfo
the CMAKE_BUILD_TYPE should also be RelWithDebInfo
arm64
with x86_64
)build-script
run you did earlier, for example, SOME_PATH/swift-project/build/Ninja-RelWithDebInfoAssert/swift-macosx-arm64
.With this done, CLion should be able to successfully import the project and have full autocomplete and code navigation powers.
Make changes to the code as appropriate. Implement a shiny new feature! Or fix a nasty bug! Update the documentation as you go! The codebase is your oyster!
:construction::construction_worker::building_construction:
Now that you have made some changes, you will need to rebuild...
Subsequent steps in this and the next subsections are specific to the platform you're building on, so we'll try to detect it first and reuse as a shell variable:
platform=$([[ $(uname) == Darwin ]] && echo macosx || echo linux)
After setting that variable you can rebuild the compiler incrementally with this command:
ninja -C ../build/Ninja-RelWithDebInfoAssert/swift-${platform}-$(uname -m) bin/swift-frontend
To rebuild everything that has its sources located in the swift
repository, including the standard library:
ninja -C ../build/Ninja-RelWithDebInfoAssert/swift-${platform}-$(uname -m)
Similarly, you can rebuild other projects like Foundation or Dispatch by substituting their respective subdirectories in the commands above.
As a quick test, go to lib/Basic/Version.cpp
and tweak the version
printing code slightly. Next, do an incremental build as above. This incremental
build should be much faster than the from-scratch build at the beginning.
Now check if the version string has been updated (assumes you have platform
shell variable
defined as specified in the previous subsection:
../build/Ninja-RelWithDebInfoAssert/swift-$(platform)-$(uname -m)/bin/swift-frontend --version
This should print your updated version string.
Good first issues typically have
small code examples that fit within a single file. You can reproduce such an
issue in various ways, such as compiling it from the command line using
/path/to/swiftc MyFile.swift
, pasting the code into Compiler Explorer
(aka godbolt) or using an Xcode Playground.
For files using frameworks from an SDK bundled with Xcode, you need the pass the SDK explicitly. Here are a couple of examples:
# Compile a file to an executable for your local machine.
xcrun -sdk macosx /path/to/swiftc MyFile.swift
# Say you are trying to compile a file importing an iOS-only framework.
xcrun -sdk iphoneos /path/to/swiftc -target arm64-apple-ios13.0 MyFile.swift
You can see the full list of -sdk
options using xcodebuild -showsdks
,
and check some potential -target
options for different operating systems by
skimming the compiler's test suite under test/
.
Sometimes bug reports come with SwiftPM packages or Xcode projects as minimal reproducers. While we do not add packages or projects to the compiler's test suite, it is generally helpful to first reproduce the issue in context before trying to create a minimal self-contained test case. If that's the case with the bug you're working on, check out our instructions on building packages and Xcode projects with a locally built compiler.
There are two main ways to run tests:
utils/run-test
: By default, run-test
builds the tests' dependencies
before running them.
# Rebuild all test dependencies and run all tests under test/.
utils/run-test --lit ../llvm-project/llvm/utils/lit/lit.py \
../build/Ninja-RelWithDebInfoAssert/swift-macosx-$(uname -m)/test-macosx-$(uname -m)
# Rebuild all test dependencies and run tests containing "MyTest".
utils/run-test --lit ../llvm-project/llvm/utils/lit/lit.py \
../build/Ninja-RelWithDebInfoAssert/swift-macosx-$(uname -m)/test-macosx-$(uname -m) \
--filter="MyTest"
lit.py
: lit doesn't know anything about dependencies. It just runs tests.
# Run all tests under test/.
../llvm-project/llvm/utils/lit/lit.py -s -vv \
../build/Ninja-RelWithDebInfoAssert/swift-macosx-$(uname -m)/test-macosx-$(uname -m)
# Run tests containing "MyTest"
../llvm-project/llvm/utils/lit/lit.py -s -vv \
../build/Ninja-RelWithDebInfoAssert/swift-macosx-$(uname -m)/test-macosx-$(uname -m) \
--filter="MyTest"
-s
and -vv
flags print a progress bar and the executed commands
respectively.If you are making small changes to the compiler or some other component, you'll
likely want to incrementally rebuild only the relevant
target and use lit.py
with --filter
. One potential failure mode with this
approach is accidental use of stale binaries. For example, say that you want to
rerun a SourceKit test but you only incrementally rebuilt the compiler. Then
your changes will not be reflected when the test runs because the sourcekitd
binary was not rebuilt. Using run-test
instead is the safer option, but it
will lead to a longer feedback loop due to more things getting rebuilt.
In the rare event that a local test failure happens to be unrelated to your changes (is not due to stale binaries and reproduces without your changes), there is a good chance that it has already been caught by our continuous integration infrastructure, and it may be ignored.
If you want to rerun all the tests, you can either rebuild the whole project
and use lit.py
without --filter
or use run-test
to handle both aspects.
For more details on running tests and understanding the various Swift-specific lit customizations, see Testing.md. Also check out the lit documentation to understand how the different lit commands work.
In this section, we briefly describe two common ways of debugging: print debugging and using LLDB.
Depending on the code you're interested in, LLDB may be significantly more effective when using a debug build. Depending on what components you are working on, you could turn off optimizations for only a few things. Here are some example invocations:
# optimized Stdlib + debug Swiftc + optimized Clang/LLVM
utils/build-script --release-debuginfo --debug-swift # other flags...
# debug Stdlib + optimized Swiftc + optimized Clang/LLVM
utils/build-script --release-debuginfo --debug-swift-stdlib # other flags...
# optimized Stdlib + debug Swiftc (except typechecker) + optimized Clang/LLVM
utils/build-script --release-debuginfo --debug-swift --force-optimized-typechecker
# Last resort option, it is highly unlikely that you will need this
# debug Stdlib + debug Swiftc + debug Clang/LLVM
utils/build-script --debug # other flags...
Debug builds have two major drawbacks:
DebuggingTheCompiler.md goes into a LOT more detail on how you can level up your debugging skills! Make sure you check it out in case you're trying to debug a tricky issue and aren't sure how to go about it.
A large number of types have dump(..)
/print(..)
methods which can be used
along with llvm::errs()
or other LLVM streams. For example, if you have a
variable std::vector<CanType> canTypes
that you want to print, you could do:
auto &e = llvm::errs();
e << "canTypes = [";
llvm::interleaveComma(canTypes, e, [&](auto ty) { ty.dump(e); });
e << "]\n";
You can also crash the compiler using assert
/llvm_unreachable
/
llvm::report_fatal_error
, after accumulating the result in a stream:
std::string msg; llvm::raw_string_ostream os(msg);
os << "unexpected canTypes = [";
llvm::interleaveComma(canTypes, os, [&](auto ty) { ty.dump(os); });
os << "] !!!\n";
llvm::report_fatal_error(os.str());
When the compiler crashes, the commandline arguments passed to it will be printed to stderr. It will likely look something like:
/path/to/swift-frontend <args>
Using LLDB on the commandline: Copy the entire invocation and pass it to LLDB.
lldb -- /path/to/swift-frontend <args>
Now you can use the usual LLDB commands like run
, breakpoint set
and so
on. If you are new to LLDB, check out the official LLDB documentation and
nesono's LLDB cheat sheet.
Using LLDB within Xcode:
Select the current scheme 'swift-frontend' → Edit Scheme → Run phase →
Arguments tab. Under "Arguments Passed on Launch", copy-paste the <args>
and make sure that "Expand Variables Based On" is set to swift-frontend.
Close the scheme editor. If you now run the compiler
(⌘+R or Product → Run), you will be able to use the
Xcode debugger.
Xcode also has the ability to attach to and debug Swift processes launched
elsewhere. Under Debug → Attach to Process by PID or name..., you can enter
a compiler process's PID or name (swift-frontend
) to debug a compiler
instance invoked elsewhere. This can be helpful if you have a single compiler
process being invoked by another tool, such as SwiftPM or another open Xcode
project.
Pro Tip: Xcode 12's terminal does not support colors, so you may see explicit color codes printed by
dump()
methods on various types. To avoid color codes in dumped output, runexpr llvm::errs().enable_color(false)
.
Make sure you check out the following resources:
If you see mistakes in the documentation (including typos, not just major errors) or identify gaps that you could potentially improve the contributing experience, please start a discussion on the forums, submit a pull request or file a bug report on Swift repository 'Issues' tab. Thanks!
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。