# Swift Package Manager Rules for Bazel

[![Build](https://github.com/cgrindel/rules_swift_package_manager/actions/workflows/ci.yml/badge.svg?event=schedule)](https://github.com/cgrindel/rules_swift_package_manager/actions/workflows/ci.yml)

This repository contains a Bazel ruleset that can be used to download, build, and consume Swift
packages. The rules in this repository build the external Swift packages using [rules_swift],
[rules_apple] and native C/C++ rulesets making the Swift package products and targets available as
Bazel targets.

This repository is designed to fully replace [rules_spm] and provide utilities to ease Swift
development inside a Bazel workspace.

## Table of Contents

<!-- MARKDOWN TOC: BEGIN -->
* [Documentation](#documentation)
* [Prerequisites](#prerequisites)
  * [Mac OS](#mac-os)
  * [Linux](#linux)
* [Quickstart](#quickstart)
  * [1. Enable bzlmod](#1-enable-bzlmod)
  * [2. Configure your `MODULE.bazel` to use rules_swift_package_manager.](#2-configure-your-modulebazel-to-use-rules_swift_package_manager)
    * [(Optional) Use `swift_package` repository for updating packages](#optional-use-swift_package-repository-for-updating-packages)
    * [(Optional) Enable `swift_deps_info` generation for the Gazelle plugin](#optional-enable-swift_deps_info-generation-for-the-gazelle-plugin)
    * [(Optional) Add dependencies to generated Swift package targets](#optional-add-dependencies-to-generated-swift-package-targets)
  * [3. Create a minimal `Package.swift` file.](#3-create-a-minimal-packageswift-file)
  * [4. Run `swift package update`](#4-run-swift-package-update)
  * [5. Run `bazel mod tidy`.](#5-run-bazel-mod-tidy)
  * [6. Build and test your project.](#6-build-and-test-your-project)
  * [7. Check in `Package.swift`, `Package.resolved`, and `MODULE.bazel`.](#7-check-in-packageswift-packageresolved-and-modulebazel)
  * [8. Start coding](#8-start-coding)
* [Using a Swift package registry](#using-a-swift-package-registry)
* [Tips and Tricks](#tips-and-tricks)
<!-- MARKDOWN TOC: END -->

## Documentation

- [Rules and API documentation](/docs/README.md)
- [High-level design](/docs/design/high-level.md)
- [Frequently Asked Questions](/docs/faq.md)

## Prerequisites

### Mac OS

Be sure to install Xcode.

### Linux

You will need to [install Swift](https://swift.org/getting-started/#installing-swift). Make sure
that running `swift --version` works properly.

Don't forget that `rules_swift` [expects the use of
`clang`](https://github.com/bazelbuild/rules_swift#3-additional-configuration-linux-only). Hence,
you will need to specify `CC=clang` before running Bazel.

Finally, help [rules_swift] and [rules_swift_package_manager] find the Swift toolchain by ensuring
that a `PATH` that includes the Swift binary is available in the Bazel actions.

```sh
cat >>local.bazelrc <<EOF
build --action_env=PATH
EOF
```

This approach is necessary to successfully execute the examples on an Ubuntu runner using Github
actions. See the [CI GitHub workflow] for more details.

## Quickstart

The following provides a quick introduction on how to set up and use the features in this
repository. These instructions assume that you are using [Bazel modules] to load your external
dependencies. If you are using Bazel's legacy external dependency management, we recommend using
[Bazel's hybrid mode], then follow the steps in this quickstart guide.

Also, check out the [examples] for more information.

### 1. Enable bzlmod

This repository supports [bzlmod].

```
common --enable_bzlmod
```

### 2. Configure your `MODULE.bazel` to use [rules_swift_package_manager].

Add a dependency on `rules_swift_package_manager`.

<!-- BEGIN MODULE SNIPPET -->
```python
bazel_dep(name = "rules_swift_package_manager", version = "1.23.0")
```
<!-- END MODULE SNIPPET -->

In addition, add the following to load the external dependencies described in your `Package.swift`
and `Package.resolved` files.

```bazel
swift_deps = use_extension(
    "@rules_swift_package_manager//:extensions.bzl",
    "swift_deps",
)
swift_deps.from_package(
    resolved = "//:Package.resolved",
    swift = "//:Package.swift",
)
use_repo(
    swift_deps,
    "swift_deps_info",  # This is generated by the ruleset.
    # The name of the Swift package repositories will be added to this declaration in step 4 after
    # running `bazel mod tidy`.
    # NOTE: The name of the Bazel external repository for a Swift package is `swiftpkg_xxx` where
    # `xxx` is the Swift package identity, lowercase, with punctuation replaced by `hyphen`. For
    # example, the repository name for apple/swift-nio is `swiftpkg_swift_nio`.
)
```

You will also need to add a dependency on [rules_swift].

NOTE: Some Swift package manager features (e.g., resources) use rules from [rules_apple]. It is a
dependency for `rules_swift_package_manager`. However, you do not need to declare it unless you use
any of the rules in your project.

#### (Optional) Use `swift_package` repository for updating packages

The `swift_deps` module extension will by default generate a `swift_package` repository which can be used to execute `swift package` commands.
This is useful if you'd like to control the flags and behavior of `swift package`, as well as for using the correct `swift` binary according to the Bazel configured toolchain.

For example, to resolve the `Package.swift` file:

```sh
bazel run @swift_package//:resolve
```

To update packages to their latest supported version:

```sh
bazel run @swift_package//:update
```

Both targets support passing arguments as well, so for example, you could update a single package:

```sh
bazel run @swift_package//:update -- MyPackage
```

These targets will update the `Package.resolved` file defined in `swift_deps.from_package`.
The targets come with default flags applied to enable the best Bazel compatibility, if you wish to configure it further, you can do so with `configure_swift_package`:

```starlark
# MODULE.bazel

swift_deps.configure_swift_package(
    build_path = "spm-build",
    cache_path = "spm-cache",
    dependency_caching = "false",
    manifest_cache = "none",
    manifest_caching = "false",
)
```

If you do not want to use the `swift_package` repository you can disable it in the `swift_deps.from_package` call:

```starlark
swift_deps.from_package(
    declare_swift_package = False,  # <=== Disable the `swift_package` repository
    resolved = "//:Package.resolved",
    swift = "//:Package.swift",
)
```

#### (Optional) Enable `swift_deps_info` generation for the Gazelle plugin

If you will be using the [Gazelle plugin for Swift], you will need to enable the generation of
the `swift_deps_info` repository by enabling `declare_swift_deps_info`.

```bazel
swift_deps.from_package(
    declare_swift_deps_info = True, # <=== Enable swift_deps_info generation for the Gazelle plugin
    resolved = "//:Package.resolved",
    swift = "//:Package.swift",
)
```

#### (Optional) Add dependencies to generated Swift package targets

If a generated Swift package target is missing a Bazel dependency, use `configure_package` to
append deps to that generated target.

```bazel
swift_deps.configure_package(
    name = "ExamplePackage",
    target_deps = {
        "ExampleTarget": [
            ":same_build_file_dep",
            "@other_repo//:dep",
            "@//app:main_repo_dep",
        ],
    },
)
```

Keys should usually be Swift package target names without `.rspm`; these map to generated
implementation targets like `ExampleTarget.rspm.__impl`. If a key already contains `.rspm`, it is
matched as a generated target name unchanged.

### 3. Create a minimal `Package.swift` file.

Create a minimal `Package.swift` file that only contains the external dependencies that are directly
used by your Bazel workspace.

```swift
// swift-tools-version: 5.7

import PackageDescription

let package = Package(
    name: "my-project",
    dependencies: [
        // Replace these entries with your dependencies.
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.0"),
        .package(url: "https://github.com/apple/swift-log", from: "1.4.4"),
    ]
)
```

The name of the package can be whatever you like. It is required for the manifest, but it is not
used by [rules_swift_package_manager]. If your project is published and consumed as a Swift package,
feel free to populate the rest of the manifest so that your package works properly by Swift package
manager. Just note that the [Swift Gazelle plugin] does not use the manifest to generate Bazel build
files.

### 4. Run `swift package update`

This will invoke Swift Package Manager and resolve all dependencies resulting in creation of
`Package.resolved` file.

### 5. Run `bazel mod tidy`.

This will update your `MODULE.bazel` with the correct `use_repo` declaration.

### 6. Build and test your project.

Build and test your project.

```sh
bazel test //...
```

### 7. Check in `Package.swift`, `Package.resolved`, and `MODULE.bazel`.

- The `Package.swift` file is used by `rules_swift_package_manager` to generate information about
  your project's dependencies.
- The `Package.resolved` file specifies that exact versions of the downloaded dependencies that were
  identified.
- The `MODULE.bazel` contains the declarations for your external dependencies.

### 8. Start coding

You are ready to start coding.

## Using a Swift package registry

See [our document on using a Swift package registry](/docs/swift_package_registry.md).

## Tips and Tricks

The following are a few tips to consider as you work with your repository:

- Are you trying to use a Swift package and it just won't build under Bazel? If you can figure out
  how to fix it, you can patch the Swift package. Check out [our document on patching Swift packages].

<!-- Links -->

[Bazel modules]: https://bazel.build/external/module
[Bazel's hybrid mode]: https://bazel.build/external/migration#hybrid-mode
[bzlmod]: https://bazel.build/external/overview#bzlmod
[our document on patching Swift packages]: docs/patch_swift_package.md
[CI GitHub workflow]: .github/workflows/ci.yml
[Gazelle plugin]: https://github.com/bazelbuild/bazel-gazelle/blob/master/extend.md
[Gazelle]: https://github.com/bazelbuild/bazel-gazelle
[Gazelle plugin for Swift]: https://github.com/cgrindel/swift_gazelle_plugin
[Swift Gazelle plugin]: https://github.com/cgrindel/swift_gazelle_plugin
[examples]: examples/
[rules_apple]: https://github.com/bazelbuild/rules_apple
[rules_spm]: https://github.com/cgrindel/rules_spm
[rules_swift]: https://github.com/bazelbuild/rules_swift
[rules_swift_package_manager]: https://github.com/cgrindel/rules_swift_package_manager
[tidy]: https://github.com/cgrindel/bazel-starlib/blob/main/doc/bzltidy/rules_and_macros_overview.md#tidy
