Skip to content

feat: Extend Swift FileFetcher for Xcode-managed SwiftPM (.xcodeproj) support#14332

Open
markhallen wants to merge 8 commits intomainfrom
swift-xcode-spm-file-fetcher
Open

feat: Extend Swift FileFetcher for Xcode-managed SwiftPM (.xcodeproj) support#14332
markhallen wants to merge 8 commits intomainfrom
swift-xcode-spm-file-fetcher

Conversation

@markhallen
Copy link
Contributor

@markhallen markhallen commented Mar 2, 2026

What are you trying to accomplish?

This adds support for discovering Swift Package Manager dependencies managed through Xcode projects (.xcodeproj), behind the :enable_swift_xcode_spm feature flag.

Currently, the Swift FileFetcher only supports repos with a top-level Package.swift manifest. Many iOS/macOS projects use Xcode's built-in SwiftPM integration instead, where dependency pins live at .xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved and package references are declared in project.pbxproj. This change allows Dependabot to discover and fetch those files.

Relates to #7694

Anything you want to highlight for special attention from reviewers?

  • Feature flag gated: All new behavior is behind Dependabot::Experiments.enabled?(:enable_swift_xcode_spm). When disabled, the FileFetcher behaves identically to before.
  • package_manifest changed from fetch_file_from_host to fetch_file_if_present: This is necessary so that repos without Package.swift don't raise immediately. The base class validation (required_files_in?) still catches repos that have neither Package.swift nor a valid .xcodeproj.
  • Memoization fix: package_resolved was using = instead of ||= — fixed as part of this PR (pre-existing bug).
  • Recursive directory scanning: scan_for_xcodeproj_dirs walks the repo tree up to 10 levels deep, skipping directories like .git, .build, Pods, node_modules, and vendor that are unlikely to contain Xcode projects.
  • project.pbxproj fetched as support file: It's needed for future parsing of XCRemoteSwiftPackageReference entries but isn't a primary dependency file.
  • Validation requires Package.resolved: A .xcodeproj without a Package.resolved is not considered valid — there are no dependency pins to work with.

How will you know you've accomplished your goal?

  • 15 RSpec examples pass covering all scenarios: single/multiple/nested .xcodeproj, coexistence with Package.swift, missing Package.resolved, and flag-disabled behavior.
  • RuboCop passes with no offenses.
  • The shared "a dependency file fetcher" examples continue to pass, confirming no new public instance methods were added.
  • When the flag is disabled, the FileFetcher behaves exactly as before (verified by existing tests passing unchanged).

Checklist

  • I have run the complete test suite to ensure all tests and linters pass.
  • I have thoroughly tested my code changes to ensure they work as expected, including adding additional tests for new functionality.
  • I have written clear and descriptive commit messages.
  • I have provided a detailed description of the changes in the pull request, including the problem it addresses, how it fixes the problem, and any relevant details about the implementation.
  • I have ensured that the code is well-documented and easy to understand.

Add five fixture projects covering different Xcode SPM configurations:
- xcode_project: single .xcodeproj with Package.resolved
- xcode_project_multiple: two .xcodeproj directories
- xcode_project_nested: .xcodeproj in a subdirectory
- xcode_project_no_resolved: .xcodeproj without Package.resolved
- xcode_project_with_manifest: both Package.swift and .xcodeproj
…dencies

Behind the :enable_swift_xcode_spm feature flag, the FileFetcher now:
- Recursively scans for .xcodeproj directories
- Fetches project.pbxproj as a support file
- Fetches Package.resolved from the Xcode shared workspace data
- Falls back to standard Package.swift flow when present
- Skips irrelevant directories (.git, .build, Pods, node_modules, etc.)

