feat: Add Swift FileParser support for Xcode-managed SwiftPM projects#14360
Merged
Conversation
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
2269eb1 to
4f37d49
Compare
- 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
…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
Contributor
There was a problem hiding this comment.
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::FileParserto dispatch between classic SPM parsing and Xcode SwiftPM parsing (feature-flagged). - Add
PackageResolvedParser(v1/v2/v3) andPbxprojParserhelpers 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.resolvedpaths that include.xcodeproj/, butDependabot::Swift::FileFetcher.required_files_in?currently treats anyPackage.resolvedas 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
kbukum1
previously approved these changes
Mar 6, 2026
…, 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."
AbhishekBhaskar
approved these changes
Mar 9, 2026
kbukum1
approved these changes
Mar 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
FileParserto parse dependencies from Xcode-managed SwiftPM projects that don't have aPackage.swiftfile.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 asXCRemoteSwiftPackageReferenceentries inproject.pbxproj. This change teaches the FileParser to extractDependabot::Dependencyobjects 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
parsemethod detects whether the project is classic SPM (hasPackage.swift) or Xcode SPM (hasPackage.resolvedinside.xcodeprojbut noPackage.swift), and dispatches accordingly. When both exist, classic SPM mode takes precedence.PackageResolvedParser: New helper that parsesPackage.resolvedv1, v2, and v3 schemas intoDependabot::Dependencyobjects. Each schema version has a different JSON structure:object.pinswithpackage,repositoryURL,state.versionpinswithidentity,location,state.versionoriginHashfieldRaises
DependencyFileNotParseablefor invalid JSON, unsupported schema versions, or missing pin data.PbxprojParser: New helper that uses regex to extractXCRemoteSwiftPackageReferenceentries fromproject.pbxprojfiles. Supports all Xcode requirement kinds:upToNextMajorVersion,upToNextMinorVersion,exactVersion,versionRange,branch, andrevision. These are mapped toNativeRequirementstrings that the SwiftUpdateCheckercan process.Requirement enrichment: Dependencies from
Package.resolvedare enriched with requirement info from matchingproject.pbxprojentries. The enrichment matches by normalized dependency name (derived from the repository URL). If noproject.pbxprojis available (e.g. wasn't fetched), the dependency is still created with anilrequirement.Multiple
.xcodeprojsupport: When a repo contains multiple Xcode projects (each with their ownPackage.resolved), the parser processes each one independently and merges results viaDependencySet.How will you know you've accomplished your goal?
PackageResolvedParser: 15 examples covering v1/v2/v3 schemas, revision-only pins, multiple dependencies, empty pins, invalid JSON, unknown schema, SCP-style URLsPbxprojParser: 10 examples covering all requirement kinds (major, minor, exact, range, branch, revision), multiple entries, empty/nil contentFileParserintegration: 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 behaviorChecklist