Skip to content

feat: Add Swift FileParser support for Xcode-managed SwiftPM projects#14360

Merged
markhallen merged 12 commits into
mainfrom
swift-xcode-spm-file-parser
Mar 10, 2026
Merged

feat: Add Swift FileParser support for Xcode-managed SwiftPM projects#14360
markhallen merged 12 commits into
mainfrom
swift-xcode-spm-file-parser

Conversation

@markhallen
Copy link
Copy Markdown
Contributor

@markhallen markhallen commented Mar 4, 2026

What are you trying to accomplish?

This is Part 2 of adding Xcode SwiftPM support, building on the FileFetcher work in #14332. It extends the Swift FileParser to parse dependencies from Xcode-managed SwiftPM projects that don't have a Package.swift file.

Many iOS/macOS projects use Xcode's built-in SwiftPM integration, where dependency pins are stored in Package.resolved (inside .xcodeproj/project.xcworkspace/xcshareddata/swiftpm/) and version requirements are declared as XCRemoteSwiftPackageReference entries in project.pbxproj. This change teaches the FileParser to extract Dependabot::Dependency objects from those files.

Relates to #7694

Anything you want to highlight for special attention from reviewers?

  • Feature flag gated: All new parsing logic is behind Dependabot::Experiments.enabled?(:enable_swift_xcode_spm) — when disabled, the FileParser behaves identically to before.

  • Dual-mode parsing: The parse method detects whether the project is classic SPM (has Package.swift) or Xcode SPM (has Package.resolved inside .xcodeproj but no Package.swift), and dispatches accordingly. When both exist, classic SPM mode takes precedence.

  • PackageResolvedParser: New helper that parses Package.resolved v1, v2, and v3 schemas into Dependabot::Dependency objects. Each schema version has a different JSON structure:

    • v1: object.pins with package, repositoryURL, state.version
    • v2: top-level pins with identity, location, state.version
    • v3: same as v2 but with additional originHash field

    Raises DependencyFileNotParseable for invalid JSON, unsupported schema versions, or missing pin data.

  • PbxprojParser: New helper that uses regex to extract XCRemoteSwiftPackageReference entries from project.pbxproj files. Supports all Xcode requirement kinds: upToNextMajorVersion, upToNextMinorVersion, exactVersion, versionRange, branch, and revision. These are mapped to NativeRequirement strings that the Swift UpdateChecker can process.

  • Requirement enrichment: Dependencies from Package.resolved are enriched with requirement info from matching project.pbxproj entries. The enrichment matches by normalized dependency name (derived from the repository URL). If no project.pbxproj is available (e.g. wasn't fetched), the dependency is still created with a nil requirement.

  • Multiple .xcodeproj support: When a repo contains multiple Xcode projects (each with their own Package.resolved), the parser processes each one independently and merges results via DependencySet.

How will you know you've accomplished your goal?

  • 140/140 RSpec examples pass across the full Swift test suite with zero failures.
  • 56 new tests covering all three parser files:
    • PackageResolvedParser: 15 examples covering v1/v2/v3 schemas, revision-only pins, multiple dependencies, empty pins, invalid JSON, unknown schema, SCP-style URLs
    • PbxprojParser: 10 examples covering all requirement kinds (major, minor, exact, range, branch, revision), multiple entries, empty/nil content
    • FileParser integration: 31 examples (12 existing + 19 new) covering single v2 project, v1 project, v3 project, multi-requirement types, multiple xcodeproj directories, revision-only, no pbxproj, invalid JSON, unknown schema, empty pins, both Package.swift and xcodeproj present, and flag-disabled behavior
  • RuboCop passes with no offenses (36 files inspected).
  • All existing classic SPM tests continue to pass unchanged.
  • 7 new fixture projects exercise the full range of Package.resolved schemas and pbxproj requirement types.

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.

@github-actions github-actions Bot added the L: swift Swift packages label Mar 4, 2026
Copy link
Copy Markdown
Contributor

@thavaahariharangit thavaahariharangit left a comment

Choose a reason for hiding this comment

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

LGTM

Base automatically changed from swift-xcode-spm-file-fetcher to main March 5, 2026 15:43
@markhallen markhallen dismissed thavaahariharangit’s stale review March 5, 2026 15:43

The base branch was changed.

Extend Swift FileParser to handle Xcode-managed SwiftPM projects that
don't have a Package.swift file. This adds:

- PackageResolvedParser: Parses Package.resolved v1, v2, and v3 schemas
  into Dependabot::Dependency objects with proper version/source info
- PbxprojParser: Extracts XCRemoteSwiftPackageReference entries from
  project.pbxproj files to enrich dependencies with requirement types
  (upToNextMajor, upToNextMinor, exact, range, branch, revision)
- FileParser dual-mode: Detects Xcode SPM mode (no Package.swift but
  Package.resolved present) under enable_swift_xcode_spm experiment flag
- Support for multiple .xcodeproj directories with separate resolved files
- Comprehensive test fixtures and specs for all parsers and edge cases
- xcodeproj gem (~> 1.27) added as a dependency
- Extract shared UrlHelpers.normalize_name module to eliminate
  duplication across PackageResolvedParser, PbxprojParser, and
  DependencyParser
- Remove xcodeproj gem dependency (stick with regex parsing)
- Consolidate build_v1_dependency/build_v2_dependency into single
  build_dependency method using PIN_KEYS mapping constant
- Remove unused _url parameter from PbxprojParser build_* methods
- Scope pbxproj requirements by xcodeproj_dir to prevent
  last-writer-wins on duplicate deps across xcodeprojs
- Remove unused xcodeproj_dir parameter from
  enrich_with_pbxproj_requirements and associated rubocop:disable
- Fix extract_xcodeproj_dir regex to handle nested paths
- Add extract_xcodeproj_dir unit tests
- Add regex assumption comment in PbxprojParser
- Update lockfiles to remove xcodeproj transitive dependencies
@markhallen markhallen force-pushed the swift-xcode-spm-file-parser branch from 2269eb1 to 4f37d49 Compare March 6, 2026 16:00
- Add nil-content guard in PackageResolvedParser#parse_json with clear
  DependencyFileNotParseable error instead of Sorbet T.must error
- Add error handling for NativeRequirement.new in PbxprojParser via
  parse_native_requirement helper that rescues malformed version strings
- Fix Sorbet type errors: use T.must for PIN_KEYS hash lookups
- Fix updater/Gemfile.lock: remove stale xcodeproj dependency reference
- Fix RuboCop Style/MultilineIfModifier offense
@dependabot dependabot deleted a comment from thavaahariharangit Mar 6, 2026
…rect testing of private #extract_xcodeproj_dir method\n- Align check_required_files guard to match xcode_resolved_files filtering\n- Harmonize error messages between check_required_files and parse\n- Add comment explaining declaration_string: nil in Xcode SPM metadata\n- Handle URI::InvalidURIError in UrlHelpers.normalize_name\n- Expand comment on v1-only identity downcasing in PackageResolvedParser"
…lpers; remove unnecessary blank line in file_parser_spec
@markhallen markhallen marked this pull request as ready for review March 6, 2026 19:31
@markhallen markhallen requested a review from a team as a code owner March 6, 2026 19:31
Copilot AI review requested due to automatic review settings March 6, 2026 19:31
Copy link
Copy Markdown
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 Swift FileParser support for Xcode-managed SwiftPM projects (no Package.swift) by parsing .xcodeproj-scoped Package.resolved and enriching dependencies with version constraints extracted from project.pbxproj, gated behind :enable_swift_xcode_spm.

Changes:

  • Extend Dependabot::Swift::FileParser to dispatch between classic SPM parsing and Xcode SwiftPM parsing (feature-flagged).
  • Add PackageResolvedParser (v1/v2/v3) and PbxprojParser helpers plus shared URL normalization (UrlHelpers).
  • Add fixtures and RSpec coverage for schema variants, requirement kinds, multi-project repos, and error cases.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
swift/lib/dependabot/swift/file_parser.rb Adds Xcode SPM parsing path + feature-flag gating and requirement enrichment logic
swift/lib/dependabot/swift/file_parser/package_resolved_parser.rb Parses Xcode Package.resolved schemas into Dependabot::Dependency objects
swift/lib/dependabot/swift/file_parser/pbxproj_parser.rb Extracts Xcode XCRemoteSwiftPackageReference requirements from project.pbxproj
swift/lib/dependabot/swift/url_helpers.rb Centralizes URL→dependency-name normalization used by multiple parsers
swift/lib/dependabot/swift/file_parser/dependency_parser.rb Switches dependency name normalization to the shared UrlHelpers
swift/spec/dependabot/swift/file_parser_spec.rb Integration specs for Xcode SPM parsing and flag-disabled behavior
swift/spec/dependabot/swift/file_parser/package_resolved_parser_spec.rb Unit specs for v1/v2/v3 schemas + invalid/unknown schema handling
swift/spec/dependabot/swift/file_parser/pbxproj_parser_spec.rb Unit specs for requirement kinds and edge cases
swift/spec/fixtures/projects/** Fixture Xcode projects for schema/requirement permutations and failure cases
Comments suppressed due to low confidence (1)

swift/lib/dependabot/swift/file_parser.rb:186

  • The experiment-enabled required-file check here only accepts Package.resolved paths that include .xcodeproj/, but Dependabot::Swift::FileFetcher.required_files_in? currently treats any Package.resolved as sufficient when the experiment is enabled. This mismatch can lead to repos passing file fetching validation but failing in the parser with a missing-files error. Consider aligning this check with the fetcher’s criteria (or tightening the fetcher) so validation behavior is consistent across the Swift ecosystem.
        if Dependabot::Experiments.enabled?(:enable_swift_xcode_spm)
          return if dependency_files.any? { |f| f.name.end_with?("Package.resolved") && f.name.include?(".xcodeproj/") }

          raise "No Package.swift or Xcode Package.resolved found!"
        end

Comment thread swift/lib/dependabot/swift/file_parser.rb Outdated
Comment thread swift/lib/dependabot/swift/file_parser/pbxproj_parser.rb Outdated
Comment thread swift/lib/dependabot/swift/file_parser.rb
kbukum1
kbukum1 previously approved these changes Mar 6, 2026
Comment thread swift/lib/dependabot/swift/file_parser/pbxproj_parser.rb Outdated
Comment thread swift/lib/dependabot/swift/url_helpers.rb
@kbukum1 kbukum1 self-requested a review March 6, 2026 23:14
…, narrow rescue

- Use xcode_resolved_files.any? in check_required_files to match the
  succeeding when only support Package.resolved files are present
- Fix PbxprojParser class comment to say 'keyed by normalized dependency
  name' instead of 'keyed by normalized URL'
- Narrow rescue in parse_native_requirement from StandardError to
  RuntimeError and Gem::Requirement::BadRequirementError to avoid masking
  unexpected bugs
…Moves the Xcode-managed SwiftPM parsing logic out of FileParser into a\ndedicated helper class under file_parser/, following the same pattern\nused by other ecosystems (e.g. bundler's GemfileDeclarationFinder).\n\nThe new XcodeSpmResolver handles:\n- Aggregating pbxproj requirements scoped by xcodeproj directory\n- Enriching Package.resolved dependencies with pbxproj metadata\n- Extracting xcodeproj directory paths from file names\n\nFileParser#parse_xcode_spm now delegates to the resolver, keeping the\nmain class focused on routing between classic SPM and Xcode SPM paths."
@markhallen markhallen merged commit 066042f into main Mar 10, 2026
119 of 120 checks passed
@markhallen markhallen deleted the swift-xcode-spm-file-parser branch March 10, 2026 11:27
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.

5 participants