SPM Build Failure in RBE
## Summary
The `generate_modulemap` rule in `rules_swift_package_manager` generates modulemaps with relative paths containing extensive parent-directory traversal (e.g., `../../../../../../../external/...`). These paths fail to resolve in RBE environments where the virtual filesystem restricts traversal across directory boundaries.
## Environment
- **Affected Package**: `rules_swift_package_manager` version 1.13.0 (and main)
- **Build System**: Bazel 7.0+
- **Platforms**: Reproduced on macOS
- **Build Modes**: Remote Build Execution (RBE)
## Symptoms
Build failures with errors such as:
```
error: umbrella directory not found
error: 'header_name.h' file not found
```
These errors occur when:
1. Building in RBE
2. Using Swift packages with C/Objective-C headers (requires modulemap generation)
3. The Swift compiler attempts to resolve headers through generated modulemaps
## Reproduction
[A sample project](https://github.com/user-attachments/files/26616714/spm_rbe_build_failure_example.zip) is attached.
```bash
bazel build //:Example --config=rbe --remote_executor=[your-rbe-url]
```
The build error is attached:
```bash
Sources/main.swift:7:17: error: cannot find 'NBPhoneNumberUtil' in scope
5 | // This will trigger umbrella directory modulemap generation which fails on RBE
6 |
7 | let phoneUtil = NBPhoneNumberUtil.sharedInstance()
| `- error: cannot find 'NBPhoneNumberUtil' in scope
8 |
9 | do {
Sources/main.swift:11:76: error: cannot infer contextual base in reference to member 'INTERNATIONAL'
9 | do {
10 | let phoneNumber = try phoneUtil.parse("+14155552671", defaultRegion: "US")
11 | let formattedNumber = try phoneUtil.format(phoneNumber, numberFormat: .INTERNATIONAL)
| `- error: cannot infer contextual base in reference to member 'INTERNATIONAL'
12 | print("Formatted phone number: \(formattedNumber ?? "error")")
13 | } catch {
error: emit-module command failed with exit code 1 (use -v to see invocation)
/private/tmp/worker/shard/operations/c79f2baf-43ad-4138-a2b1-7d402bc7a02d/bazel-out/darwin_arm64-fastbuild/bin/external/rules_swift_package_manager++swift_deps+swiftpkg_libphonenumber_ios/libPhoneNumberInternal.rspm_modulemap_modulemap/_/module.modulemap:3:14: warning: umbrella directory '../../../../../../../external/rules_swift_package_manager++swift_deps+swiftpkg_libphonenumber_ios/libPhoneNumberInternal' not found
1 | module "libPhoneNumberInternal" {
2 |
3 | umbrella "../../../../../../../external/rules_swift_package_manager++swift_deps+swiftpkg_libphonenumber_ios/libPhoneNumberInternal"
| `- warning: umbrella directory '../../../../../../../external/rules_swift_package_manager++swift_deps+swiftpkg_libphonenumber_ios/libPhoneNumberInternal' not found
4 |
5 |
/private/tmp/worker/shard/operations/c79f2baf-43ad-4138-a2b1-7d402bc7a02d/bazel-out/darwin_arm64-fastbuild/bin/external/rules_swift_package_manager++swift_deps+swiftpkg_libphonenumber_ios/libPhoneNumber.rspm_modulemap_modulemap/_/module.modulemap:3:14: warning: umbrella directory '../../../../../../../external/rules_swift_package_manager++swift_deps+swiftpkg_libphonenumber_ios/libPhoneNumber' not found
1 | module "libPhoneNumber" {
2 |
3 | umbrella "../../../../../../../external/rules_swift_package_manager++swift_deps+swiftpkg_libphonenumber_ios/libPhoneNumber"
| `- warning: umbrella directory '../../../../../../../external/rules_swift_package_manager++swift_deps+swiftpkg_libphonenumber_ios/libPhoneNumber' not found
4 |
5 |
Sources/main.swift:7:17: error: cannot find 'NBPhoneNumberUtil' in scope
5 | // This will trigger umbrella directory modulemap generation which fails on RBE
6 |
7 | let phoneUtil = NBPhoneNumberUtil.sharedInstance()
| `- error: cannot find 'NBPhoneNumberUtil' in scope
8 |
9 | do {
Sources/main.swift:11:76: error: cannot infer contextual base in reference to member 'INTERNATIONAL'
9 | do {
10 | let phoneNumber = try phoneUtil.parse("+14155552671", defaultRegion: "US")
11 | let formattedNumber = try phoneUtil.format(phoneNumber, numberFormat: .INTERNATIONAL)
| `- error: cannot infer contextual base in reference to member 'INTERNATIONAL'
12 | print("Formatted phone number: \(formattedNumber ?? "error")")
13 | } catch {
Target //:Example failed to build
```
## Root Cause
### Current Behavior
When `rules_swift_package_manager` generates modulemaps for packages with C headers:
1. Modulemap is created in `bazel-out/darwin-fastbuild/bin/external/swiftpkg_*/...`
2. Headers exist in `external/swiftpkg_*/...` (workspace root)
3. The modulemap references headers using relative paths:
```
module "MyModule" {
umbrella "../../../../../../../external/swiftpkg_package/Headers"
}
```
4. This path traverses from deep within `bazel-out/` back to the workspace root
### Why It Fails
RBE virtual filesystems and strict sandboxes isolate the execution tree. Path components like `bazel-out/` and `external/` may exist in separate filesystem boundaries that don't permit `../` traversal between them. The Swift compiler cannot resolve these paths and fails with header/umbrella directory errors.
## Potential Solution
Two patches are required to fix this issue:
#### Patch 1: Replace Umbrella Directory with Symlinked Headers
```bash
--- swiftpkg/internal/generate_modulemap.bzl
+++ swiftpkg/internal/generate_modulemap.bzl
@@ -9,7 +9,6 @@
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
load(":clang_files.bzl", "clang_files")
-load(":module_maps.bzl", "write_module_map")
ModuleMapInfo = provider(
doc = "Contains information about a generate module map.",
@@ -40,33 +39,39 @@
if len(hdrs) == 0:
fail("No header files were provided.")
- # Use umbrella directory if all headers share a common root
- umbrella_dir = None
- if len(hdrs) > 0:
- # Find the longest common directory prefix
- dirs = [h.dirname for h in hdrs]
- if len(dirs) > 0:
- common_parts = dirs[0].split("/")
- for d in dirs[1:]:
- d_parts = d.split("/")
- new_common = []
- for i in range(min(len(common_parts), len(d_parts))):
- if common_parts[i] == d_parts[i]:
- new_common.append(common_parts[i])
- else:
- break
- common_parts = new_common
- if len(common_parts) > 0:
- umbrella_dir = "/".join(common_parts)
-
- write_module_map(
- actions = ctx.actions,
- module_map_file = modulemap_file,
- module_name = module_name,
- dependent_module_names = uses,
- umbrella_directory = umbrella_dir,
- public_headers = hdrs if not umbrella_dir else [],
+ # Create symlinks for each header adjacent to the modulemap file.
+ # This avoids relative path traversal (../../..) from bazel-out/ to
+ # external/, which fails on RBE virtual filesystems that do not
+ # support parent-directory traversal across directory boundaries.
+ symlink_hdrs = []
+ header_lines = []
+ for hdr in hdrs:
+ symlink_relpath = "hdrs/" + hdr.path
+ symlink_name = "{}_modulemap/_/{}".format(ctx.attr.name, symlink_relpath)
+ symlink = ctx.actions.declare_file(symlink_name)
+ ctx.actions.symlink(output = symlink, target_file = hdr)
+ symlink_hdrs.append(symlink)
+ header_lines.append(' header "{}"'.format(symlink_relpath))
+
+ # Build the modulemap content referencing the local symlinks.
+ content = ctx.actions.args()
+ content.set_param_file_format("multiline")
+ content.add(module_name, format = 'module "%s" {')
+ content.add("")
+ for line in header_lines:
+ content.add(line)
+ content.add("")
+ for use in uses:
+ content.add(use, format = ' use "%s"')
+ content.add("")
+ content.add(" export *")
+ content.add("}")
+
+ ctx.actions.write(
+ output = modulemap_file,
+ content = content,
)
+
provider_hdr = [modulemap_file]
# This target itself is a modulemap, so suppress any module generation
@@ -83,7 +88,7 @@
),
CcInfo(
compilation_context = cc_common.create_compilation_context(
- headers = depset(provider_hdr),
+ headers = depset(provider_hdr + hdrs + symlink_hdrs),
direct_public_headers = provider_hdr,
includes = depset([modulemap_file.dirname]),
),
```
#### Patch 2: Propagate Modulemap to Child Libraries
```bash
--- swiftpkg/internal/swiftpkg_build_files.bzl
+++ swiftpkg/internal/swiftpkg_build_files.bzl
@@ -516,6 +516,15 @@
)
attrs["aspect_hints"] = [aspect_hint_target_name]
+ # Add the modulemap target as a dep of child libraries so its CcInfo
+ # (containing header files and symlinks) flows through the
+ # swift_clang_module_aspect. Without this, the modulemap's headers
+ # are not included as action inputs on RBE.
+ if hint_module_map and not clang_src_info.modulemap_path:
+ child_deps = list(attrs.get("deps", []))
+ child_deps.append(":{}".format(modulemap_target_name))
+ attrs["deps"] = child_deps
+
rule_kind = clang_kinds.library
if clang_src_info.organized_srcs.c_srcs:
```
0 条评论