Introducing the Swift plugins

This post introduces some new native plugins that we’ve been working on that can build Swift libraries and applications. They work on both macOS and Linux with the official Swift compiler.

The plugins take advantage of the many features baked into Gradle core, such as a rich dependency management engine as well as source dependencies, build cache, composite builds, finer grained parallel execution, build scans, and more. Note that, unfortunately, the plugins can’t be used out of the box to build iOS application and libraries.

If you’d like to provide feedback, please get in touch with us.

Building an application

You can find all of the samples from this post in the Gradle native samples GitHub repository. Let’s look at building a simple application.

The build script should look familiar to anyone who has used Gradle’s Java plugins:

plugins {
    id 'swift-application'
}

This application has no dependencies, and the Swift source files are located in the default location: the src/main/swift directory. Since this is Gradle, you can easily configure the source locations to match whatever layout your project requires, including the common Swift package manager pattern.

Here’s the result of running ./gradlew assemble on this sample:

./gradlew assemble

Take a look at the build scan for this build to see what happened in more detail.

compile debug timeline

The plugins automatically find the compiler, linker and other tools to build the application. The result ends up installed in the build/install directory ready to run.

IDE support

Xcode is currently supported for Swift projects. You can just run ./gradlew openXcode. Gradle will generate the workspace and open the generated workspace in Xcode. Support for other IDEs will be gradually added.

Here is the result of running ./gradlew openXcode on the sample:

./gradlew xcode

This is how the workspace looks in Xcode:

Xcode integration

Dependencies

The plugin uses Gradle’s dependency management features, just like other plugins such as the Java or Android plugins. This means, for example, transitive dependencies work just fine.

Let’s add a dependency on a library to the application. In this sample, the library and it’s dependencies are built before compiling and linking the application. You don’t have to add the transitive library anywhere manually to your build script.

Here is the result of running ./gradlew build on the sample:

./gradle build

Take a look at the build scan for this build to see what happened in more detail.

transitive dependencies

Here is how this project looks in Xcode:

Xcode integration

Tests

XCTest testing is supported out of the box. Here is a sample that uses XCTest. It will locate the XCTest Framework inside your Swift tool chain installation.

The build script applies the XCTest plugin which configures your build to generate an XCTest bundle on macOS or an XCTest executable on Linux:

plugins {
    id 'xctest'
}

Here is the result of running ./gradlew check. Gradle locates the XCTest module inside the tool chain installation, compiles the Swift source and tests, and then runs the tests:

./gradlew check

build scan for math check

Gradle has a deep understanding of this testing framework. It can generate JUnit and HTML test results report as well as supports the LinuxMain.swift convention for declaring the XCTest Linux executable entry point. It also supports basic test filtering. Build scan support, parallel execution and improved filtering for XCTest will be added later.

Fast builds

The plugins can produce debug and release builds of the application or library using Gradle’s new variant-aware dependency management, so that debug builds are compiled and linked against debug library binaries, and release builds are compiled and linked against release library binaries. When you build the debug build, which is the default, Gradle builds only the debug builds of the libraries that you need, rather than building everything.

Developer and CI builds are fast. Swift compilation is a cacheable task, so you can avoid unnecessary and long compilation times when using the build cache. Gradle Enterprise comes with a build cache backend. You don’t need to use the --parallel option as Gradle does incremental and parallel compilation and linking by default.

Let’s run some clean builds that use the build cache:

./gradlew assemble with build cache

You can see that the second build is faster, as the result is fetched from the build cache rather than recompiled. Build scans for non-cached build and cached build.

cached vs non-cached assemble

Composite builds

Composite builds also work the same as in Java or C++ projects. This sample using a composite build combines builds so they can be worked on together:

rootProject.name = 'app'

includeBuild 'utilities-library'
includeBuild 'list-library'

Here’s the result in Xcode. The application and the libraries it uses are available to edit, build and test together:

Xcode integration

Operating System and Source Compatibility

Native projects can also configure the operating system (a.k.a target machines) for Swift components.

Each component script block (e.g. application, library, xctest) can configure the targetMachines property. The configured target machines will participate in the build variant for the machine compatible with the build host. For example, the following specifies that the application is only buildable on Linux machines.

plugins {
    id "swift-application"
}

application {
    targetMachines = [machines.linux.x86_64]
}

On a Linux machine, a variant of all the tasks will be created. As shown on the screencast below, the assemble task compiles the debug variant, also known as the development binary.

Building Swift Application on macOS

The other variant release is buildable via the linkReleaseX86-64 tasks.

On other operating systems, Gradle won’t build anything since no target machine is compatible with the build host. It’s worth noting that none of the tasks mentioned previously will be created as the variants aren’t buildable. Instead, invoking the assemble task will simply print an informative message as shown in the screencast below.

Building Swift Application on Linux

Source dependencies

Support for source dependencies was recently announced in the Introducing source dependencies blog post.

In short, source dependencies allow Gradle to use other projects as dependencies directly from source. It is common practice for Swift developers to build dependencies from source. Gradle takes care of all the wiring required to build sources before building the consumer.

If the source dependency build is a Gradle build, the experience will be similar to a composite included build. On the other hand, if the source dependency is built by another build system, Gradle can inject configuration code to execute the right build commands and map the artifacts into a Gradle build.

Documentation

If you are familiar with the native samples repository, chances are you are already familiar with the samples demonstrating what can already be achieved with Swift plugins. With Gradle 5.6, we released additional documentation to help new users get on-board with Swift development. We suggest reading the Building Swift Projects and Testing Swift Projects chapters of the user guide to get started.

We have also introduced reference chapters for each Swift plugin. You can also read the Xcode plugin reference chapter to learn how to use the Xcode IDE during your Swift development.

Providing Feedback

These plugins are a work in progress and have some limitations. For example, model elements such as debuggability don’t allow any configuration yet and you’ll need to resort to using compiler/linker flags.

We’ll continue to improve these plugins, make them stable, and eventually support iOS application.

Please try these plugins out and let us know what you think. The easiest way to get started is to clone the native samples repository and follow the instructions. Our samples use a Gradle nightly build, so you’ll see the latest and greatest developments there.

We’d love to hear what you think works well, what’s confusing, and what is missing that would block you from using Gradle to build Swift software. You can also leave feedback on the Gradle forums or raise issues on the Gradle native GitHub repository.