Skip to content

Commit

Permalink
Introduce a pure Swift runfiles library (#1310)
Browse files Browse the repository at this point in the history
Fixes #890 

# Main implementation

I followed guidance from @fmeum and based this implementation on the
runfiles library of `rules_python` where applicable since this was
pointed as the reference implementation.

In addition to `rules_python` implementation, this implementation uses a
similar mechanism as the C++ implementation for deducing the
`RUNFILES_DIR` and `RUNFILES_MANIFEST_FILE` location based on `argv0`.

---------

Co-authored-by: Brentley Jones <github@brentleyjones.com>
Co-authored-by: Luis Padron <heyluispadron@gmail.com>
Co-authored-by: Fabian Meumertzheim <fabian@meumertzhe.im>
  • Loading branch information
4 people committed Mar 4, 2025
1 parent d92bc8b commit 25ac7f2
Show file tree
Hide file tree
Showing 9 changed files with 979 additions and 0 deletions.
13 changes: 13 additions & 0 deletions examples/runfiles/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("//swift:swift.bzl", "swift_binary")

swift_binary(
name = "runfiles_example",
srcs = ["main.swift"],
data = [
"data/sample.txt",
],
visibility = ["//visibility:public"],
deps = [
"//swift/runfiles",
],
)
1 change: 1 addition & 0 deletions examples/runfiles/data/sample.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello runfiles
17 changes: 17 additions & 0 deletions examples/runfiles/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import BazelRunfiles

do {
let runfiles = try Runfiles.create()
// Runfiles lookup paths have the form `my_workspace/package/file`.
// Runfiles path lookup may throw.
let fileURL = try runfiles.rlocation("build_bazel_rules_swift/examples/runfiles/data/sample.txt")
print("file: \(fileURL)")

// Runfiles path lookup may return a non-existent path.
let content = try String(contentsOf: fileURL, encoding: .utf8)

assert(content == "Hello runfiles")
print(content)
} catch {
print("runfiles error: \(error)")
}
6 changes: 6 additions & 0 deletions swift/internal/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ bzl_library(
],
)

bzl_library(
name = "runfiles",
srcs = ["runfiles.bzl"],
visibility = ["//swift:__subpackages__"],
)

bzl_library(
name = "swift_autoconfiguration",
srcs = ["swift_autoconfiguration.bzl"],
Expand Down
10 changes: 10 additions & 0 deletions swift/runfiles/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
load("//swift:swift_library.bzl", "swift_library")

swift_library(
name = "runfiles",
srcs = [
"Runfiles.swift",
],
module_name = "BazelRunfiles",
visibility = ["//visibility:public"],
)
76 changes: 76 additions & 0 deletions swift/runfiles/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Swift BazelRunfiles library

This is a Bazel Runfiles lookup library for Bazel-built Swift binaries and tests.

Learn about runfiles: read [Runfiles guide](https://bazel.build/extending/rules#runfiles)
or watch [Fabian's BazelCon talk](https://www.youtube.com/watch?v=5NbgUMH1OGo).

## Usage

1. Depend on this runfiles library from your build rule:

```python
swift_binary(
name = "my_binary",
...
data = ["//path/to/my/data.txt"],
deps = ["@build_bazel_rules_swift//swift/runfiles"],
)
```

2. Include the runfiles library:

```swift
import BazelRunfiles
```

3. Create a Runfiles instance and use `rlocation` to look up runfile urls:

```swift
import BazelRunfiles

do {
let runfiles = try Runfiles.create()
let fileURL = try runfiles.rlocation("my_workspace/path/to/my/data.txt")
print("file: \(fileURL)")
} catch {
print("runfiles error: \(error)")
}
```

The code above:

- Creates a manifest- or directory-based implementation based on
the environment variables in `Process.processInfo.environment`.
See `Runfiles.create()` for more info.
- The `Runfiles.create` function uses the runfiles manifest and the runfiles
directory from the `RUNFILES_MANIFEST_FILE` and `RUNFILES_DIR` environment
variables. If not present, the function looks for the manifest and directory
near `CommandLine.arguments.first` (e.g. `argv[0]` the path of the main program).

If you want to start subprocesses, the runfiles library helps you set the required environment variables for them to find their runfiles:

```swift
import BazelRunfiles
import Foundation

do {

let runfiles = try Runfiles.create()
let executableURL = try runfiles.rlocation("my_workspace/path/to/binary")

let process = Process()
process.executableURL = executableURL
process.environment = runfiles.envVars()

do {
// Launch the process
try process.run()
process.waitUntilExit()
} catch {
// ...
}
} catch {
fatalError("runfiles error: \(error)")
}
```
Loading

0 comments on commit 25ac7f2

Please sign in to comment.