How to Use External Swift Packages in Bazel

Swift Package Manager and Bazel

NOTE: This article is about using rules_spm to leverage Swift packages in Bazel. The rules_spm project is being sunset. It has been superseded by swift_bazel. I have written a blog post on how to get started.

If you build Swift software in Bazel, you probably use the excellent rules_swift rules to develop modules and executables for your project. While working on your project, you almost certainly have wanted to import one of the many third-party Swift packages that exist. Unfortunately, rules_swift does not provide a means to integrate these packages into your project quickly. So, I created rules_spm to address this very issue.

Table of Contents

What is rules_spm?

The rules_spm project contains Bazel rules and macros that allow a project to depend upon external Swift packages. It provides a means to declare dependent packages, much like Swift Package Manager (SPM). Under the covers, it downloads the external packages, builds them using SPM, and makes the artifacts available as Bazel binary targets for execution or Bazel library targets suitable for consumption by rules_swift rules.

A Simple Example

This article will describe how to import the Logging module from Apple’s swift-log package into a Swift binary built using Bazel. You can find the code for this example here.

1. Download and Configure rules_spm

First, we need to add rules_spm to the WORKSPACE file. The following code downloads the rules and configures them.

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "cgrindel_rules_spm",
    sha256 = "fab28a41793744f3944ad2606cdd9c0f8e2f4861dd29fb1d61aa4263c7a1400a",
    strip_prefix = "rules_spm-0.6.0",
    urls = ["https://github.com/cgrindel/rules_spm/archive/v0.6.0.tar.gz"],
)

load(
    "@cgrindel_rules_spm//spm:deps.bzl",
    "spm_rules_dependencies",
)

spm_rules_dependencies()

load(
    "@build_bazel_rules_swift//swift:repositories.bzl",
    "swift_rules_dependencies",
)

swift_rules_dependencies()

load(
    "@build_bazel_rules_swift//swift:extras.bzl",
    "swift_rules_extra_dependencies",
)

swift_rules_extra_dependencies()

NOTE: At the time of writing, the latest version of rules_spm was 0.6.0. Before using rules_spm in your project, check out the README.md for the most up-to-date version and initialization code.

NOTE: The rules_spm rules use rules_swift under the covers. If your project requires a specific version of rules_spm, add the appropriate rules_swift declarations before running spm_rules_dependencies.

2. Declare the Dependent Swift Packages

Next, we need to declare the Swift packages that we want to use in our project. We do that by adding a spm_repositories collection to the WORKSPACE file. Then, we add an spm_pkg entry for each of the Swift packages that we want to import. Much like the SPM package dependency syntax, spm_pkg accepts a URL, a minimum version, and, optionally, a name. The one additional piece of information required is the list of product names that our project will use.

load("@cgrindel_rules_spm//spm:spm.bzl", "spm_pkg", "spm_repositories")

spm_repositories(
    name = "swift_pkgs",
    dependencies = [
        spm_pkg(
            "https://github.com/apple/swift-log.git",
            from_version = "1.0.0",
            products = ["Logging"],
        ),
    ],
)

For our example, the previous code will download the latest 1.x.x version of apple/swift-log and generate Bazel build files that define the SPM targets associated with the Logging library product.

3. Import the Logging module in the Swift Binary

Now, it’s time to use the Logging module in the project’s Swift code. Add a dependency to the swift_binary declaration in the BUILD file.

load("@build_bazel_rules_swift//swift:swift.bzl", "swift_binary")

swift_binary(
    name = "simple",
    srcs = ["main.swift"],
    visibility = ["//swift:__subpackages__"],
    deps = [
        "@swift_pkgs//swift-log:Logging",
    ],
)

The format for the dependency target is

@collection-name//swift-package-name:SwiftModuleName

The collection-name is the name that you provide to the spm_repositories declaration. The swift-package-name is the name of the Swift package. Finally, the SwiftModuleName is the name of the Swift module.

NOTE: The Swift package name is defined in the package’s Package.swift. While the package name is often identical to the repository name. There are occasions when they are different.

Finally, import the module and use it in the Swift code.

import Logging

let logger = Logger(label: "com.example.main")
logger.info("Hello World!")