ITADN

SPM Build Failure in RBE

#2192Opencongt 创建于 2026-04-09
C
congtcommented
## 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 条评论