When the flag is disabled, behavior is unchanged.
Cover all Xcode SPM scenarios with the feature flag enabled:
- Single .xcodeproj with Package.resolved
- Multiple .xcodeproj directories
- Nested .xcodeproj in subdirectories
- Coexistence with Package.swift (classic flow takes priority)
- .xcodeproj without Package.resolved (raises error)
- Flag disabled with .xcodeproj only (raises error)
@github-actions github-actions bot added the L: swift Swift packages label Mar 3, 2026
@markhallen markhallen marked this pull request as ready for review March 3, 2026 14:19
@markhallen markhallen requested a review from a team as a code owner March 3, 2026 14:19
Copilot AI review requested due to automatic review settings March 3, 2026 14:19
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds experimental Swift FileFetcher support to discover Xcode-managed SwiftPM dependency files (inside .xcodeproj bundles) when :enable_swift_xcode_spm is enabled, while keeping the classic Package.swift flow intact.

Changes:

  • Extend Swift FileFetcher to (optionally) scan for .xcodeproj directories and fetch project.pbxproj (support file) plus the Xcode SwiftPM Package.resolved.
  • Update Swift FileFetcher behavior to not immediately error when Package.swift is absent (so Xcode-only repos can be inspected under the flag).
  • Add RSpec coverage and fixtures for single/multiple/nested .xcodeproj scenarios and mixed repos.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
swift/lib/dependabot/swift/file_fetcher.rb Adds feature-flagged .xcodeproj discovery + fetch logic, plus required-files logic updates and a memoization fix.
swift/spec/dependabot/swift/file_fetcher_spec.rb Adds test coverage for flag-enabled/disabled behavior and various .xcodeproj layouts.
swift/spec/fixtures/projects/xcode_project_with_manifest/* Fixture repo containing both classic Package.swift + Package.resolved and an .xcodeproj SwiftPM resolved file.
swift/spec/fixtures/projects/xcode_project/* Fixture repo containing an .xcodeproj with project.pbxproj and Xcode SwiftPM Package.resolved.
swift/spec/fixtures/projects/xcode_project_multiple/* Fixture repo with multiple .xcodeproj directories each containing SwiftPM resolved/pbxproj files.
swift/spec/fixtures/projects/xcode_project_nested/* Fixture repo with a nested .xcodeproj SwiftPM resolved/pbxproj under a subdirectory.
swift/spec/fixtures/projects/xcode_project_no_resolved/* Fixture repo with .xcodeproj but no SwiftPM Package.resolved (invalid case).
Comments suppressed due to low confidence (1)

swift/lib/dependabot/swift/file_fetcher.rb:54

  • The comment about the base class validating against required_files_in? is a bit misleading: Dependabot::FileFetchers::Base#files raises DependencyFileNotFound early when fetch_files returns an empty array, before calling required_files_in?. Consider updating/removing this comment or raising a more specific error from here when neither Package.swift nor any Xcode SwiftPM files are found.
        # Base class validates returned files against required_files_in? and raises if needed
        return fetched_files unless Dependabot::Experiments.enabled?(:enable_swift_xcode_spm)

- Revert required_files_in? to always require Package.swift, preventing
  xcodeproj-only repos from passing validation when the parser still
  requires Package.swift (avoids downstream DependencyFileNotFound)
- Simplify required_files_message (no experiment check needed)
- Reduce MAX_SCAN_DEPTH from 10 to 3 (xcodeproj is rarely >3 levels deep)
- Add MAX_XCODEPROJ_RESULTS = 5 with early exit to bound API calls
- Update tests: xcodeproj-only scenarios now expect DependencyFileNotFound
… fixture

- Replace recursive scan_for_xcodeproj_dirs with a single repo_contents
  call on the configured directory, matching existing fetcher behavior
- Remove MAX_SCAN_DEPTH, MAX_XCODEPROJ_RESULTS, SKIP_DIRECTORIES constants
- Remove Pathname require (no longer needed)
- Remove xcode_project_nested fixture (no longer applicable)
- Restore flag-gated required_files_in? and required_files_message
  (the parser mismatch is expected; parser support comes in a follow-up)
Copy link
Contributor

@AbhishekBhaskar AbhishekBhaskar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L: swift Swift packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants