Skip to content

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

Draft
markhallen wants to merge 1 commit intoswift-xcode-spm-file-fetcherfrom
swift-xcode-spm-file-parser
Draft

feat: Add Swift FileParser support for Xcode-managed SwiftPM projects#14360
markhallen wants to merge 1 commit intoswift-xcode-spm-file-fetcherfrom
swift-xcode-spm-file-parser

Conversation

@markhallen
Copy link
Contributor

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.

  • xcodeproj gem added: Added xcodeproj ~> 1.27 as a dependency for future use in more robust pbxproj parsing.

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.

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
@github-actions github-actions bot added the L: swift Swift packages label Mar 4, 2026
Copy link
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

Copy link
Contributor

Code Review: Xcode SPM FileParser & FileFetcher Support

🔴 High Priority

  • Issue: Duplicated normalize_name method — identical implementation in two files

    • Location: swift/lib/dependabot/swift/file_parser/package_resolved_parser.rb:174 and swift/lib/dependabot/swift/file_parser/pbxproj_parser.rb:185
    • Recommendation: Extract to a shared module (e.g. Dependabot::Swift::UrlNormalizer) or a private method on the parent FileParser class. Both parse URI identically — any future divergence would be a bug.
  • Issue: Scan limits removed — xcodeproj_dirs has no bounds on results

    • Location: swift/lib/dependabot/swift/file_fetcher.rb:80-87
    • Recommendation: The earlier commit 0d52fa924 intentionally added MAX_XCODEPROJ_RESULTS = 5 and MAX_SCAN_DEPTH = 3 to bound API calls. The latest commit removed all limits. A repository with many .xcodeproj directories (e.g., monorepos with CocoaPods-generated projects) could trigger excessive API calls. Re-add a MAX_XCODEPROJ_RESULTS cap or document why it was removed.
  • Issue: required_files_in? is overly broad — matches any Package.resolved file

    • Location: swift/lib/dependabot/swift/file_fetcher.rb:21
    • Recommendation: f.end_with?("Package.resolved") will match a standalone Package.resolved that isn't inside an .xcodeproj. This could cause the Swift ecosystem to "claim" repos that belong to other ecosystems or repos with only a top-level Package.resolved (no manifest). Consider adding f.include?(".xcodeproj/") as an additional condition, or at least matching the path pattern *.xcodeproj/**/Package.resolved.

🟡 Medium Priority

  • Issue: Unused parameter xcodeproj_dir with RuboCop disable comment

    • Location: swift/lib/dependabot/swift/file_parser.rb:125
    • Recommendation: enrich_with_pbxproj_requirements accepts xcodeproj_dir but never uses it, suppressed with # rubocop:disable Lint/UnusedMethodArgument. Either remove the parameter and the disable comment, or implement per-project scoping of requirements (if a dep appears in multiple xcodeprojs with different constraints, the current code takes the last-writer-wins from aggregate_pbxproj_requirements).
  • Issue: xcodeproj gem declared as dependency but never imported

    • Location: swift/dependabot-swift.gemspec:31
    • Recommendation: The gemspec adds xcodeproj ~> 1.27 but no code requires it. The PbxprojParser does its own regex parsing. Either remove the unused dependency or document future intent. Unused dependencies increase install time and attack surface.
  • Issue: required_files_in? accesses experiment flag in a class method context

    • Location: swift/lib/dependabot/swift/file_fetcher.rb:21
    • Recommendation: required_files_in? is a class method called potentially before experiments are configured. Verify that Dependabot::Experiments.enabled? is safe to call in this context and returns false by default. If experiment registration is lazy, this could silently fail.

🟢 Low Priority

  • Issue: parse_json doesn't guard against nil resolved_file.content

    • Location: swift/lib/dependabot/swift/file_parser/package_resolved_parser.rb:42
    • Recommendation: T.must(resolved_file.content) will raise a Sorbet error if content is nil, which is fine at runtime but produces a less informative error than a DependencyFileNotParseable. Consider adding a nil check with a clear error message.
  • Issue: extract_xcodeproj_dir return value xcodeproj_dir is computed but unused in parse_xcode_spm

    • Location: swift/lib/dependabot/swift/file_parser.rb:85-88
    • Recommendation: The variable is computed and passed to enrich_with_pbxproj_requirements but ignored there (see medium priority item above). This is dead code.
  • Issue: NativeRequirement constructed without error handling in PbxprojParser

    • Location: swift/lib/dependabot/swift/file_parser/pbxproj_parser.rb:109-112 and similar
    • Recommendation: If a malformed version string is extracted from a .pbxproj, NativeRequirement.new could raise an unexpected error. Consider wrapping in a rescue for robustness against malformed Xcode projects.

Summary Table

Priority Issue Effort
🔴 High Duplicated normalize_name in two parsers Small
🔴 High Scan limits removed from xcodeproj_dirs Small
🔴 High required_files_in? overly broad — matches any Package.resolved Small
🟡 Medium Unused xcodeproj_dir parameter with RuboCop disable Small
🟡 Medium xcodeproj gem dependency declared but never used Small
🟡 Medium Experiment flag in class method context Small
🟢 Low parse_json nil-content guard Small
🟢 Low Dead code: extract_xcodeproj_dir result unused Small
🟢 Low No error handling for NativeRequirement.new in pbxproj parser Small

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.

2 participants