├── .all-contributorsrc ├── .editorconfig ├── .gitattributes ├── .github ├── .commitlint.rules.js ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── invite-contributors.yml └── workflows │ ├── conventional-pr.yml │ ├── release.yml │ └── xcodeproj.yml ├── .gitignore ├── .mise.toml ├── .mise └── tasks │ ├── build │ ├── build-linux │ ├── lint │ ├── lint-fix │ ├── test │ └── test-linux ├── .spi.yml ├── .swift-version ├── .swiftformat ├── .swiftlint.yml ├── Assets └── diagram.png ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Documentation ├── README.md ├── getting-started.md └── migration-guides.md ├── Fixtures ├── FileSharedAcrossTargets │ ├── FileSharedAcrossTargets.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── pepicrft.xcuserdatad │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ ├── FileSharedAcrossTargets │ │ ├── FileSharedAcrossTargets.h │ │ └── SharedHeader.h │ └── FileSharedAcrossTargetsTests │ │ └── FileSharedAcrossTargetsTests.swift ├── Schemes │ ├── AppClip.xcscheme │ ├── BuildArchitectures.xcscheme │ ├── MinimalInformation.xcscheme │ ├── NoBlueprintID.xcscheme │ ├── RunPostActionsOnFailure.xcscheme │ ├── RunnableWithoutBuildableReference.xcscheme │ └── xcschememanagement.plist ├── SynchronizedRootGroups │ ├── .gitignore │ ├── SynchronizedRootGroups.xcodeproj │ │ ├── project.pbxproj │ │ └── xcuserdata │ │ │ └── pepicrft.xcuserdatad │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ └── SynchronizedRootGroups │ │ ├── Exception │ │ └── Exception.swift │ │ └── SynchronizedRootGroups.swift ├── TargetWithCustomBuildRules │ ├── TargetWithCustomBuildRules.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ ├── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ └── xcuserdata │ │ │ │ └── pepicrft.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ └── xcuserdata │ │ │ └── pepicrft.xcuserdatad │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ └── TargetWithCustomBuildRules │ │ ├── TargetWithCustomBuildRules.h │ │ └── TargetWithCustomBuildRules.m ├── WithoutWorkspace │ ├── .gitignore │ ├── Package.swift │ ├── Sources │ │ └── WithoutWorkspace │ │ │ └── WithoutWorkspace.swift │ ├── Tests │ │ └── WithoutWorkspaceTests │ │ │ └── WithoutWorkspaceTests.swift │ └── WithoutWorkspace.xcodeproj │ │ ├── WithoutWorkspaceTests_Info.plist │ │ ├── WithoutWorkspace_Info.plist │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ └── xcschemes │ │ ├── WithoutWorkspace-Package.xcscheme │ │ └── xcschememanagement.plist ├── Workspace.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── WorkspaceSettings │ ├── Default.xcsettings │ ├── OriginalAbsoluteDerivedData.xcsettings │ ├── OriginalBuildSystem.xcsettings │ └── OriginalRelativeDerivedData.xcsettings ├── XCConfigs │ ├── Children.xcconfig │ └── Parent.xcconfig ├── Xcode16 │ ├── README.md │ ├── Test.xcodeproj │ │ └── project.pbxproj │ ├── Test │ │ ├── Assets.xcassets │ │ │ ├── AccentColor.colorset │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ │ └── Contents.json │ │ ├── Test.entitlements │ │ └── TestApp.swift │ └── copy.xcodeproj │ │ └── project.pbxproj ├── Xcode16ProjectReferenceOrder │ ├── Sources │ │ └── AppDelegate.swift │ ├── Test.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── App.xcscheme │ └── Wrong.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ └── xcschemes │ │ └── App.xcscheme ├── dummy.framework │ └── .gitkeep └── iOS │ ├── AppWithExtensions │ ├── AppWithExtensions.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ ├── AppWithExtensions.xcscheme │ │ │ ├── MessagesExtension.xcscheme │ │ │ ├── TodayViewExtension.xcscheme │ │ │ ├── WatchApp (Notification).xcscheme │ │ │ └── WatchApp.xcscheme │ ├── AppWithExtensions │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ ├── ContentView.swift │ │ ├── Info.plist │ │ ├── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ │ └── Contents.json │ │ └── SceneDelegate.swift │ ├── MessagesExtension │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ └── iMessage App Icon.stickersiconset │ │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── MainInterface.storyboard │ │ ├── Info.plist │ │ └── MessagesViewController.swift │ ├── TodayViewExtension │ │ ├── Base.lproj │ │ │ └── MainInterface.storyboard │ │ ├── Info.plist │ │ └── TodayViewController.swift │ ├── WatchApp Extension │ │ ├── Assets.xcassets │ │ │ ├── Complication.complicationset │ │ │ │ ├── Circular.imageset │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── Extra Large.imageset │ │ │ │ │ └── Contents.json │ │ │ │ ├── Graphic Bezel.imageset │ │ │ │ │ └── Contents.json │ │ │ │ ├── Graphic Circular.imageset │ │ │ │ │ └── Contents.json │ │ │ │ ├── Graphic Corner.imageset │ │ │ │ │ └── Contents.json │ │ │ │ ├── Graphic Large Rectangular.imageset │ │ │ │ │ └── Contents.json │ │ │ │ ├── Modular.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Utilitarian.imageset │ │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── ExtensionDelegate.swift │ │ ├── HostingController.swift │ │ ├── Info.plist │ │ ├── NotificationController.swift │ │ ├── NotificationView.swift │ │ ├── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ │ └── Contents.json │ │ └── PushNotificationPayload.apns │ └── WatchApp │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── Base.lproj │ │ └── Interface.storyboard │ │ └── Info.plist │ ├── BuildSettings.xcodeproj │ └── project.pbxproj │ ├── MyLocalPackage │ ├── .gitignore │ ├── Package.swift │ ├── README.md │ ├── Sources │ │ └── MyLocalPackage │ │ │ └── MyLocalPackage.swift │ └── Tests │ │ ├── LinuxMain.swift │ │ └── MyLocalPackageTests │ │ ├── MyLocalPackageTests.swift │ │ └── XCTestManifests.swift │ ├── MyOtherLocalPackage │ └── MyOtherLocalPackage │ │ ├── .gitignore │ │ ├── Package.swift │ │ ├── README.md │ │ ├── Sources │ │ └── MyOtherLocalPackage │ │ │ └── MyOtherLocalPackage.swift │ │ └── Tests │ │ ├── LinuxMain.swift │ │ └── MyOtherLocalPackageTests │ │ ├── MyLocalPackageTests.swift │ │ └── XCTestManifests.swift │ ├── Project.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ │ └── Package.resolved │ │ └── xcuserdata │ │ │ └── pepicrft.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ ├── xcshareddata │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ │ └── iOS.xcscheme │ └── xcuserdata │ │ ├── username1.xcuserdatad │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ │ ├── iOS-debug.xcscheme │ │ │ ├── iOS-other.xcscheme │ │ │ ├── iOS-release.xcscheme │ │ │ └── xcschememanagement.plist │ │ ├── username2.xcuserdatad │ │ └── xcschemes │ │ │ └── iOSTests.xcscheme │ │ └── username3.xcuserdatad │ │ └── xcschemes │ │ └── custom-scheme.xcscheme │ ├── ProjectWithRelativeXCLocalSwiftPackageReference │ └── ProjectWithRelativeXCLocalSwiftPackageReference.xcodeproj │ │ └── project.pbxproj │ ├── ProjectWithXCLocalSwiftPackageReference.xcodeproj │ └── project.pbxproj │ ├── ProjectWithXCLocalSwiftPackageReferences.xcodeproj │ └── project.pbxproj │ ├── ProjectWithoutProductsGroup.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ ├── SameName │ └── SameName.h │ ├── iOS.xctestplan │ ├── iOS │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── Model.xcdatamodeld │ │ └── Model.xcdatamodel │ │ │ └── contents │ ├── Private.h │ ├── Protected.h │ ├── Public.h │ └── ViewController.swift │ └── iOSTests │ ├── Info.plist │ └── iOSTests.swift ├── LICENSE.md ├── Package.resolved ├── Package.swift ├── README.md ├── Scripts └── dump-known-file-extensions.py ├── Sources └── XcodeProj │ ├── Errors │ └── Errors.swift │ ├── Extensions │ ├── AEXML+XcodeFormat.swift │ ├── Array+Extras.swift │ ├── Bool+Extras.swift │ ├── KeyedDecodingContainer+Additions.swift │ ├── NSRecursiveLock+Sync.swift │ ├── Path+Extras.swift │ ├── String+Utils.swift │ └── String+md5.swift │ ├── Objects │ ├── BuildPhase │ │ ├── BuildFileSetting.swift │ │ ├── BuildPhase.swift │ │ ├── PBXBuildFile.swift │ │ ├── PBXBuildPhase.swift │ │ ├── PBXBuildRule.swift │ │ ├── PBXCopyFilesBuildPhase.swift │ │ ├── PBXFrameworksBuildPhase.swift │ │ ├── PBXHeadersBuildPhase.swift │ │ ├── PBXResourcesBuildPhase.swift │ │ ├── PBXRezBuildPhase.swift │ │ ├── PBXShellScriptBuildPhase.swift │ │ └── PBXSourcesBuildPhase.swift │ ├── Configuration │ │ ├── BuildSettings.swift │ │ ├── XCBuildConfiguration.swift │ │ └── XCConfigurationList.swift │ ├── Files │ │ ├── PBXContainerItem.swift │ │ ├── PBXContainerItemProxy.swift │ │ ├── PBXFileElement.swift │ │ ├── PBXFileReference.swift │ │ ├── PBXFileSystemSynchronizedBuildFileExceptionSet.swift │ │ ├── PBXFileSystemSynchronizedExceptionSet.swift │ │ ├── PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet.swift │ │ ├── PBXFileSystemSynchronizedRootGroup.swift │ │ ├── PBXGroup.swift │ │ ├── PBXSourceTree.swift │ │ ├── PBXVariantGroup.swift │ │ └── XCVersionGroup.swift │ ├── Project │ │ ├── PBXObject.swift │ │ ├── PBXObjectDictionaryEntry.swift │ │ ├── PBXObjectReference.swift │ │ ├── PBXObjects.swift │ │ ├── PBXOutputSettings.swift │ │ ├── PBXProj.swift │ │ ├── PBXProjEncoder.swift │ │ ├── PBXProject.swift │ │ └── ProjectAttribute.swift │ ├── Sourcery │ │ ├── Equality.generated.swift │ │ └── Sourcery.swift │ ├── SwiftPackage │ │ ├── XCLocalSwiftPackageReference.swift │ │ ├── XCRemoteSwiftPackageReference.swift │ │ └── XCSwiftPackageProductDependency.swift │ └── Targets │ │ ├── PBXAggregateTarget.swift │ │ ├── PBXLegacyTarget.swift │ │ ├── PBXNativeTarget.swift │ │ ├── PBXProductType.swift │ │ ├── PBXReferenceProxy.swift │ │ ├── PBXTarget.swift │ │ └── PBXTargetDependency.swift │ ├── Project │ ├── WorkspaceSettings.swift │ ├── XCBreakpointList.swift │ ├── XCDebugger.swift │ ├── XCSharedData.swift │ ├── XCUserData.swift │ ├── Xcode.swift │ └── XcodeProj.swift │ ├── Protocols │ └── Writable.swift │ ├── Scheme │ ├── XCScheme+AditionalOption.swift │ ├── XCScheme+AnalyzeAction.swift │ ├── XCScheme+ArchiveAction.swift │ ├── XCScheme+BuildAction.swift │ ├── XCScheme+BuildableProductRunnable.swift │ ├── XCScheme+BuildableReference.swift │ ├── XCScheme+CommandLineArguments.swift │ ├── XCScheme+EnvironmentVariable.swift │ ├── XCScheme+ExecutionAction.swift │ ├── XCScheme+LaunchAction.swift │ ├── XCScheme+LocationScenarioReference.swift │ ├── XCScheme+PathRunnable.swift │ ├── XCScheme+ProfileAction.swift │ ├── XCScheme+RemoteRunnable.swift │ ├── XCScheme+Runnable.swift │ ├── XCScheme+SerialAction.swift │ ├── XCScheme+StoreKitConfigurationFileReference.swift │ ├── XCScheme+TestAction.swift │ ├── XCScheme+TestItem.swift │ ├── XCScheme+TestParallelization.swift │ ├── XCScheme+TestPlanReference.swift │ ├── XCScheme+TestableReference.swift │ ├── XCScheme.swift │ └── XCSchemeManagement.swift │ ├── Utils │ ├── BuildSettingsProvider.swift │ ├── CommentedString.swift │ ├── Decoders.swift │ ├── JSONDecoding.swift │ ├── PBXBatchUpdater.swift │ ├── PlistDecoding.swift │ ├── PlistValue.swift │ ├── ReferenceGenerator.swift │ └── XCConfig.swift │ └── Workspace │ ├── XCWorkspace.swift │ ├── XCWorkspaceData.swift │ ├── XCWorkspaceDataElement.swift │ ├── XCWorkspaceDataElementLocationType.swift │ ├── XCWorkspaceDataFileRef.swift │ └── XCWorkspaceDataGroup.swift ├── Templates └── Equality.stencil ├── Tests └── XcodeProjTests │ ├── Extensions │ ├── AEXML+XcodeFormatTests.swift │ ├── PathExtrasTests.swift │ ├── XCTestCase+Assertions.swift │ ├── XCTestCase+Shell.swift │ └── XCTestCase+Temporary.swift │ ├── Objects │ ├── BuildPhase │ │ ├── BuildPhaseTests.swift │ │ ├── PBXBuildFileTests.swift │ │ ├── PBXBuildPhaseTests.swift │ │ ├── PBXBuildRuleTests.swift │ │ ├── PBXCopyFilesBuildPhaseTests.swift │ │ ├── PBXFrameworksBuildPhaseTests.swift │ │ ├── PBXHeadersBuildPhaseTests.swift │ │ ├── PBXResourcesBuildPhaseTests.swift │ │ ├── PBXRezBuildPhaseTests.swift │ │ ├── PBXShellScriptBuildPhaseTests.swift │ │ ├── PBXSourcesBuildPhase+Fixtures.swift │ │ └── PBXSourcesBuildPhaseTests.swift │ ├── Configuration │ │ ├── BuildFileSettingTests.swift │ │ ├── BuildSettingTests.swift │ │ ├── XCBuildConfiguration+Fixtures.swift │ │ ├── XCBuildConfigurationTests.swift │ │ ├── XCConfigurationList+Fixtures.swift │ │ └── XCConfigurationListTests.swift │ ├── Files │ │ ├── PBXContainerItemProxyTests.swift │ │ ├── PBXFileElementTests.swift │ │ ├── PBXFileReference+Fixtures.swift │ │ ├── PBXFileReferenceTests.swift │ │ ├── PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift │ │ ├── PBXFileSystemSynchronizedBuildFileExceptionSetTests.swift │ │ ├── PBXFileSystemSynchronizedRootGroup+Fixtures.swift │ │ ├── PBXFileSystemSynchronizedRootGroupTests.swift │ │ ├── PBXGroup+Fixtures.swift │ │ ├── PBXGroupTests.swift │ │ ├── PBXSourceTreeTests.swift │ │ ├── PBXVariantGroupTests.swift │ │ ├── XCVersionGroup+Fixtures.swift │ │ └── XCVersionGroupTests.swift │ ├── Project │ │ ├── PBXOutputSettingsTests.swift │ │ ├── PBXProj+Fixtures.swift │ │ ├── PBXProj+XCTest.swift │ │ ├── PBXProjEncoderTests.swift │ │ ├── PBXProjIntegrationTests.swift │ │ ├── PBXProjObjectsHelpersTests.swift │ │ ├── PBXProject+Fixtures.swift │ │ └── PBXProjectTests.swift │ ├── SwiftPackage │ │ ├── XCLocalSwiftPackageReferenceTests.swift │ │ ├── XCRemoteSwiftPackageReferenceTests.swift │ │ └── XCSwiftPackageProductDependencyTests.swift │ └── Targets │ │ ├── PBXAggregateTargetTests.swift │ │ ├── PBXLegacyTargetTests.swift │ │ ├── PBXNativeTargetTests.swift │ │ ├── PBXProductTypeTests.swift │ │ ├── PBXReferenceProxyTests.swift │ │ ├── PBXTarget+Fixtures.swift │ │ ├── PBXTargetDependencyTests.swift │ │ └── PBXTargetTests.swift │ ├── Project │ ├── WorkspaceSettingsTests.swift │ ├── XCBreakpointListTests.swift │ ├── XCUserDataTests.swift │ └── XcodeProjIntegrationTests.swift │ ├── Scheme │ ├── XCScheme+BuildableReferenceTests.swift │ ├── XCSchemeManagementTests.swift │ └── XCSchemeTests.swift │ ├── Tests │ ├── Fixtures.swift │ └── testWrite.swift │ ├── Utils │ ├── BuildSettingsProviderTests.swift │ ├── CommentedStringTests.swift │ ├── ObjectReferenceTests.swift │ ├── PBXBatchUpdaterTests.swift │ ├── PlistValueTests.swift │ ├── ReferenceGeneratorTests.swift │ └── XCConfigTests.swift │ └── Workspace │ ├── XCWorkspaceDataElementTests.swift │ ├── XCWorkspaceDataTests.swift │ └── XCWorkspaceTests.swift ├── Tuist.swift ├── cliff.toml └── renovate.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{json,js,jsx,html,css}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | CHANGELOG merge=union 2 | -------------------------------------------------------------------------------- /.github/.commitlint.rules.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'header-max-length': [2, 'always', 200], 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: tuistapp 2 | github: tuist 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Context 🕵️‍♀️ 2 | > Provide information that helps with understanding your issue. For example your use case that the project doesn't cover, what you were doing when you found the bug... You can also provide the version of the library that you were using, how you integrated it with your project, the platform version... 3 | 4 | ## What 🌱 5 | > Describe here your issue, a bug you found, an idea that you had, something that you think can be improved... 6 | 7 | ## Proposal 🎉 8 | > Attach your own proposal *(if you have it)*. We'll discuss in on the issue to find the best one that fits into the library. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Resolves https://github.com/tuist/xcodeproj/issues/YYY 2 | 3 | ### Short description 📝 4 | > Describe here the purpose of your PR. 5 | 6 | ### Solution 📦 7 | > Describe the solution you came up with and the reasons that led you to that solution. If you thought about other solutions don't forget about mentioning them. 8 | 9 | ### Implementation 👩‍💻👨‍💻 10 | > Detail in a checklist the steps that you took to implement the PR. 11 | 12 | - [ ] Step 1 13 | - [ ] Step 2 14 | -------------------------------------------------------------------------------- /.github/invite-contributors.yml: -------------------------------------------------------------------------------- 1 | team: Contributors 2 | -------------------------------------------------------------------------------- /.github/workflows/conventional-pr.yml: -------------------------------------------------------------------------------- 1 | name: conventional-pr 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | - master 7 | types: 8 | - opened 9 | - edited 10 | - synchronize 11 | jobs: 12 | lint-pr: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: CondeNast/conventional-pull-request-action@v0.2.0 17 | with: 18 | commitTitleMatch: false 19 | commitlintRulesPath: ".github/.commitlint.rules.js" 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/xcodeproj.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idname 2 | name: XcodeProj 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: {} 9 | 10 | concurrency: 11 | group: xcodeproj-${{ github.head_ref }} 12 | cancel-in-progress: true 13 | 14 | env: 15 | MISE_EXPERIMENTAL: 1 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | 18 | jobs: 19 | build: 20 | name: Build (macOS) 21 | runs-on: macos-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: jdx/mise-action@v2 25 | - name: Build 26 | run: mise run build 27 | build-linux: 28 | name: Build (Linux) 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v3 32 | - uses: jdx/mise-action@v2 33 | - run: mise use swift@6.0.2 34 | - name: Build 35 | run: swift build --configuration release 36 | test: 37 | name: Test (macOS / Xcode) 38 | runs-on: macos-latest 39 | steps: 40 | - uses: actions/checkout@v3 41 | - uses: jdx/mise-action@v2 42 | - name: Run tests 43 | run: mise run test 44 | 45 | test-linux: 46 | name: Test (Linux) 47 | runs-on: ubuntu-latest 48 | steps: 49 | - uses: actions/checkout@v3 50 | - uses: jdx/mise-action@v2 51 | - run: mise use swift@6.0.2 52 | - run: | 53 | git config --global user.email 'xcodeproj@tuist.dev' 54 | git config --global user.name 'xcodeproj' 55 | git config --global init.defaultBranch main 56 | - name: Test 57 | run: swift test 58 | 59 | lint: 60 | name: Lint 61 | runs-on: macos-latest 62 | steps: 63 | - uses: actions/checkout@v3 64 | - uses: jdx/mise-action@v2 65 | - run: mise run lint 66 | -------------------------------------------------------------------------------- /.mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | swiftformat = "0.54.3" 3 | tuist = "4.50.2" 4 | swiftlint = "0.55.1" 5 | "git-cliff" = "2.4.0" 6 | -------------------------------------------------------------------------------- /.mise/tasks/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # mise description="Build the package in the host" 3 | 4 | swift build --package-path $MISE_PROJECT_ROOT --configuration debug 5 | -------------------------------------------------------------------------------- /.mise/tasks/build-linux: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # mise description="Build on Linux" 3 | 4 | CONTAINER_RUNTIME=$(command -v podman || command -v docker) 5 | 6 | if [ -z "$CONTAINER_RUNTIME" ]; then 7 | echo "Neither podman nor docker is available. Please install one to proceed." 8 | exit 1 9 | fi 10 | 11 | $CONTAINER_RUNTIME run --rm --interactive --tty --volume "$(pwd):/package" --workdir "/package" swift:5.10.1 bash -c " 12 | git config --global user.email 'xcodeproj@tuist.io' && 13 | git config --global user.name 'xcodeproj' && 14 | git config --global init.defaultBranch main && 15 | swift build --configuration release 16 | " 17 | -------------------------------------------------------------------------------- /.mise/tasks/lint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # mise description="Lint the workspace" 3 | set -euo pipefail 4 | 5 | swiftformat $MISE_PROJECT_ROOT --lint 6 | swiftlint lint --quiet --config $MISE_PROJECT_ROOT/.swiftlint.yml $MISE_PROJECT_ROOT/Sources 7 | -------------------------------------------------------------------------------- /.mise/tasks/lint-fix: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # mise description="Lint the workspace fixing issues" 3 | set -euo pipefail 4 | 5 | swiftformat $MISE_PROJECT_ROOT 6 | swiftlint lint --fix --quiet --config $MISE_PROJECT_ROOT/.swiftlint.yml $MISE_PROJECT_ROOT/Sources 7 | -------------------------------------------------------------------------------- /.mise/tasks/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # mise description="Run tests in the host" 3 | 4 | swift test --package-path $MISE_PROJECT_ROOT 5 | -------------------------------------------------------------------------------- /.mise/tasks/test-linux: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # mise description="Run tests on Linux" 3 | 4 | CONTAINER_RUNTIME=docker 5 | 6 | if [ -z "$CONTAINER_RUNTIME" ]; then 7 | echo "Neither podman nor docker is available. Please install one to proceed." 8 | exit 1 9 | fi 10 | 11 | $CONTAINER_RUNTIME run --rm --interactive --tty --volume "$(pwd):/package" --workdir "/package" swift:5.10.1 bash -c " 12 | git config --global user.email 'xcodeproj@tuist.io' && 13 | git config --global user.name 'xcodeproj' && 14 | git config --global init.defaultBranch main && 15 | swift test 16 | " 17 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [XcodeProj] 5 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 6.0 2 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | # file options 2 | 3 | --symlinks ignore 4 | --exclude Tests/XCTestManifests.swift 5 | 6 | # format options 7 | 8 | --disable wrapMultilineStatementBraces 9 | --disable braces 10 | --allman false 11 | --binarygrouping 4,8 12 | --commas always 13 | --comments indent 14 | --decimalgrouping 3,6 15 | --elseposition same-line 16 | --empty void 17 | --exponentcase lowercase 18 | --exponentgrouping disabled 19 | --fractiongrouping disabled 20 | --header ignore 21 | --hexgrouping 4,8 22 | --hexliteralcase uppercase 23 | --ifdef indent 24 | --indent 4 25 | --indentcase false 26 | --importgrouping testable-bottom 27 | --linebreaks lf 28 | --octalgrouping 4,8 29 | --operatorfunc spaced 30 | --patternlet hoist 31 | --ranges spaced 32 | --self remove 33 | --semicolons inline 34 | --stripunusedargs always 35 | --trimwhitespace always 36 | --wraparguments preserve 37 | --wrapcollections preserve 38 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - Sources 3 | excluded: 4 | - Assets 5 | - Fixtures 6 | - Tests 7 | - .build 8 | - Sources/XcodeProj/String+md5.swift 9 | disabled_rules: 10 | - trailing_whitespace 11 | - trailing_comma 12 | - nesting 13 | - cyclomatic_complexity 14 | - file_length 15 | - todo 16 | line_length: 17 | warning: 150 18 | error: 170 19 | identifier_name: 20 | min_length: 21 | error: 1 22 | warning: 1 23 | function_parameter_count: 24 | warning: 7 25 | error: 8 26 | -------------------------------------------------------------------------------- /Assets/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeProj/4488984883071307a9136251e7ccf06a41b6258d/Assets/diagram.png -------------------------------------------------------------------------------- /Documentation/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | In the following pages you'll find documentation that will help you understand Xcode projects and how to interact with them using xcodeproj. 4 | 5 | - [Getting started](getting-started.md): If you are the type of person that likes to get the hands a bit dirty before reading documentation, this is a good place to start from to get a sense of what the interface of the library looks like. 6 | - [Migration guides](migration-guides.md): If you are already using a version of xcodeproj and would like to migrate to the newest version, this document describes the necessary steps that you need to take to migrate between versions. 7 | 8 | 9 | -------------------------------------------------------------------------------- /Fixtures/FileSharedAcrossTargets/FileSharedAcrossTargets.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Fixtures/FileSharedAcrossTargets/FileSharedAcrossTargets.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Fixtures/FileSharedAcrossTargets/FileSharedAcrossTargets.xcodeproj/xcuserdata/pepicrft.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FileSharedAcrossTargets.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Fixtures/FileSharedAcrossTargets/FileSharedAcrossTargets/FileSharedAcrossTargets.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | //! Project version number for FileSharedAcrossTargets. 4 | FOUNDATION_EXPORT double FileSharedAcrossTargetsVersionNumber; 5 | 6 | //! Project version string for FileSharedAcrossTargets. 7 | FOUNDATION_EXPORT const unsigned char FileSharedAcrossTargetsVersionString[]; 8 | -------------------------------------------------------------------------------- /Fixtures/FileSharedAcrossTargets/FileSharedAcrossTargets/SharedHeader.h: -------------------------------------------------------------------------------- 1 | #ifndef SharedHeader_h 2 | #define SharedHeader_h 3 | 4 | 5 | #endif /* SharedHeader_h */ 6 | -------------------------------------------------------------------------------- /Fixtures/FileSharedAcrossTargets/FileSharedAcrossTargetsTests/FileSharedAcrossTargetsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import FileSharedAcrossTargets 3 | 4 | final class FileSharedAcrossTargetsTests: XCTestCase { 5 | override func setUpWithError() throws { 6 | // Put setup code here. This method is called before the invocation of each test method in the class. 7 | } 8 | 9 | override func tearDownWithError() throws { 10 | // Put teardown code here. This method is called after the invocation of each test method in the class. 11 | } 12 | 13 | func testExample() throws { 14 | // This is an example of a functional test case. 15 | // Use XCTAssert and related functions to verify your tests produce the correct results. 16 | // Any test you write for XCTest can be annotated as throws and async. 17 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. 18 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. 19 | } 20 | 21 | func testPerformanceExample() throws { 22 | // This is an example of a performance test case. 23 | measure { 24 | // Put the code you want to measure the time of here. 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Fixtures/Schemes/MinimalInformation.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Fixtures/Schemes/NoBlueprintID.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Fixtures/Schemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | App.xcscheme 8 | 9 | isShown 10 | 11 | orderHint 12 | 0 13 | 14 | Test 0.xcscheme 15 | 16 | orderHint 17 | 3 18 | 19 | Test 1.xcscheme 20 | 21 | isShown 22 | 23 | orderHint 24 | 4 25 | 26 | Tuist.xcscheme_^#shared#^_ 27 | 28 | isShown 29 | 30 | orderHint 31 | 1 32 | 33 | XcodeProj.xcscheme 34 | 35 | orderHint 36 | 2 37 | 38 | 39 | SuppressBuildableAutocreation 40 | 41 | E525238B16245A900012E2BA 42 | 43 | primary 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Fixtures/SynchronizedRootGroups/.gitignore: -------------------------------------------------------------------------------- 1 | *.xcodeproj/xcuserdata 2 | -------------------------------------------------------------------------------- /Fixtures/SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj/xcuserdata/pepicrft.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SynchronizedRootGroups.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Fixtures/SynchronizedRootGroups/SynchronizedRootGroups/Exception/Exception.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Exception.swift 3 | // SynchronizedRootGroups 4 | // 5 | // Created by Pedro Piñera Buendía on 26.07.24. 6 | // 7 | -------------------------------------------------------------------------------- /Fixtures/SynchronizedRootGroups/SynchronizedRootGroups/SynchronizedRootGroups.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SynchronizedRootGroups.swift 3 | // SynchronizedRootGroups 4 | // 5 | // Created by Pedro Piñera Buendía on 26.07.24. 6 | // 7 | -------------------------------------------------------------------------------- /Fixtures/TargetWithCustomBuildRules/TargetWithCustomBuildRules.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Fixtures/TargetWithCustomBuildRules/TargetWithCustomBuildRules.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Fixtures/TargetWithCustomBuildRules/TargetWithCustomBuildRules.xcodeproj/project.xcworkspace/xcuserdata/pepicrft.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeProj/4488984883071307a9136251e7ccf06a41b6258d/Fixtures/TargetWithCustomBuildRules/TargetWithCustomBuildRules.xcodeproj/project.xcworkspace/xcuserdata/pepicrft.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Fixtures/TargetWithCustomBuildRules/TargetWithCustomBuildRules.xcodeproj/xcuserdata/pepicrft.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TargetWithCustomBuildRules.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Fixtures/TargetWithCustomBuildRules/TargetWithCustomBuildRules/TargetWithCustomBuildRules.h: -------------------------------------------------------------------------------- 1 | // 2 | // TargetWithCustomBuildRules.h 3 | // TargetWithCustomBuildRules 4 | // 5 | // Created by Pedro Piñera Buendía on 06.07.23. 6 | // 7 | 8 | #import 9 | 10 | @interface TargetWithCustomBuildRules : NSObject 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Fixtures/TargetWithCustomBuildRules/TargetWithCustomBuildRules/TargetWithCustomBuildRules.m: -------------------------------------------------------------------------------- 1 | // 2 | // TargetWithCustomBuildRules.m 3 | // TargetWithCustomBuildRules 4 | // 5 | // Created by Pedro Piñera Buendía on 06.07.23. 6 | // 7 | 8 | #import "TargetWithCustomBuildRules.h" 9 | 10 | @implementation TargetWithCustomBuildRules 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Fixtures/WithoutWorkspace/.gitignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | -------------------------------------------------------------------------------- /Fixtures/WithoutWorkspace/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "WithoutWorkspace", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "WithoutWorkspace", 12 | targets: ["WithoutWorkspace"] 13 | ), 14 | ], 15 | dependencies: [ 16 | // Dependencies declare other packages that this package depends on. 17 | // .package(url: /* package url */, from: "1.0.0"), 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 21 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 22 | .target( 23 | name: "WithoutWorkspace", 24 | dependencies: [] 25 | ), 26 | .testTarget( 27 | name: "WithoutWorkspaceTests", 28 | dependencies: ["WithoutWorkspace"] 29 | ), 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /Fixtures/WithoutWorkspace/Sources/WithoutWorkspace/WithoutWorkspace.swift: -------------------------------------------------------------------------------- 1 | struct WithoutWorkspace { 2 | var text = "Hello, World!" 3 | } 4 | -------------------------------------------------------------------------------- /Fixtures/WithoutWorkspace/Tests/WithoutWorkspaceTests/WithoutWorkspaceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import WithoutWorkspace 3 | 4 | class WithoutWorkspaceTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssertEqual(WithoutWorkspace().text, "Hello, World!") 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Fixtures/WithoutWorkspace/WithoutWorkspace.xcodeproj/WithoutWorkspaceTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Fixtures/WithoutWorkspace/WithoutWorkspace.xcodeproj/WithoutWorkspace_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Fixtures/WithoutWorkspace/WithoutWorkspace.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SchemeUserState 5 | 6 | WithoutWorkspace-Package.xcscheme 7 | 8 | 9 | SuppressBuildableAutocreation 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Fixtures/Workspace.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 9 | 10 | 12 | 13 | 14 | 17 | 18 | 20 | 21 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Fixtures/Workspace.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Fixtures/WorkspaceSettings/Default.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Fixtures/WorkspaceSettings/OriginalAbsoluteDerivedData.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | DerivedDataCustomLocation 8 | /User/xcodeproj/DerivedData 9 | DerivedDataLocationStyle 10 | AbsolutePath 11 | 12 | 13 | -------------------------------------------------------------------------------- /Fixtures/WorkspaceSettings/OriginalBuildSystem.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /Fixtures/WorkspaceSettings/OriginalRelativeDerivedData.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | DerivedDataCustomLocation 8 | CustomizedDerivedData 9 | DerivedDataLocationStyle 10 | WorkspaceRelativePath 11 | 12 | 13 | -------------------------------------------------------------------------------- /Fixtures/XCConfigs/Children.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Parent.xcconfig" 2 | 3 | CONFIGURATION_BUILD_DIR = Test/ // NOTE: Test comment line to check several slashes 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) 5 | WARNING_CFLAGS = -Wall -Wno-direct-ivar-access -Wno-objc-missing-property-synthesis -Wno-readonly-iboutlet-property -Wno-switch-enum -Wno-padded 6 | -------------------------------------------------------------------------------- /Fixtures/XCConfigs/Parent.xcconfig: -------------------------------------------------------------------------------- 1 | // NOTE: Top level comment 2 | OTHER_SWIFT_FLAGS_XCODE_0821 = $(inherited) 3 | OTHER_SWIFT_FLAGS_XCODE_0830 = $(inherited) -enable-bridging-pch 4 | PRODUCT_NAME = $(TARGET_NAME) // NOTE: Test Comment 5 | SWIFT_OPTIMIZATION_LEVEL = -Onone// Edge-case when a comment has no space 6 | -------------------------------------------------------------------------------- /Fixtures/Xcode16/README.md: -------------------------------------------------------------------------------- 1 | # Xcode 16 project 2 | 3 | Xcode 16 introduced some changes in Xcode projects, like [this one](https://github.com/tuist/XcodeProj/issues/861), so this fixture tries to capture those changes to run tests against them. 4 | -------------------------------------------------------------------------------- /Fixtures/Xcode16/Test/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Fixtures/Xcode16/Test/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "idiom" : "universal", 16 | "platform" : "ios", 17 | "size" : "1024x1024" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "tinted" 24 | } 25 | ], 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "size" : "1024x1024" 29 | }, 30 | { 31 | "idiom" : "mac", 32 | "scale" : "1x", 33 | "size" : "16x16" 34 | }, 35 | { 36 | "idiom" : "mac", 37 | "scale" : "2x", 38 | "size" : "16x16" 39 | }, 40 | { 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "32x32" 44 | }, 45 | { 46 | "idiom" : "mac", 47 | "scale" : "2x", 48 | "size" : "32x32" 49 | }, 50 | { 51 | "idiom" : "mac", 52 | "scale" : "1x", 53 | "size" : "128x128" 54 | }, 55 | { 56 | "idiom" : "mac", 57 | "scale" : "2x", 58 | "size" : "128x128" 59 | }, 60 | { 61 | "idiom" : "mac", 62 | "scale" : "1x", 63 | "size" : "256x256" 64 | }, 65 | { 66 | "idiom" : "mac", 67 | "scale" : "2x", 68 | "size" : "256x256" 69 | }, 70 | { 71 | "idiom" : "mac", 72 | "scale" : "1x", 73 | "size" : "512x512" 74 | }, 75 | { 76 | "idiom" : "mac", 77 | "scale" : "2x", 78 | "size" : "512x512" 79 | } 80 | ], 81 | "info" : { 82 | "author" : "xcode", 83 | "version" : 1 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Fixtures/Xcode16/Test/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Fixtures/Xcode16/Test/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Test 4 | // 5 | // Created by F1248 on 30.09.24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | var body: some View { 12 | VStack { 13 | Image(systemName: "globe") 14 | .imageScale(.large) 15 | .foregroundStyle(.tint) 16 | Text("Hello, world!") 17 | } 18 | .padding() 19 | } 20 | } 21 | 22 | #Preview { 23 | ContentView() 24 | } 25 | -------------------------------------------------------------------------------- /Fixtures/Xcode16/Test/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Fixtures/Xcode16/Test/Test.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Fixtures/Xcode16/Test/TestApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestApp.swift 3 | // Test 4 | // 5 | // Created by F1248 on 30.09.24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct TestApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Fixtures/Xcode16ProjectReferenceOrder/Sources/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Framework1 2 | import Framework2 3 | import UIKit 4 | 5 | @main 6 | class AppDelegate: UIResponder, UIApplicationDelegate { 7 | var window: UIWindow? 8 | 9 | func applicationDidFinishLaunching(_: UIApplication) { 10 | print(hello()) 11 | } 12 | 13 | func hello() -> String { 14 | "AppDelegate.hello()" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Fixtures/dummy.framework/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeProj/4488984883071307a9136251e7ccf06a41b6258d/Fixtures/dummy.framework/.gitkeep -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/AppWithExtensions.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/AppWithExtensions.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/AppWithExtensions/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @main 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 6 | // Override point for customization after application launch. 7 | true 8 | } 9 | 10 | // MARK: UISceneSession Lifecycle 11 | 12 | func application(_: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options _: UIScene.ConnectionOptions) -> UISceneConfiguration { 13 | // Called when a new scene session is being created. 14 | // Use this method to select a configuration to create the new scene with. 15 | UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 16 | } 17 | 18 | func application(_: UIApplication, didDiscardSceneSessions _: Set) { 19 | // Called when the user discards a scene session. 20 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 21 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/AppWithExtensions/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/AppWithExtensions/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/AppWithExtensions/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/AppWithExtensions/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | var body: some View { 5 | Text("Hello, World!") 6 | } 7 | } 8 | 9 | struct ContentView_Previews: PreviewProvider { 10 | static var previews: some View { 11 | ContentView() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/AppWithExtensions/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/AppWithExtensions/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/MessagesExtension/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/MessagesExtension/Assets.xcassets/iMessage App Icon.stickersiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "29x29" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "29x29" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "60x45" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "60x45" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "scale" : "2x", 26 | "size" : "29x29" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "scale" : "2x", 31 | "size" : "67x50" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "scale" : "2x", 36 | "size" : "74x55" 37 | }, 38 | { 39 | "idiom" : "ios-marketing", 40 | "scale" : "1x", 41 | "size" : "1024x1024" 42 | }, 43 | { 44 | "idiom" : "universal", 45 | "platform" : "ios", 46 | "scale" : "2x", 47 | "size" : "27x20" 48 | }, 49 | { 50 | "idiom" : "universal", 51 | "platform" : "ios", 52 | "scale" : "3x", 53 | "size" : "27x20" 54 | }, 55 | { 56 | "idiom" : "universal", 57 | "platform" : "ios", 58 | "scale" : "2x", 59 | "size" : "32x24" 60 | }, 61 | { 62 | "idiom" : "universal", 63 | "platform" : "ios", 64 | "scale" : "3x", 65 | "size" : "32x24" 66 | }, 67 | { 68 | "idiom" : "ios-marketing", 69 | "platform" : "ios", 70 | "scale" : "1x", 71 | "size" : "1024x768" 72 | } 73 | ], 74 | "info" : { 75 | "author" : "xcode", 76 | "version" : 1 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/MessagesExtension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | MessagesExtension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSExtension 24 | 25 | NSExtensionMainStoryboard 26 | MainInterface 27 | NSExtensionPointIdentifier 28 | com.apple.message-payload-provider 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/TodayViewExtension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | TodayViewExtension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSExtension 24 | 25 | NSExtensionMainStoryboard 26 | MainInterface 27 | NSExtensionPointIdentifier 28 | com.apple.widget-extension 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/TodayViewExtension/TodayViewController.swift: -------------------------------------------------------------------------------- 1 | import NotificationCenter 2 | import UIKit 3 | 4 | class TodayViewController: UIViewController, NCWidgetProviding { 5 | override func viewDidLoad() { 6 | super.viewDidLoad() 7 | // Do any additional setup after loading the view. 8 | } 9 | 10 | func widgetPerformUpdate(completionHandler: @escaping (NCUpdateResult) -> Void) { 11 | // Perform any setup necessary in order to update the view. 12 | 13 | // If an error is encountered, use NCUpdateResult.Failed 14 | // If there's no update required, use NCUpdateResult.NoData 15 | // If there's an update, use NCUpdateResult.NewData 16 | 17 | completionHandler(NCUpdateResult.newData) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/Assets.xcassets/Complication.complicationset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "filename" : "Circular.imageset", 5 | "idiom" : "watch", 6 | "role" : "circular" 7 | }, 8 | { 9 | "filename" : "Extra Large.imageset", 10 | "idiom" : "watch", 11 | "role" : "extra-large" 12 | }, 13 | { 14 | "filename" : "Graphic Bezel.imageset", 15 | "idiom" : "watch", 16 | "role" : "graphic-bezel" 17 | }, 18 | { 19 | "filename" : "Graphic Circular.imageset", 20 | "idiom" : "watch", 21 | "role" : "graphic-circular" 22 | }, 23 | { 24 | "filename" : "Graphic Corner.imageset", 25 | "idiom" : "watch", 26 | "role" : "graphic-corner" 27 | }, 28 | { 29 | "filename" : "Graphic Large Rectangular.imageset", 30 | "idiom" : "watch", 31 | "role" : "graphic-large-rectangular" 32 | }, 33 | { 34 | "filename" : "Modular.imageset", 35 | "idiom" : "watch", 36 | "role" : "modular" 37 | }, 38 | { 39 | "filename" : "Utilitarian.imageset", 40 | "idiom" : "watch", 41 | "role" : "utilitarian" 42 | } 43 | ], 44 | "info" : { 45 | "author" : "xcode", 46 | "version" : 1 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | var body: some View { 5 | Text("Hello, World!") 6 | } 7 | } 8 | 9 | struct ContentView_Previews: PreviewProvider { 10 | static var previews: some View { 11 | ContentView() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/HostingController.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftUI 3 | import WatchKit 4 | 5 | class HostingController: WKHostingController { 6 | override var body: ContentView { 7 | ContentView() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | WatchApp Extension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSExtension 24 | 25 | NSExtensionAttributes 26 | 27 | WKAppBundleIdentifier 28 | io.tuist.xcodeproj.fixtures.AppWithExtensions.watchkitapp 29 | 30 | NSExtensionPointIdentifier 31 | com.apple.watchkit 32 | 33 | WKExtensionDelegateClassName 34 | $(PRODUCT_MODULE_NAME).ExtensionDelegate 35 | 36 | 37 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/NotificationController.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import UserNotifications 3 | import WatchKit 4 | 5 | class NotificationController: WKUserNotificationHostingController { 6 | override var body: NotificationView { 7 | NotificationView() 8 | } 9 | 10 | override func willActivate() { 11 | // This method is called when watch view controller is about to be visible to user 12 | super.willActivate() 13 | } 14 | 15 | override func didDeactivate() { 16 | // This method is called when watch view controller is no longer visible 17 | super.didDeactivate() 18 | } 19 | 20 | override func didReceive(_: UNNotification) { 21 | // This method is called when a notification needs to be presented. 22 | // Implement it if you use a dynamic notification interface. 23 | // Populate your dynamic notification interface as quickly as possible. 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/NotificationView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct NotificationView: View { 4 | var body: some View { 5 | Text("Hello, World!") 6 | } 7 | } 8 | 9 | struct NotificationView_Previews: PreviewProvider { 10 | static var previews: some View { 11 | NotificationView() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp Extension/PushNotificationPayload.apns: -------------------------------------------------------------------------------- 1 | { 2 | "aps": { 3 | "alert": { 4 | "body": "Test message", 5 | "title": "Optional title", 6 | "subtitle": "Optional subtitle" 7 | }, 8 | "category": "myCategory", 9 | "thread-id": "5280" 10 | }, 11 | 12 | "WatchKit Simulator Actions": [ 13 | { 14 | "title": "First Button", 15 | "identifier": "firstButtonAction" 16 | } 17 | ], 18 | 19 | "customKey": "Use this file to define a testing payload for your notifications. The aps dictionary specifies the category, alert text and title. The WatchKit Simulator Actions array can provide info for one or more action buttons in addition to the standard Dismiss button. Any other top level keys are custom payload. If you have multiple such JSON files in your project, you'll be able to select them when choosing to debug the notification interface of your Watch App." 20 | } 21 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "role" : "notificationCenter", 6 | "scale" : "2x", 7 | "size" : "24x24", 8 | "subtype" : "38mm" 9 | }, 10 | { 11 | "idiom" : "watch", 12 | "role" : "notificationCenter", 13 | "scale" : "2x", 14 | "size" : "27.5x27.5", 15 | "subtype" : "42mm" 16 | }, 17 | { 18 | "idiom" : "watch", 19 | "role" : "companionSettings", 20 | "scale" : "2x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "watch", 25 | "role" : "companionSettings", 26 | "scale" : "3x", 27 | "size" : "29x29" 28 | }, 29 | { 30 | "idiom" : "watch", 31 | "role" : "appLauncher", 32 | "scale" : "2x", 33 | "size" : "40x40", 34 | "subtype" : "38mm" 35 | }, 36 | { 37 | "idiom" : "watch", 38 | "role" : "appLauncher", 39 | "scale" : "2x", 40 | "size" : "44x44", 41 | "subtype" : "40mm" 42 | }, 43 | { 44 | "idiom" : "watch", 45 | "role" : "appLauncher", 46 | "scale" : "2x", 47 | "size" : "50x50", 48 | "subtype" : "44mm" 49 | }, 50 | { 51 | "idiom" : "watch", 52 | "role" : "quickLook", 53 | "scale" : "2x", 54 | "size" : "86x86", 55 | "subtype" : "38mm" 56 | }, 57 | { 58 | "idiom" : "watch", 59 | "role" : "quickLook", 60 | "scale" : "2x", 61 | "size" : "98x98", 62 | "subtype" : "42mm" 63 | }, 64 | { 65 | "idiom" : "watch", 66 | "role" : "quickLook", 67 | "scale" : "2x", 68 | "size" : "108x108", 69 | "subtype" : "44mm" 70 | }, 71 | { 72 | "idiom" : "watch-marketing", 73 | "scale" : "1x", 74 | "size" : "1024x1024" 75 | } 76 | ], 77 | "info" : { 78 | "author" : "xcode", 79 | "version" : 1 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp/Base.lproj/Interface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Fixtures/iOS/AppWithExtensions/WatchApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | AppWithExtensions 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | UISupportedInterfaceOrientations 24 | 25 | UIInterfaceOrientationPortrait 26 | UIInterfaceOrientationPortraitUpsideDown 27 | 28 | WKCompanionAppBundleIdentifier 29 | io.tuist.xcodeproj.fixtures.AppWithExtensions 30 | WKWatchKitApp 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Fixtures/iOS/MyLocalPackage/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /Fixtures/iOS/MyLocalPackage/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "MyLocalPackage", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "MyLocalPackage", 12 | targets: ["MyLocalPackage"] 13 | ), 14 | ], 15 | dependencies: [ 16 | // Dependencies declare other packages that this package depends on. 17 | // .package(url: /* package url */, from: "1.0.0"), 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 21 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 22 | .target( 23 | name: "MyLocalPackage", 24 | dependencies: [] 25 | ), 26 | .testTarget( 27 | name: "MyLocalPackageTests", 28 | dependencies: ["MyLocalPackage"] 29 | ), 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /Fixtures/iOS/MyLocalPackage/README.md: -------------------------------------------------------------------------------- 1 | # MyLocalPackage 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /Fixtures/iOS/MyLocalPackage/Sources/MyLocalPackage/MyLocalPackage.swift: -------------------------------------------------------------------------------- 1 | struct MyLocalPackage { 2 | var text = "Hello, World!" 3 | } 4 | -------------------------------------------------------------------------------- /Fixtures/iOS/MyLocalPackage/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import MyLocalPackageTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += MyLocalPackageTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Fixtures/iOS/MyLocalPackage/Tests/MyLocalPackageTests/MyLocalPackageTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import MyLocalPackage 3 | 4 | final class MyLocalPackageTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssertEqual(MyLocalPackage().text, "Hello, World!") 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Fixtures/iOS/MyLocalPackage/Tests/MyLocalPackageTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | [ 6 | testCase(MyLocalPackageTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Fixtures/iOS/MyOtherLocalPackage/MyOtherLocalPackage/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /Fixtures/iOS/MyOtherLocalPackage/MyOtherLocalPackage/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "MyOtherLocalPackage", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "MyOtherLocalPackage", 12 | targets: ["MyOtherLocalPackage"] 13 | ), 14 | ], 15 | dependencies: [ 16 | // Dependencies declare other packages that this package depends on. 17 | // .package(url: /* package url */, from: "1.0.0"), 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 21 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 22 | .target( 23 | name: "MyOtherLocalPackage", 24 | dependencies: [] 25 | ), 26 | .testTarget( 27 | name: "MyOtherLocalPackageTests", 28 | dependencies: ["MyOtherLocalPackage"] 29 | ), 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /Fixtures/iOS/MyOtherLocalPackage/MyOtherLocalPackage/README.md: -------------------------------------------------------------------------------- 1 | # MyLocalPackage 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /Fixtures/iOS/MyOtherLocalPackage/MyOtherLocalPackage/Sources/MyOtherLocalPackage/MyOtherLocalPackage.swift: -------------------------------------------------------------------------------- 1 | struct MyOtherLocalPackage { 2 | var text = "Hello, World!" 3 | } 4 | -------------------------------------------------------------------------------- /Fixtures/iOS/MyOtherLocalPackage/MyOtherLocalPackage/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import MyLocalPackageTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += MyLocalPackageTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Fixtures/iOS/MyOtherLocalPackage/MyOtherLocalPackage/Tests/MyOtherLocalPackageTests/MyLocalPackageTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import MyOtherLocalPackage 3 | 4 | final class MyLocalPackageTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssertEqual(MyOtherLocalPackage().text, "Hello, World!") 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Fixtures/iOS/MyOtherLocalPackage/MyOtherLocalPackage/Tests/MyOtherLocalPackageTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | [ 6 | testCase(MyLocalPackageTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Fixtures/iOS/Project.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Fixtures/iOS/Project.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Fixtures/iOS/Project.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "RxSwift", 6 | "repositoryURL": "https://github.com/ReactiveX/RxSwift", 7 | "state": { 8 | "branch": null, 9 | "revision": "b3e888b4972d9bc76495dd74d30a8c7fad4b9395", 10 | "version": "5.0.1" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Fixtures/iOS/Project.xcodeproj/project.xcworkspace/xcuserdata/pepicrft.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeProj/4488984883071307a9136251e7ccf06a41b6258d/Fixtures/iOS/Project.xcodeproj/project.xcworkspace/xcuserdata/pepicrft.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Fixtures/iOS/Project.xcodeproj/xcuserdata/username1.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Fixtures/iOS/Project.xcodeproj/xcuserdata/username1.xcuserdatad/xcschemes/iOS-debug.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Fixtures/iOS/Project.xcodeproj/xcuserdata/username1.xcuserdatad/xcschemes/iOS-other.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Fixtures/iOS/Project.xcodeproj/xcuserdata/username1.xcuserdatad/xcschemes/iOS-release.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Fixtures/iOS/Project.xcodeproj/xcuserdata/username1.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Rx (Playground) 1.xcscheme 8 | 9 | isShown 10 | 11 | orderHint 12 | 5 13 | 14 | Rx (Playground) 2.xcscheme 15 | 16 | isShown 17 | 18 | orderHint 19 | 6 20 | 21 | Rx (Playground).xcscheme 22 | 23 | isShown 24 | 25 | orderHint 26 | 4 27 | 28 | iOS-debug.xcscheme 29 | 30 | orderHint 31 | 0 32 | 33 | iOS-release.xcscheme 34 | 35 | orderHint 36 | 1 37 | 38 | iOS.xcscheme_^#shared#^_ 39 | 40 | orderHint 41 | 3 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Fixtures/iOS/Project.xcodeproj/xcuserdata/username2.xcuserdatad/xcschemes/iOSTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 10 | 11 | 16 | 17 | 20 | 26 | 27 | 28 | 29 | 30 | 40 | 41 | 47 | 48 | 50 | 51 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Fixtures/iOS/Project.xcodeproj/xcuserdata/username3.xcuserdatad/xcschemes/custom-scheme.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Fixtures/iOS/ProjectWithoutProductsGroup.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXGroup section */ 10 | E4C06A7B1FC1CA8500A9AB51 = { 11 | isa = PBXGroup; 12 | children = ( 13 | ); 14 | sourceTree = ""; 15 | }; 16 | /* End PBXGroup section */ 17 | 18 | /* Begin PBXProject section */ 19 | E4C06A7C1FC1CA8500A9AB51 /* Project object */ = { 20 | isa = PBXProject; 21 | attributes = { 22 | LastSwiftUpdateCheck = 0920; 23 | LastUpgradeCheck = 0920; 24 | ORGANIZATIONNAME = tuist; 25 | }; 26 | buildConfigurationList = E4C06A7F1FC1CA8500A9AB51 /* Build configuration list for PBXProject "ProjectWithoutProductsGroup" */; 27 | compatibilityVersion = "Xcode 8.0"; 28 | developmentRegion = en; 29 | hasScannedForEncodings = 0; 30 | mainGroup = E4C06A7B1FC1CA8500A9AB51; 31 | productRefGroup = E4C06A7B1FC1CA8500A9AB51; 32 | projectDirPath = ""; 33 | projectRoot = ""; 34 | targets = ( 35 | ); 36 | }; 37 | /* End PBXProject section */ 38 | 39 | /* Begin XCBuildConfiguration section */ 40 | E4C06A941FC1CA8500A9AB51 /* Debug */ = { 41 | isa = XCBuildConfiguration; 42 | buildSettings = { 43 | }; 44 | name = Debug; 45 | }; 46 | /* End XCBuildConfiguration section */ 47 | 48 | /* Begin XCConfigurationList section */ 49 | E4C06A7F1FC1CA8500A9AB51 /* Build configuration list for PBXProject "ProjectWithoutProductsGroup" */ = { 50 | isa = XCConfigurationList; 51 | buildConfigurations = ( 52 | E4C06A941FC1CA8500A9AB51 /* Debug */, 53 | ); 54 | defaultConfigurationIsVisible = 0; 55 | defaultConfigurationName = Debug; 56 | }; 57 | /* End XCConfigurationList section */ 58 | }; 59 | rootObject = E4C06A7C1FC1CA8500A9AB51 /* Project object */; 60 | } 61 | -------------------------------------------------------------------------------- /Fixtures/iOS/ProjectWithoutProductsGroup.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Fixtures/iOS/ProjectWithoutProductsGroup.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Fixtures/iOS/SameName/SameName.h: -------------------------------------------------------------------------------- 1 | // 2 | // SameName.h 3 | // SameName 4 | // 5 | // Created by Timothy Costa on 2022/06/10. 6 | // Copyright © 2022 es.ppinera. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SameName. 12 | FOUNDATION_EXPORT double SameNameVersionNumber; 13 | 14 | //! Project version string for SameName. 15 | FOUNDATION_EXPORT const unsigned char SameNameVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Fixtures/iOS/iOS.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "B98CEFD8-D0A3-4BDD-BA5B-8DC671AA5291", 5 | "name" : "Configuration 1", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | "codeCoverage" : { 13 | "targets" : [ 14 | 15 | ] 16 | }, 17 | "commandLineArgumentEntries" : [ 18 | { 19 | "argument" : "MyLaunchArgument" 20 | } 21 | ], 22 | "environmentVariableEntries" : [ 23 | { 24 | "key" : "ENV_VAR", 25 | "value" : "RUN" 26 | } 27 | ], 28 | "targetForVariableExpansion" : { 29 | "containerPath" : "container:Project.xcodeproj", 30 | "identifier" : "23766C111EAA3484007A9026", 31 | "name" : "iOS" 32 | } 33 | }, 34 | "testTargets" : [ 35 | { 36 | "target" : { 37 | "containerPath" : "container:Project.xcodeproj", 38 | "identifier" : "23766C251EAA3484007A9026", 39 | "name" : "iOSTests" 40 | } 41 | } 42 | ], 43 | "version" : 1 44 | } 45 | -------------------------------------------------------------------------------- /Fixtures/iOS/iOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // iOS 4 | // 5 | // Created by Pedro Pinera Buendia on 21.04.17. 6 | // Copyright © 2017 es.ppinera. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @main 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | true 18 | } 19 | 20 | func applicationWillResignActive(_: UIApplication) { 21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 22 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 23 | } 24 | 25 | func applicationDidEnterBackground(_: UIApplication) { 26 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 27 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 28 | } 29 | 30 | func applicationWillEnterForeground(_: UIApplication) { 31 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 32 | } 33 | 34 | func applicationDidBecomeActive(_: UIApplication) { 35 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 36 | } 37 | 38 | func applicationWillTerminate(_: UIApplication) { 39 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Fixtures/iOS/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Fixtures/iOS/iOS/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Fixtures/iOS/iOS/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Fixtures/iOS/iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Fixtures/iOS/iOS/Model.xcdatamodeld/Model.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Fixtures/iOS/iOS/Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // Private.h 3 | // Project 4 | // 5 | // Created by Pedro Piñera Buendía on 11.07.17. 6 | // Copyright © 2017 es.ppinera. All rights reserved. 7 | // 8 | 9 | #ifndef Private_h 10 | #define Private_h 11 | 12 | 13 | #endif /* Private_h */ 14 | -------------------------------------------------------------------------------- /Fixtures/iOS/iOS/Protected.h: -------------------------------------------------------------------------------- 1 | // 2 | // Protected.h 3 | // Project 4 | // 5 | // Created by Pedro Piñera Buendía on 11.07.17. 6 | // Copyright © 2017 es.ppinera. All rights reserved. 7 | // 8 | 9 | #ifndef Protected_h 10 | #define Protected_h 11 | 12 | 13 | #endif /* Protected_h */ 14 | -------------------------------------------------------------------------------- /Fixtures/iOS/iOS/Public.h: -------------------------------------------------------------------------------- 1 | // 2 | // Public.h 3 | // Project 4 | // 5 | // Created by Pedro Piñera Buendía on 11.07.17. 6 | // Copyright © 2017 es.ppinera. All rights reserved. 7 | // 8 | 9 | #ifndef Public_h 10 | #define Public_h 11 | 12 | 13 | #endif /* Public_h */ 14 | -------------------------------------------------------------------------------- /Fixtures/iOS/iOS/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // iOS 4 | // 5 | // Created by Pedro Pinera Buendia on 21.04.17. 6 | // Copyright © 2017 es.ppinera. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | // Do any additional setup after loading the view, typically from a nib. 15 | } 16 | 17 | override func didReceiveMemoryWarning() { 18 | super.didReceiveMemoryWarning() 19 | // Dispose of any resources that can be recreated. 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Fixtures/iOS/iOSTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Fixtures/iOS/iOSTests/iOSTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iOSTests.swift 3 | // iOSTests 4 | // 5 | // Created by Pedro Pinera Buendia on 21.04.17. 6 | // Copyright © 2017 es.ppinera. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import iOS 11 | 12 | class iOSTests: XCTestCase { 13 | override func setUp() { 14 | super.setUp() 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | super.tearDown() 21 | } 22 | 23 | func testExample() { 24 | // This is an example of a functional test case. 25 | // Use XCTAssert and related functions to verify your tests produce the correct results. 26 | } 27 | 28 | func testPerformanceExample() { 29 | // This is an example of a performance test case. 30 | measure { 31 | // Put the code you want to measure the time of here. 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) from 2018 Pedro Piñera Buendía 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "8a13a61314cb0e10f1d22dddaabfebd1ab407a7b9ab06aa42fc1e38041a202a5", 3 | "pins" : [ 4 | { 5 | "identity" : "aexml", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/tadija/AEXML.git", 8 | "state" : { 9 | "revision" : "db806756c989760b35108146381535aec231092b", 10 | "version" : "4.7.0" 11 | } 12 | }, 13 | { 14 | "identity" : "pathkit", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/kylef/PathKit.git", 17 | "state" : { 18 | "revision" : "3bfd2737b700b9a36565a8c94f4ad2b050a5e574", 19 | "version" : "1.0.1" 20 | } 21 | }, 22 | { 23 | "identity" : "spectre", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/kylef/Spectre.git", 26 | "state" : { 27 | "revision" : "26cc5e9ae0947092c7139ef7ba612e34646086c7", 28 | "version" : "0.10.1" 29 | } 30 | } 31 | ], 32 | "version" : 3 33 | } 34 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:6.0.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "XcodeProj", 7 | products: [ 8 | .library(name: "XcodeProj", targets: ["XcodeProj"]), 9 | ], 10 | dependencies: [ 11 | .package(url: "https://github.com/tadija/AEXML.git", .upToNextMinor(from: "4.7.0")), 12 | .package(url: "https://github.com/kylef/PathKit.git", .upToNextMinor(from: "1.0.1")), 13 | ], 14 | targets: [ 15 | .target(name: "XcodeProj", 16 | dependencies: [ 17 | .product(name: "PathKit", package: "PathKit"), 18 | .product(name: "AEXML", package: "AEXML"), 19 | ], 20 | swiftSettings: [ 21 | .enableExperimentalFeature("StrictConcurrency"), 22 | ]), 23 | .testTarget(name: "XcodeProjTests", dependencies: ["XcodeProj"]), 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /Scripts/dump-known-file-extensions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import json 4 | import os 5 | import plistlib 6 | import re 7 | import subprocess 8 | import sys 9 | 10 | 11 | def parse_xcspec(path): 12 | # convert ASCII plist to XML format and read as string 13 | xml_string = subprocess.check_output([ 14 | 'plutil', '-convert', 'xml1', '-o', '-', '--', path]) 15 | return plistlib.readPlistFromString(xml_string) 16 | 17 | 18 | def extract_extensions(path, into={}): 19 | data = parse_xcspec(path) 20 | extracted = 0 21 | 22 | if isinstance(data, list): 23 | for item in data: 24 | if 'Identifier' in item and 'Extensions' in item: 25 | ident = item['Identifier'] 26 | for ext in item['Extensions']: 27 | into[ext] = ident 28 | extracted += 1 29 | 30 | if extracted > 0: 31 | sys.stderr.write('** Extracted {} extensions from {}\n'.format( 32 | extracted, path)) 33 | 34 | return into 35 | 36 | 37 | def run(xcode_path): 38 | plugins_path = os.path.join(xcode_path, 'Contents', 'PlugIns') 39 | matcher = re.compile(r'\.(xcspec|pbfilespec)$') 40 | all_extensions = {} 41 | for root, dirs, files in os.walk(plugins_path): 42 | for file in files: 43 | if matcher.search(file): 44 | path = os.path.join(root, file) 45 | extract_extensions(path, all_extensions) 46 | 47 | json.dump( 48 | all_extensions, 49 | sys.stdout, 50 | sort_keys=True, 51 | indent=4, 52 | separators=(',', ': ') 53 | ) 54 | 55 | 56 | if __name__ == '__main__': 57 | if len(sys.argv) == 2: 58 | run(sys.argv[1]) 59 | else: 60 | sys.stderr.write('usage: {} /path/to/Xcode.app\n'.format( 61 | os.path.basename(sys.argv[0]))) 62 | exit(1) 63 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Extensions/Array+Extras.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Array where Element: Hashable { 4 | /// Return the array with all duplicates removed. 5 | /// 6 | /// i.e. `[ 1, 2, 3, 1, 2 ].uniqued() == [ 1, 2, 3 ]` 7 | /// 8 | /// - note: Taken from stackoverflow.com/a/46354989/3141234, as 9 | /// per @Alexander's comment. 10 | func uniqued() -> [Element] { 11 | var seen = Set() 12 | return filter { seen.insert($0).inserted } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Extensions/Bool+Extras.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Bool { 4 | /// Returns a XML string value that represents the boolean. 5 | var xmlString: String { 6 | self ? "YES" : "NO" 7 | } 8 | 9 | /// Returns a 1 for true and 0 for false 10 | var int: UInt { 11 | self ? 1 : 0 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Extensions/KeyedDecodingContainer+Additions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension KeyedDecodingContainer { 4 | func decode(_ key: KeyedDecodingContainer.Key) throws -> T where T: Decodable { 5 | try decode(T.self, forKey: key) 6 | } 7 | 8 | func decodeIfPresent(_ key: KeyedDecodingContainer.Key) throws -> T? where T: Decodable { 9 | try decodeIfPresent(T.self, forKey: key) 10 | } 11 | 12 | func decodeIntIfPresent(_ key: KeyedDecodingContainer.Key) throws -> UInt? { 13 | if let string: String = try? decodeIfPresent(key) { 14 | UInt(string) 15 | } else if let bool: Bool = try? decodeIfPresent(key) { 16 | bool ? 0 : 1 17 | } else if let int: UInt = try decodeIfPresent(key) { 18 | // don't `try?` here in case key _does_ exist but isn't an expected type 19 | // ie. not a string/bool/int 20 | int 21 | } else { 22 | nil 23 | } 24 | } 25 | 26 | func decodeIntBool(_ key: KeyedDecodingContainer.Key) throws -> Bool { 27 | guard let bool = try decodeIntBoolIfPresent(key) else { 28 | return false 29 | } 30 | return bool 31 | } 32 | 33 | func decodeIntBoolIfPresent(_ key: KeyedDecodingContainer.Key) throws -> Bool? { 34 | guard let int = try decodeIntIfPresent(key) else { 35 | return nil 36 | } 37 | 38 | guard int <= 2 else { 39 | throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Expected to decode Bool but found a number that is not 0 or 1") 40 | } 41 | 42 | return int == 1 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Extensions/NSRecursiveLock+Sync.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension NSRecursiveLock { 4 | func whileLocked(closure: () -> T) -> T { 5 | lock() 6 | defer { 7 | unlock() 8 | } 9 | let value = closure() 10 | return value 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Extensions/String+Utils.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension String { 4 | var quoted: String { 5 | "\"\(self)\"" 6 | } 7 | 8 | var isQuoted: Bool { 9 | hasPrefix("\"") && hasSuffix("\"") 10 | } 11 | 12 | static func random(length: Int = 20) -> String { 13 | let base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 14 | var randomString = "" 15 | 16 | for _ in 0 ..< length { 17 | let randomValue = Int.random(in: 0 ..< base.count) 18 | randomString += "\(base[base.index(base.startIndex, offsetBy: randomValue)])" 19 | } 20 | return randomString 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift: -------------------------------------------------------------------------------- 1 | public enum BuildFileSetting: Sendable, Equatable, Hashable { 2 | case string(String) 3 | case array([String]) 4 | 5 | public var stringValue: String? { 6 | if case let .string(value) = self { 7 | value 8 | } else { 9 | nil 10 | } 11 | } 12 | 13 | public var arrayValue: [String]? { 14 | if case let .array(value) = self { 15 | value 16 | } else { 17 | nil 18 | } 19 | } 20 | } 21 | 22 | extension BuildFileSetting: Codable { 23 | public init(from decoder: Decoder) throws { 24 | let container = try decoder.singleValueContainer() 25 | do { 26 | let string = try container.decode(String.self) 27 | self = .string(string) 28 | } catch { 29 | let array = try container.decode([String].self) 30 | self = .array(array) 31 | } 32 | } 33 | 34 | public func encode(to encoder: Encoder) throws { 35 | var container = encoder.singleValueContainer() 36 | switch self { 37 | case let .string(string): 38 | try container.encode(string) 39 | case let .array(array): 40 | try container.encode(array) 41 | } 42 | } 43 | } 44 | 45 | extension BuildFileSetting: ExpressibleByArrayLiteral { 46 | public init(arrayLiteral elements: String...) { 47 | self = .array(elements) 48 | } 49 | } 50 | 51 | extension BuildFileSetting: ExpressibleByStringInterpolation { 52 | public init(stringLiteral value: StringLiteralType) { 53 | self = .string(value) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Objects/BuildPhase/BuildPhase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Enum that encapsulates all kind of build phases available in Xcode. 4 | /// 5 | /// - sources: sources. 6 | /// - frameworks: frameworks. 7 | /// - resources: resources. 8 | /// - copyFiles: files. 9 | /// - runScript: scripts. 10 | /// - headers: headers. 11 | /// - carbonResources: build legacy Carbon resources. 12 | public enum BuildPhase: String { 13 | case sources = "Sources" 14 | case frameworks = "Frameworks" 15 | case resources = "Resources" 16 | case copyFiles = "CopyFiles" 17 | case runScript = "Run Script" 18 | case headers = "Headers" 19 | case carbonResources = "Rez" 20 | } 21 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Objects/BuildPhase/PBXFrameworksBuildPhase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This is the element for the framework link build phase. 4 | public final class PBXFrameworksBuildPhase: PBXBuildPhase { 5 | override public var buildPhase: BuildPhase { 6 | .frameworks 7 | } 8 | 9 | override func isEqual(to object: Any?) -> Bool { 10 | guard let rhs = object as? PBXFrameworksBuildPhase else { return false } 11 | return isEqual(to: rhs) 12 | } 13 | } 14 | 15 | // MARK: - PBXFrameworksBuildPhase Extension (PlistSerializable) 16 | 17 | extension PBXFrameworksBuildPhase: PlistSerializable { 18 | func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { 19 | var dictionary: [CommentedString: PlistValue] = try plistValues(proj: proj, reference: reference) 20 | dictionary["isa"] = .string(CommentedString(PBXFrameworksBuildPhase.isa)) 21 | return (key: CommentedString(reference, comment: "Frameworks"), value: .dictionary(dictionary)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Objects/BuildPhase/PBXHeadersBuildPhase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PathKit 3 | 4 | /// This is the element for the framework headers build phase. 5 | public final class PBXHeadersBuildPhase: PBXBuildPhase { 6 | override public var buildPhase: BuildPhase { 7 | .headers 8 | } 9 | 10 | override func isEqual(to object: Any?) -> Bool { 11 | guard let rhs = object as? PBXHeadersBuildPhase else { return false } 12 | return isEqual(to: rhs) 13 | } 14 | } 15 | 16 | // MARK: - PBXHeadersBuildPhase Extension (PlistSerializable) 17 | 18 | extension PBXHeadersBuildPhase: PlistSerializable { 19 | func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { 20 | var dictionary: [CommentedString: PlistValue] = try plistValues(proj: proj, reference: reference) 21 | dictionary["isa"] = .string(CommentedString(PBXHeadersBuildPhase.isa)) 22 | return (key: CommentedString(reference, comment: "Headers"), value: .dictionary(dictionary)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Objects/BuildPhase/PBXResourcesBuildPhase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This is the element for the resources copy build phase. 4 | public final class PBXResourcesBuildPhase: PBXBuildPhase { 5 | override public var buildPhase: BuildPhase { 6 | .resources 7 | } 8 | 9 | override func isEqual(to object: Any?) -> Bool { 10 | guard let rhs = object as? PBXResourcesBuildPhase else { return false } 11 | return isEqual(to: rhs) 12 | } 13 | } 14 | 15 | // MARK: - PBXResourcesBuildPhase Extension (PlistSerializable) 16 | 17 | extension PBXResourcesBuildPhase: PlistSerializable { 18 | func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { 19 | var dictionary: [CommentedString: PlistValue] = try plistValues(proj: proj, reference: reference) 20 | dictionary["isa"] = .string(CommentedString(PBXResourcesBuildPhase.isa)) 21 | return (key: CommentedString(reference, comment: "Resources"), value: .dictionary(dictionary)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Objects/BuildPhase/PBXRezBuildPhase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This is the element for the Build Carbon Resources build phase. 4 | /// These are legacy .r files from the Classic Mac OS era. 5 | public final class PBXRezBuildPhase: PBXBuildPhase { 6 | override public var buildPhase: BuildPhase { 7 | .carbonResources 8 | } 9 | 10 | override func isEqual(to object: Any?) -> Bool { 11 | guard let rhs = object as? PBXRezBuildPhase else { return false } 12 | return isEqual(to: rhs) 13 | } 14 | } 15 | 16 | // MARK: - PBXRezBuildPhase Extension (PlistSerializable) 17 | 18 | extension PBXRezBuildPhase: PlistSerializable { 19 | func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { 20 | var dictionary: [CommentedString: PlistValue] = try plistValues(proj: proj, reference: reference) 21 | dictionary["isa"] = .string(CommentedString(PBXRezBuildPhase.isa)) 22 | return (key: CommentedString(reference, comment: "Rez"), value: .dictionary(dictionary)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Objects/BuildPhase/PBXSourcesBuildPhase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This is the element for the sources compilation build phase. 4 | public final class PBXSourcesBuildPhase: PBXBuildPhase { 5 | override public var buildPhase: BuildPhase { 6 | .sources 7 | } 8 | 9 | override func isEqual(to object: Any?) -> Bool { 10 | guard let rhs = object as? PBXSourcesBuildPhase else { return false } 11 | return isEqual(to: rhs) 12 | } 13 | } 14 | 15 | extension PBXSourcesBuildPhase: PlistSerializable { 16 | // MARK: - PlistSerializable 17 | 18 | func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { 19 | var dictionary: [CommentedString: PlistValue] = try plistValues(proj: proj, reference: reference) 20 | dictionary["isa"] = .string(CommentedString(PBXSourcesBuildPhase.isa)) 21 | return (key: CommentedString(reference, comment: "Sources"), value: .dictionary(dictionary)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Objects/Configuration/BuildSettings.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Build settings. 4 | public typealias BuildSettings = [String: BuildSetting] 5 | 6 | private let yes = "YES" 7 | private let no = "NO" 8 | 9 | public enum BuildSetting: Sendable, Equatable { 10 | case string(String) 11 | case array([String]) 12 | 13 | public var stringValue: String? { 14 | if case let .string(value) = self { 15 | value 16 | } else { 17 | nil 18 | } 19 | } 20 | 21 | public var boolValue: Bool? { 22 | if case let .string(value) = self { 23 | switch value { 24 | case yes: true 25 | case no: false 26 | default: nil 27 | } 28 | } else { 29 | nil 30 | } 31 | } 32 | 33 | public var arrayValue: [String]? { 34 | if case let .array(value) = self { 35 | value 36 | } else { 37 | nil 38 | } 39 | } 40 | } 41 | 42 | extension BuildSetting: CustomStringConvertible { 43 | public var description: String { 44 | switch self { 45 | case let .string(string): 46 | string 47 | case let .array(array): 48 | array.joined(separator: " ") 49 | } 50 | } 51 | } 52 | 53 | extension BuildSetting: Decodable { 54 | public init(from decoder: Decoder) throws { 55 | let container = try decoder.singleValueContainer() 56 | do { 57 | let string = try container.decode(String.self) 58 | self = .string(string) 59 | } catch { 60 | let array = try container.decode([String].self) 61 | self = .array(array) 62 | } 63 | } 64 | } 65 | 66 | extension BuildSetting: ExpressibleByArrayLiteral { 67 | public init(arrayLiteral elements: String...) { 68 | self = .array(elements) 69 | } 70 | } 71 | 72 | extension BuildSetting: ExpressibleByStringInterpolation { 73 | public init(stringLiteral value: StringLiteralType) { 74 | self = .string(value) 75 | } 76 | } 77 | 78 | extension BuildSetting: ExpressibleByBooleanLiteral { 79 | public init(booleanLiteral value: Bool) { 80 | self = .string(value ? yes : no) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Objects/Files/PBXContainerItem.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Class representing an element that may contain other elements. 4 | public class PBXContainerItem: PBXObject { 5 | /// User comments for the object. 6 | var comments: String? 7 | 8 | // MARK: - Init 9 | 10 | init(comments: String? = nil) { 11 | self.comments = comments 12 | super.init() 13 | } 14 | 15 | // MARK: - Decodable 16 | 17 | fileprivate enum CodingKeys: String, CodingKey { 18 | case comments 19 | } 20 | 21 | public required init(from decoder: Decoder) throws { 22 | let container = try decoder.container(keyedBy: CodingKeys.self) 23 | comments = try container.decodeIfPresent(.comments) 24 | try super.init(from: decoder) 25 | } 26 | 27 | func plistValues(proj _: PBXProj, reference _: String) throws -> [CommentedString: PlistValue] { 28 | var dictionary = [CommentedString: PlistValue]() 29 | if let comments { 30 | dictionary["comments"] = .string(CommentedString(comments)) 31 | } 32 | return dictionary 33 | } 34 | 35 | override func isEqual(to object: Any?) -> Bool { 36 | guard let rhs = object as? PBXContainerItem else { return false } 37 | return isEqual(to: rhs) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedExceptionSet.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Common class for exception sets, such as `PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet` and `PBXFileSystemSynchronizedBuildFileExceptionSet` 4 | public class PBXFileSystemSynchronizedExceptionSet: PBXObject {} 5 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Objects/Files/PBXVariantGroup.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // This is the element for referencing localized resources. 4 | public final class PBXVariantGroup: PBXGroup { 5 | override func isEqual(to object: Any?) -> Bool { 6 | guard let rhs = object as? PBXVariantGroup else { return false } 7 | return isEqual(to: rhs) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Objects/Project/ProjectAttribute.swift: -------------------------------------------------------------------------------- 1 | public enum ProjectAttribute: Equatable { 2 | case string(String) 3 | case array([String]) 4 | case targetReference(PBXObject) 5 | case attributeDictionary([String: [String: ProjectAttribute]]) 6 | 7 | public var stringValue: String? { 8 | if case let .string(value) = self { 9 | value 10 | } else { 11 | nil 12 | } 13 | } 14 | 15 | public var arrayValue: [String]? { 16 | if case let .array(value) = self { 17 | value 18 | } else { 19 | nil 20 | } 21 | } 22 | } 23 | 24 | extension ProjectAttribute: Decodable { 25 | public init(from decoder: Decoder) throws { 26 | let container = try decoder.singleValueContainer() 27 | 28 | if let string = try? container.decode(String.self) { 29 | self = .string(string) 30 | } else if let array = try? container.decode([String].self) { 31 | self = .array(array) 32 | } else { 33 | let targetAttributes = try container.decode([String: [String: ProjectAttribute]].self) 34 | self = .attributeDictionary(targetAttributes) 35 | } 36 | } 37 | } 38 | 39 | extension ProjectAttribute: ExpressibleByArrayLiteral { 40 | public init(arrayLiteral elements: String...) { 41 | self = .array(elements) 42 | } 43 | } 44 | 45 | extension ProjectAttribute: ExpressibleByStringInterpolation { 46 | public init(stringLiteral value: StringLiteralType) { 47 | self = .string(value) 48 | } 49 | } 50 | 51 | extension ProjectAttribute: ExpressibleByDictionaryLiteral { 52 | public init(dictionaryLiteral elements: (String, [String: ProjectAttribute])...) { 53 | self = .attributeDictionary(Dictionary(uniqueKeysWithValues: elements)) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Objects/Sourcery/Sourcery.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | protocol AutoEquatable {} 4 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Objects/SwiftPackage/XCLocalSwiftPackageReference.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This element is an abstract parent for specialized targets. 4 | public class XCLocalSwiftPackageReference: PBXContainerItem, PlistSerializable { 5 | /// Repository url. 6 | public var relativePath: String 7 | 8 | /// Initializes the local swift package reference with its attributes. 9 | /// 10 | /// - Parameters: 11 | /// - repositoryPath: Package repository path. 12 | public init(relativePath: String) { 13 | self.relativePath = relativePath 14 | super.init() 15 | } 16 | 17 | enum CodingKeys: String, CodingKey { 18 | case relativePath 19 | } 20 | 21 | public required init(from decoder: Decoder) throws { 22 | let container = try decoder.container(keyedBy: CodingKeys.self) 23 | 24 | relativePath = try container.decode(String.self, forKey: .relativePath) 25 | 26 | try super.init(from: decoder) 27 | } 28 | 29 | /// It returns the name of the package reference. 30 | public var name: String? { 31 | relativePath 32 | } 33 | 34 | func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { 35 | var dictionary = try super.plistValues(proj: proj, reference: reference) 36 | dictionary["isa"] = .string(CommentedString(XCLocalSwiftPackageReference.isa)) 37 | dictionary["relativePath"] = .string(.init(relativePath)) 38 | return (key: CommentedString(reference, comment: "XCLocalSwiftPackageReference \"\(name ?? "")\""), 39 | value: .dictionary(dictionary)) 40 | } 41 | 42 | override func isEqual(to object: Any?) -> Bool { 43 | guard let rhs = object as? XCLocalSwiftPackageReference else { return false } 44 | return isEqual(to: rhs) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Objects/Targets/PBXAggregateTarget.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This is the element for a build target that aggregates several others. 4 | public final class PBXAggregateTarget: PBXTarget { 5 | override func isEqual(to object: Any?) -> Bool { 6 | guard let rhs = object as? PBXAggregateTarget else { return false } 7 | return isEqual(to: rhs) 8 | } 9 | } 10 | 11 | // MARK: - PBXAggregateTarget Extension (PlistSerializable) 12 | 13 | extension PBXAggregateTarget: PlistSerializable { 14 | func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { 15 | try plistValues(proj: proj, isa: PBXAggregateTarget.isa, reference: reference) 16 | } 17 | } 18 | 19 | // MARK: - Helpers 20 | 21 | public extension PBXAggregateTarget { 22 | /// Adds a local target dependency to the target. 23 | /// 24 | /// - Parameter target: dependency target. 25 | /// - Returns: target dependency reference. 26 | /// - Throws: an error if the dependency cannot be created. 27 | func addDependency(target: PBXTarget) throws -> PBXTargetDependency? { 28 | let objects = try target.objects() 29 | guard let project = objects.projects.first?.value else { 30 | return nil 31 | } 32 | let proxy = PBXContainerItemProxy(containerPortal: .project(project), 33 | remoteGlobalID: .object(target), 34 | proxyType: .nativeTarget, 35 | remoteInfo: target.name) 36 | objects.add(object: proxy) 37 | let targetDependency = PBXTargetDependency(name: target.name, 38 | target: target, 39 | targetProxy: proxy) 40 | objects.add(object: targetDependency) 41 | dependencies.append(targetDependency) 42 | return targetDependency 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Project/XCDebugger.swift: -------------------------------------------------------------------------------- 1 | import AEXML 2 | import Foundation 3 | import PathKit 4 | 5 | enum XCDebugger { 6 | /// Returns debugger folder path relative to the given path. 7 | /// 8 | /// - Parameter path: parent folder of debugger folder (xcshareddata or xcuserdata) 9 | /// - Returns: debugger folder path relative to the given path. 10 | public static func path(_ path: Path) -> Path { 11 | path + "xcdebugger" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Protocols/Writable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PathKit 3 | 4 | /// Protocol that defines how an entity can be written to disk 5 | public protocol Writable { 6 | /// Writes the object that conforms the protocol. 7 | /// 8 | /// - Parameter path: The path to write to 9 | /// - Parameter override: True if the content should be overridden if it already exists. 10 | /// - Throws: writing error if something goes wrong. 11 | func write(path: Path, override: Bool) throws 12 | 13 | /// Writes the object that conforms the protocol. 14 | /// 15 | /// - Parameter pathString: The path string to write to 16 | /// - Parameter override: True if the content should be overridden if it already exists. 17 | /// - Throws: writing error if something goes wrong. 18 | func write(pathString: String, override: Bool) throws 19 | 20 | /// Gets the data representation of the object that conforms to the protocol. 21 | /// 22 | /// - Throws: error if encoding to Data fails. 23 | func dataRepresentation() throws -> Data? 24 | } 25 | 26 | public extension Writable { 27 | func write(pathString: String, override: Bool) throws { 28 | let path = Path(pathString) 29 | try write(path: path, override: override) 30 | } 31 | 32 | func dataRepresentation() throws -> Data? { nil } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Scheme/XCScheme+AditionalOption.swift: -------------------------------------------------------------------------------- 1 | import AEXML 2 | import Foundation 3 | import PathKit 4 | 5 | public extension XCScheme { 6 | final class AdditionalOption: Equatable { 7 | // MARK: - Attributes 8 | 9 | public var key: String 10 | public var value: String 11 | public var isEnabled: Bool 12 | 13 | // MARK: - Init 14 | 15 | public init(key: String, value: String, isEnabled: Bool) { 16 | self.key = key 17 | self.value = value 18 | self.isEnabled = isEnabled 19 | } 20 | 21 | init(element: AEXMLElement) throws { 22 | key = element.attributes["key"]! 23 | value = element.attributes["value"]! 24 | isEnabled = element.attributes["isEnabled"] == "YES" 25 | } 26 | 27 | // MARK: - XML 28 | 29 | func xmlElement() -> AEXMLElement { 30 | AEXMLElement(name: "AdditionalOption", 31 | value: nil, 32 | attributes: [ 33 | "key": key, 34 | "value": value, 35 | "isEnabled": isEnabled.xmlString, 36 | ]) 37 | } 38 | 39 | // MARK: - Equatable 40 | 41 | public static func == (lhs: AdditionalOption, rhs: AdditionalOption) -> Bool { 42 | lhs.key == rhs.key && 43 | lhs.value == rhs.value && 44 | lhs.isEnabled == rhs.isEnabled 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Scheme/XCScheme+AnalyzeAction.swift: -------------------------------------------------------------------------------- 1 | import AEXML 2 | import Foundation 3 | import PathKit 4 | 5 | public extension XCScheme { 6 | final class AnalyzeAction: Equatable { 7 | // MARK: - Static 8 | 9 | // Xcode disables PreActions and PostActions for Analyze actions, so this Action 10 | // does not exetend SerialAction. 11 | private static let defaultBuildConfiguration = "Debug" 12 | 13 | // MARK: - Attributes 14 | 15 | public var buildConfiguration: String 16 | 17 | // MARK: - Init 18 | 19 | public init(buildConfiguration: String) { 20 | self.buildConfiguration = buildConfiguration 21 | } 22 | 23 | init(element: AEXMLElement) throws { 24 | buildConfiguration = element.attributes["buildConfiguration"] ?? AnalyzeAction.defaultBuildConfiguration 25 | } 26 | 27 | // MARK: - XML 28 | 29 | func xmlElement() -> AEXMLElement { 30 | var attributes: [String: String] = [:] 31 | attributes["buildConfiguration"] = buildConfiguration 32 | return AEXMLElement(name: "AnalyzeAction", value: nil, attributes: attributes) 33 | } 34 | 35 | // MARK: - Equatable 36 | 37 | public static func == (lhs: AnalyzeAction, rhs: AnalyzeAction) -> Bool { 38 | lhs.buildConfiguration == rhs.buildConfiguration 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Scheme/XCScheme+BuildableProductRunnable.swift: -------------------------------------------------------------------------------- 1 | import AEXML 2 | import Foundation 3 | 4 | public extension XCScheme { 5 | final class BuildableProductRunnable: Runnable { 6 | // MARK: - XML 7 | 8 | override func xmlElement() -> AEXMLElement { 9 | let element = super.xmlElement() 10 | element.name = "BuildableProductRunnable" 11 | return element 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Scheme/XCScheme+EnvironmentVariable.swift: -------------------------------------------------------------------------------- 1 | import AEXML 2 | import Foundation 3 | 4 | public extension XCScheme { 5 | struct EnvironmentVariable: Equatable { 6 | // MARK: - Attributes 7 | 8 | public let variable: String 9 | public let value: String 10 | public let enabled: Bool 11 | 12 | // MARK: - Init 13 | 14 | public init(variable: String, value: String, enabled: Bool) { 15 | self.variable = variable 16 | self.value = value 17 | self.enabled = enabled 18 | } 19 | 20 | // MARK: - XML 21 | 22 | func xmlElement() -> AEXMLElement { 23 | AEXMLElement(name: "EnvironmentVariable", 24 | value: nil, 25 | attributes: ["key": variable, "value": value, "isEnabled": enabled ? "YES" : "NO"]) 26 | } 27 | 28 | static func parseVariables(from element: AEXMLElement) throws -> [EnvironmentVariable] { 29 | try element.children.map { elt in 30 | guard let variableKey = elt.attributes["key"] else { 31 | throw XCSchemeError.missing(property: "key") 32 | } 33 | guard let variableValue = elt.attributes["value"] else { 34 | throw XCSchemeError.missing(property: "value") 35 | } 36 | guard let variableEnabledRaw = elt.attributes["isEnabled"] else { 37 | throw XCSchemeError.missing(property: "isEnabled") 38 | } 39 | 40 | return EnvironmentVariable(variable: variableKey, value: variableValue, enabled: variableEnabledRaw == "YES") 41 | } 42 | } 43 | 44 | static func xmlElement(from variables: [EnvironmentVariable]) -> AEXMLElement { 45 | let element = AEXMLElement(name: "EnvironmentVariables", 46 | value: nil) 47 | for arg in variables { 48 | element.addChild(arg.xmlElement()) 49 | } 50 | 51 | return element 52 | } 53 | 54 | // MARK: - Equatable 55 | 56 | public static func == (lhs: EnvironmentVariable, rhs: EnvironmentVariable) -> Bool { 57 | lhs.variable == rhs.variable && 58 | lhs.value == rhs.value && 59 | lhs.enabled == rhs.enabled 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Scheme/XCScheme+LocationScenarioReference.swift: -------------------------------------------------------------------------------- 1 | import AEXML 2 | import Foundation 3 | 4 | public extension XCScheme { 5 | final class LocationScenarioReference: Equatable { 6 | // MARK: - Attributes 7 | 8 | public var identifier: String 9 | public var referenceType: String 10 | 11 | // MARK: - Init 12 | 13 | public init(identifier: String, referenceType: String) { 14 | self.identifier = identifier 15 | self.referenceType = referenceType 16 | } 17 | 18 | init(element: AEXMLElement) throws { 19 | identifier = element.attributes["identifier"]! 20 | referenceType = element.attributes["referenceType"]! 21 | } 22 | 23 | // MARK: - XML 24 | 25 | func xmlElement() -> AEXMLElement { 26 | AEXMLElement(name: "LocationScenarioReference", 27 | value: nil, 28 | attributes: [ 29 | "identifier": identifier, 30 | "referenceType": referenceType, 31 | ]) 32 | } 33 | 34 | // MARK: - Equatable 35 | 36 | public static func == (lhs: LocationScenarioReference, rhs: LocationScenarioReference) -> Bool { 37 | lhs.identifier == rhs.identifier && 38 | lhs.referenceType == rhs.referenceType 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Scheme/XCScheme+PathRunnable.swift: -------------------------------------------------------------------------------- 1 | import AEXML 2 | import Foundation 3 | import PathKit 4 | 5 | public extension XCScheme { 6 | class PathRunnable: Runnable { 7 | // MARK: - Attributes 8 | 9 | public var filePath: String 10 | 11 | // MARK: - Init 12 | 13 | public init(filePath: String, 14 | runnableDebuggingMode: String = "0") { 15 | self.filePath = filePath 16 | super.init(buildableReference: nil, 17 | runnableDebuggingMode: runnableDebuggingMode) 18 | } 19 | 20 | override init(element: AEXMLElement) throws { 21 | filePath = element.attributes["FilePath"] ?? "" 22 | try super.init(element: element) 23 | } 24 | 25 | // MARK: - XML 26 | 27 | override func xmlElement() -> AEXMLElement { 28 | let element = super.xmlElement() 29 | element.name = "PathRunnable" 30 | element.attributes["FilePath"] = filePath 31 | return element 32 | } 33 | 34 | // MARK: - Equatable 35 | 36 | override func isEqual(other: XCScheme.Runnable) -> Bool { 37 | guard let other = other as? PathRunnable else { 38 | return false 39 | } 40 | 41 | return super.isEqual(other: other) && 42 | filePath == other.filePath 43 | } 44 | 45 | public static func == (lhs: PathRunnable, rhs: PathRunnable) -> Bool { 46 | lhs.isEqual(other: rhs) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Scheme/XCScheme+RemoteRunnable.swift: -------------------------------------------------------------------------------- 1 | import AEXML 2 | import Foundation 3 | 4 | public extension XCScheme { 5 | final class RemoteRunnable: Runnable { 6 | // MARK: - Attributes 7 | 8 | public var bundleIdentifier: String 9 | public var remotePath: String? 10 | 11 | // MARK: - Init 12 | 13 | public init(buildableReference: BuildableReference, 14 | bundleIdentifier: String, 15 | runnableDebuggingMode: String = "0", 16 | remotePath: String? = nil) { 17 | self.bundleIdentifier = bundleIdentifier 18 | self.remotePath = remotePath 19 | super.init(buildableReference: buildableReference, 20 | runnableDebuggingMode: runnableDebuggingMode) 21 | } 22 | 23 | override init(element: AEXMLElement) throws { 24 | bundleIdentifier = element.attributes["BundleIdentifier"] ?? "" 25 | remotePath = element.attributes["RemotePath"] 26 | try super.init(element: element) 27 | } 28 | 29 | // MARK: - XML 30 | 31 | override func xmlElement() -> AEXMLElement { 32 | let element = super.xmlElement() 33 | element.name = "RemoteRunnable" 34 | element.attributes["BundleIdentifier"] = bundleIdentifier 35 | element.attributes["RemotePath"] = remotePath 36 | return element 37 | } 38 | 39 | // MARK: - Equatable 40 | 41 | override func isEqual(other: XCScheme.Runnable) -> Bool { 42 | guard let other = other as? RemoteRunnable else { 43 | return false 44 | } 45 | 46 | return super.isEqual(other: other) && 47 | bundleIdentifier == other.bundleIdentifier && 48 | remotePath == other.remotePath 49 | } 50 | 51 | public static func == (lhs: RemoteRunnable, rhs: RemoteRunnable) -> Bool { 52 | lhs.isEqual(other: rhs) && rhs.isEqual(other: lhs) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Scheme/XCScheme+Runnable.swift: -------------------------------------------------------------------------------- 1 | import AEXML 2 | import Foundation 3 | 4 | public extension XCScheme { 5 | class Runnable: Equatable { 6 | // MARK: - Attributes 7 | 8 | public var runnableDebuggingMode: String 9 | public var buildableReference: BuildableReference? 10 | 11 | // MARK: - Init 12 | 13 | public init(buildableReference: BuildableReference?, 14 | runnableDebuggingMode: String = "0") { 15 | self.buildableReference = buildableReference 16 | self.runnableDebuggingMode = runnableDebuggingMode 17 | } 18 | 19 | init(element: AEXMLElement) throws { 20 | runnableDebuggingMode = element.attributes["runnableDebuggingMode"] ?? "0" 21 | buildableReference = try? BuildableReference(element: element["BuildableReference"]) 22 | } 23 | 24 | // MARK: - XML 25 | 26 | func xmlElement() -> AEXMLElement { 27 | let element = AEXMLElement(name: "Runnable", 28 | value: nil, 29 | attributes: ["runnableDebuggingMode": runnableDebuggingMode]) 30 | if let buildableReference { 31 | element.addChild(buildableReference.xmlElement()) 32 | } 33 | return element 34 | } 35 | 36 | // MARK: - Equatable 37 | 38 | func isEqual(other: Runnable) -> Bool { 39 | runnableDebuggingMode == other.runnableDebuggingMode && 40 | buildableReference == other.buildableReference 41 | } 42 | 43 | public static func == (lhs: Runnable, rhs: Runnable) -> Bool { 44 | lhs.isEqual(other: rhs) && rhs.isEqual(other: lhs) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Scheme/XCScheme+SerialAction.swift: -------------------------------------------------------------------------------- 1 | import AEXML 2 | import Foundation 3 | 4 | public extension XCScheme { 5 | class SerialAction: Equatable { 6 | // MARK: - Attributes 7 | 8 | public var preActions: [ExecutionAction] 9 | public var postActions: [ExecutionAction] 10 | 11 | // MARK: - Init 12 | 13 | init(_ preActions: [ExecutionAction], _ postActions: [ExecutionAction]) { 14 | self.preActions = preActions 15 | self.postActions = postActions 16 | } 17 | 18 | init(element: AEXMLElement) throws { 19 | preActions = try element["PreActions"]["ExecutionAction"].all?.map(ExecutionAction.init) ?? [] 20 | postActions = try element["PostActions"]["ExecutionAction"].all?.map(ExecutionAction.init) ?? [] 21 | } 22 | 23 | // MARK: - XML 24 | 25 | func writeXML(parent element: AEXMLElement) { 26 | if !preActions.isEmpty { 27 | let preActions = element.addChild(name: "PreActions") 28 | for preAction in self.preActions { 29 | preActions.addChild(preAction.xmlElement()) 30 | } 31 | } 32 | if !postActions.isEmpty { 33 | let postActions = element.addChild(name: "PostActions") 34 | for postAction in self.postActions { 35 | postActions.addChild(postAction.xmlElement()) 36 | } 37 | } 38 | } 39 | 40 | // MARK: - Equatable 41 | 42 | func isEqual(to: Any?) -> Bool { 43 | guard let rhs = to as? SerialAction else { return false } 44 | return preActions == rhs.preActions && 45 | postActions == rhs.postActions 46 | } 47 | 48 | public static func == (lhs: SerialAction, rhs: SerialAction) -> Bool { 49 | lhs.isEqual(to: rhs) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Scheme/XCScheme+StoreKitConfigurationFileReference.swift: -------------------------------------------------------------------------------- 1 | import AEXML 2 | import Foundation 3 | 4 | public extension XCScheme { 5 | final class StoreKitConfigurationFileReference: Equatable { 6 | // MARK: - Attributes 7 | 8 | public var identifier: String 9 | 10 | // MARK: - Init 11 | 12 | public init(identifier: String) { 13 | self.identifier = identifier 14 | } 15 | 16 | init(element: AEXMLElement) throws { 17 | identifier = element.attributes["identifier"]! 18 | } 19 | 20 | // MARK: - XML 21 | 22 | func xmlElement() -> AEXMLElement { 23 | AEXMLElement(name: "StoreKitConfigurationFileReference", 24 | value: nil, 25 | attributes: [ 26 | "identifier": identifier, 27 | ]) 28 | } 29 | 30 | // MARK: - Equatable 31 | 32 | public static func == (lhs: StoreKitConfigurationFileReference, rhs: StoreKitConfigurationFileReference) -> Bool { 33 | lhs.identifier == rhs.identifier 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Scheme/XCScheme+TestItem.swift: -------------------------------------------------------------------------------- 1 | import AEXML 2 | import Foundation 3 | 4 | public extension XCScheme { 5 | final class TestItem: Equatable { 6 | // MARK: - Attributes 7 | 8 | public var identifier: String 9 | 10 | // MARK: - Init 11 | 12 | public init(identifier: String) { 13 | self.identifier = identifier 14 | } 15 | 16 | init(element: AEXMLElement) throws { 17 | identifier = element.attributes["Identifier"]! 18 | } 19 | 20 | // MARK: - XML 21 | 22 | func xmlElement() -> AEXMLElement { 23 | AEXMLElement(name: "Test", 24 | value: nil, 25 | attributes: ["Identifier": identifier]) 26 | } 27 | 28 | // MARK: - Equatable 29 | 30 | public static func == (lhs: TestItem, rhs: TestItem) -> Bool { 31 | lhs.identifier == rhs.identifier 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Scheme/XCScheme+TestParallelization.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension XCScheme { 4 | /// With the introduction of Swift Testing and Xcode 16, you can now choose to run your tests 5 | // in parallel across either the full suite of tests in a target with `.all`, just those created 6 | // under Swift Testing with `.swiftTestingOnly`, or run them serially with the `.none` option. 7 | enum TestParallelization: String { 8 | case all 9 | case swiftTestingOnly 10 | case none 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Scheme/XCScheme+TestPlanReference.swift: -------------------------------------------------------------------------------- 1 | import AEXML 2 | import Foundation 3 | 4 | public extension XCScheme { 5 | final class TestPlanReference: Equatable { 6 | // MARK: - Attributes 7 | 8 | public var reference: String 9 | public var `default`: Bool 10 | 11 | // MARK: - Init 12 | 13 | public init(reference: String, 14 | default: Bool = false) { 15 | self.reference = reference 16 | self.default = `default` 17 | } 18 | 19 | init(element: AEXMLElement) throws { 20 | reference = element.attributes["reference"]! 21 | `default` = element.attributes["default"] == "YES" 22 | } 23 | 24 | // MARK: - XML 25 | 26 | func xmlElement() -> AEXMLElement { 27 | var attributes: [String: String] = ["reference": reference] 28 | if `default` { 29 | attributes["default"] = `default`.xmlString 30 | } 31 | 32 | let element = AEXMLElement(name: "TestPlanReference", 33 | value: nil, 34 | attributes: attributes) 35 | 36 | return element 37 | } 38 | 39 | // MARK: - Equatable 40 | 41 | public static func == (lhs: TestPlanReference, rhs: TestPlanReference) -> Bool { 42 | lhs.reference == rhs.reference && 43 | lhs.default == rhs.default 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Utils/Decoders.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Project object reference repository. 4 | class PBXObjectReferenceRepository { 5 | /// References. 6 | var references: [String: PBXObjectReference] = [:] 7 | let lock = NSRecursiveLock() 8 | 9 | /// Returns and creates if it doesn't exist an object reference. 10 | /// 11 | /// - Parameters: 12 | /// - reference: reference value. 13 | /// - objects: objects. 14 | /// - Returns: object reference. 15 | func getOrCreate(reference: String, objects: PBXObjects) -> PBXObjectReference { 16 | lock.whileLocked { 17 | if let objectReference = references[reference] { 18 | return objectReference 19 | } 20 | let objectReference = PBXObjectReference(reference, objects: objects) 21 | references[reference] = objectReference 22 | return objectReference 23 | } 24 | } 25 | } 26 | 27 | /// Context used when the project is being decoded. 28 | class ProjectDecodingContext { 29 | /// Object reference repository. 30 | let objectReferenceRepository: PBXObjectReferenceRepository 31 | 32 | /// Objects. 33 | let objects: PBXObjects 34 | 35 | init() { 36 | objectReferenceRepository = PBXObjectReferenceRepository() 37 | objects = PBXObjects(objects: []) 38 | } 39 | } 40 | 41 | // MARK: - CodingUserInfoKey (Context) 42 | 43 | extension CodingUserInfoKey { 44 | /// Context user info key. 45 | static let context: CodingUserInfoKey = .init(rawValue: "context")! 46 | } 47 | 48 | /// Xcodeproj JSON decoder. 49 | final class XcodeprojJSONDecoder: JSONDecoder, @unchecked Sendable { 50 | /// Default init. 51 | init(context: ProjectDecodingContext = ProjectDecodingContext()) { 52 | super.init() 53 | userInfo = [.context: context] 54 | } 55 | } 56 | 57 | /// Xcodeproj property list decoder. 58 | final class XcodeprojPropertyListDecoder: PropertyListDecoder, @unchecked Sendable { 59 | /// Default init. 60 | init(context: ProjectDecodingContext = ProjectDecodingContext()) { 61 | super.init() 62 | userInfo = [.context: context] 63 | } 64 | } 65 | 66 | // MARK: - Decoder (Context) 67 | 68 | extension Decoder { 69 | /// Returns the decoding context. 70 | var context: ProjectDecodingContext { 71 | // swiftlint:disable:next force_cast 72 | userInfo[.context] as! ProjectDecodingContext 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Utils/PlistDecoding.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | indirect enum PlistObject: Sendable, Equatable { 4 | case string(String) 5 | case array([String]) 6 | case dictionary([String: PlistObject]) 7 | } 8 | 9 | extension PlistObject: Codable { 10 | public init(from decoder: Decoder) throws { 11 | let container = try decoder.singleValueContainer() 12 | do { 13 | let string = try container.decode(String.self) 14 | self = .string(string) 15 | } catch { 16 | do { 17 | let array = try container.decode([String].self) 18 | self = .array(array) 19 | } catch { 20 | let dictionary = try container.decode([String: PlistObject].self) 21 | self = .dictionary(dictionary) 22 | } 23 | } 24 | } 25 | 26 | public func encode(to encoder: Encoder) throws { 27 | var container = encoder.singleValueContainer() 28 | switch self { 29 | case let .string(string): 30 | try container.encode(string) 31 | case let .array(array): 32 | try container.encode(array) 33 | case let .dictionary(dictionary): 34 | try container.encode(dictionary) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Workspace/XCWorkspaceDataElement.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum XCWorkspaceDataElement: Equatable { 4 | public enum Error: Swift.Error { 5 | case unknownName(String) 6 | } 7 | 8 | case file(XCWorkspaceDataFileRef) 9 | case group(XCWorkspaceDataGroup) 10 | 11 | /// Returns the location to the workspace data element. 12 | public var location: XCWorkspaceDataElementLocationType { 13 | switch self { 14 | case let .file(ref): 15 | ref.location 16 | case let .group(ref): 17 | ref.location 18 | } 19 | } 20 | 21 | // MARK: - Equatable 22 | 23 | public static func == (lhs: XCWorkspaceDataElement, rhs: XCWorkspaceDataElement) -> Bool { 24 | switch (lhs, rhs) { 25 | case let (.file(lhs), .file(rhs)): 26 | lhs == rhs 27 | case let (.group(lhs), .group(rhs)): 28 | lhs == rhs 29 | default: 30 | false 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Workspace/XCWorkspaceDataFileRef.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class XCWorkspaceDataFileRef { 4 | public var location: XCWorkspaceDataElementLocationType 5 | 6 | public init(location: XCWorkspaceDataElementLocationType) { 7 | self.location = location 8 | } 9 | } 10 | 11 | extension XCWorkspaceDataFileRef: Equatable { 12 | public static func == (lhs: XCWorkspaceDataFileRef, rhs: XCWorkspaceDataFileRef) -> Bool { 13 | lhs.location == rhs.location 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/XcodeProj/Workspace/XCWorkspaceDataGroup.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class XCWorkspaceDataGroup { 4 | public var location: XCWorkspaceDataElementLocationType 5 | public var name: String? 6 | public var children: [XCWorkspaceDataElement] 7 | 8 | public init(location: XCWorkspaceDataElementLocationType, name: String?, children: [XCWorkspaceDataElement]) { 9 | self.location = location 10 | self.name = name 11 | self.children = children 12 | } 13 | } 14 | 15 | extension XCWorkspaceDataGroup: Equatable { 16 | public static func == (lhs: XCWorkspaceDataGroup, rhs: XCWorkspaceDataGroup) -> Bool { 17 | lhs.location == rhs.location && 18 | lhs.name == rhs.name && 19 | lhs.children == rhs.children 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Templates/Equality.stencil: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | {% for type in types.implementing.AutoEquatable|class|!annotated:"skipEquality" %} 4 | extension {{ type.name }} { 5 | /// :nodoc: 6 | func isEqual(to rhs: {{ type.name }}) -> Bool { 7 | {% for variable in type.storedVariables %} 8 | {% if variable.typeName.dictionary %} 9 | if !NSDictionary(dictionary: {{ variable.name}}{% if variable.typeName.isOptional %} ?? [:]{% endif %}).isEqual(NSDictionary(dictionary: rhs.{{ variable.name }}{% if variable.typeName.isOptional %} ?? [:]{% endif %})) { return false } 10 | {% elif variable|!annotated:"skipEquality" %} 11 | if {{ variable.name }} != rhs.{{ variable.name }} { return false } 12 | {% endif %} 13 | {% endfor %} 14 | {% for variable in type.computedVariables|annotated:"forceEquality" %}if self.{{ variable.name }} != rhs.{{ variable.name }} { return false } 15 | {% endfor %} 16 | {% if type.inheritedTypes.first == "NSObject" %}return true{% else %}return super.isEqual(to: rhs){% endif %} 17 | } 18 | } 19 | 20 | {% endfor %} 21 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Extensions/PathExtrasTests.swift: -------------------------------------------------------------------------------- 1 | import PathKit 2 | import XCTest 3 | @testable import XcodeProj 4 | 5 | final class PathExtrasTests: XCTestCase { 6 | func testThat_GivenAbsoluteSubPath_WhenRelativeToAbsoluteSuperPath_ThenResultIsTheRemainder() { 7 | XCTAssertEqual(Path("/absolute/dir/file.txt").relative(to: Path("/absolute")), Path("dir/file.txt")) 8 | } 9 | 10 | func testThat_GivenAbsolutePath_WhenRelativeToNotSuperseedingAbsolutePath_ThenResultHasDoubleDot() { 11 | XCTAssertEqual(Path("/absolute/dir/file.txt").relative(to: Path("/absolute/anotherDir")), Path("../dir/file.txt")) 12 | } 13 | 14 | func testThat_GivenAbsoluteSubPath_WhenRelativeToIntersectingAbsolutePath_ThenResultIsTheFullPathToTheRootAndThenFullAbsolutePath() { 15 | XCTAssertEqual(Path("/absolute/dir/file.txt").relative(to: Path("/var")), Path("../absolute/dir/file.txt")) 16 | } 17 | 18 | func testThat_GivenSubPath_WhenRelativeToSuperPath_ThenResultIsTheRemainder() { 19 | XCTAssertEqual(Path("some/dir/file.txt").relative(to: Path("some")), Path("dir/file.txt")) 20 | } 21 | 22 | func testThat_GivenPath_WhenRelativeToNotSuperseedingPath_ThenResultHasDoubleDot() { 23 | XCTAssertEqual(Path("some/dir/file.txt").relative(to: Path("anotherDir")), Path("../some/dir/file.txt")) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Extensions/XCTestCase+Assertions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | extension XCTestCase { 5 | typealias EquatableError = Equatable & Error 6 | 7 | func XCTAssertNotNilAndUnwrap(_ obj: T?, message: String = "") -> T { 8 | guard let unwrappedObj = obj else { 9 | XCTAssertNotNil(obj, message) 10 | fatalError() // Unreachable since the above assertion will fail 11 | } 12 | return unwrappedObj 13 | } 14 | 15 | func XCTAssertThrowsSpecificError(_ expression: @autoclosure () throws -> some Any, _ error: E, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { 16 | XCTAssertThrowsError(try expression(), message(), file: file, line: line) { actualError in 17 | let message = "Expected \(error) got \(actualError)" 18 | 19 | guard let actualCastedError = actualError as? E else { 20 | XCTFail(message, file: file, line: line) 21 | return 22 | } 23 | XCTAssertEqual(actualCastedError, error, message, file: file, line: line) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Extensions/XCTestCase+Shell.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Returns the output of running `executable` with `args`. Throws an error if the process exits indicating failure. 4 | @discardableResult 5 | func checkedOutput(_ executable: String, _ args: [String]) throws -> String? { 6 | let process = Process() 7 | let output = Pipe() 8 | 9 | if executable.contains("/") { 10 | process.launchPath = executable 11 | } else { 12 | process.launchPath = try checkedOutput("/usr/bin/which", [executable])?.trimmingCharacters(in: .newlines) 13 | } 14 | 15 | process.arguments = args 16 | process.standardOutput = output 17 | process.launch() 18 | process.waitUntilExit() 19 | 20 | guard process.terminationStatus == 0 else { 21 | throw NSError(domain: NSPOSIXErrorDomain, code: Int(process.terminationStatus)) 22 | } 23 | 24 | return String(data: output.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) 25 | } 26 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Extensions/XCTestCase+Temporary.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PathKit 3 | import XCTest 4 | 5 | extension XCTestCase { 6 | func withTemporaryDirectory(_ closure: (Path) throws -> Void) throws { 7 | let directory = try Path.uniqueTemporary() 8 | try closure(directory) 9 | try directory.delete() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/BuildPhase/BuildPhaseTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XcodeProj 3 | import XCTest 4 | 5 | class BuildPhaseTests: XCTestCase { 6 | func test_sources_hasTheCorrectRawValue() { 7 | XCTAssertEqual(BuildPhase.sources.rawValue, "Sources") 8 | } 9 | 10 | func test_frameworks_hasTheCorrectRawValue() { 11 | XCTAssertEqual(BuildPhase.frameworks.rawValue, "Frameworks") 12 | } 13 | 14 | func test_resources_hasTheCorrectRawValue() { 15 | XCTAssertEqual(BuildPhase.resources.rawValue, "Resources") 16 | } 17 | 18 | func test_copyFiles_hasTheCorrectRawValue() { 19 | XCTAssertEqual(BuildPhase.copyFiles.rawValue, "CopyFiles") 20 | } 21 | 22 | func test_runStript_hasTheCorrectRawValue() { 23 | XCTAssertEqual(BuildPhase.runScript.rawValue, "Run Script") 24 | } 25 | 26 | func test_headers_hasTheCorrectRawValue() { 27 | XCTAssertEqual(BuildPhase.headers.rawValue, "Headers") 28 | } 29 | 30 | func test_carbonResources_hasTheCorrectRawValue() { 31 | XCTAssertEqual(BuildPhase.carbonResources.rawValue, "Rez") 32 | } 33 | 34 | func test_sources_hasTheCorrectBuildPhase() { 35 | XCTAssertEqual(BuildPhase.sources, PBXSourcesBuildPhase().buildPhase) 36 | } 37 | 38 | func test_frameworks_hasTheCorrectBuildPhase() { 39 | XCTAssertEqual(BuildPhase.frameworks, PBXFrameworksBuildPhase().buildPhase) 40 | } 41 | 42 | func test_resources_hasTheCorrectBuildPhase() { 43 | XCTAssertEqual(BuildPhase.resources, PBXResourcesBuildPhase().buildPhase) 44 | } 45 | 46 | func test_copyFiles_hasTheCorrectBuildPhase() { 47 | XCTAssertEqual(BuildPhase.copyFiles, PBXCopyFilesBuildPhase().buildPhase) 48 | } 49 | 50 | func test_runStript_hasTheCorrectBuildPhase() { 51 | XCTAssertEqual(BuildPhase.runScript, PBXShellScriptBuildPhase().buildPhase) 52 | } 53 | 54 | func test_headers_hasTheCorrectBuildPhase() { 55 | XCTAssertEqual(BuildPhase.headers, PBXHeadersBuildPhase().buildPhase) 56 | } 57 | 58 | func test_carbonResources_hasTheCorrectBuildPhase() { 59 | XCTAssertEqual(BuildPhase.carbonResources, PBXRezBuildPhase().buildPhase) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/BuildPhase/PBXBuildFileTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XcodeProj 3 | import XCTest 4 | 5 | final class PBXBuildFileTests: XCTestCase { 6 | func test_isa_returnsTheCorrectValue() { 7 | XCTAssertEqual(PBXBuildFile.isa, "PBXBuildFile") 8 | } 9 | 10 | func test_platformFilterIsSet() { 11 | let pbxBuildFile = PBXBuildFile( 12 | platformFilter: "platformFilter" 13 | ) 14 | XCTAssertEqual(pbxBuildFile.platformFilter, "platformFilter") 15 | } 16 | 17 | func test_platformCompilerFlagsIsSet() { 18 | let expected = "flagValue" 19 | let pbxBuildFile = PBXBuildFile( 20 | settings: ["COMPILER_FLAGS": .string(expected)] 21 | ) 22 | XCTAssertEqual(pbxBuildFile.compilerFlags, expected) 23 | } 24 | 25 | func test_platformAttributesIsSet() { 26 | let expected = ["Public"] 27 | let pbxBuildFile = PBXBuildFile( 28 | settings: ["ATTRIBUTES": .array(expected)] 29 | ) 30 | XCTAssertEqual(pbxBuildFile.attributes, expected) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/BuildPhase/PBXBuildPhaseTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | @testable import XcodeProj 4 | 5 | final class PBXBuildPhaseTests: XCTestCase { 6 | var subject: PBXBuildPhase! 7 | var proj: PBXProj! 8 | 9 | override func setUp() { 10 | super.setUp() 11 | subject = PBXSourcesBuildPhase() 12 | proj = PBXProj.fixture() 13 | proj.add(object: subject) 14 | } 15 | 16 | func test_add_files() throws { 17 | let file = PBXFileElement(sourceTree: .absolute, 18 | path: "path", 19 | name: "name", 20 | includeInIndex: false, 21 | wrapsLines: true) 22 | 23 | let buildFile = try subject.add(file: file) 24 | XCTAssertEqual(subject.files?.contains(buildFile), true) 25 | } 26 | 27 | func test_add_files_only_once() throws { 28 | let file = PBXFileElement(sourceTree: .absolute, 29 | path: "path", 30 | name: "name", 31 | includeInIndex: false, 32 | wrapsLines: true) 33 | 34 | let buildFile = try subject.add(file: file) 35 | let sameBuildFile = try subject.add(file: file) 36 | XCTAssertEqual(buildFile, sameBuildFile, "Expected adding a file only once but it didn't") 37 | 38 | let fileOccurrencesCount = subject.files?.filter { $0 == buildFile }.count 39 | XCTAssertTrue(fileOccurrencesCount == 1, "Expected adding a file only once but it didn't") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/BuildPhase/PBXBuildRuleTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XcodeProj 3 | import XCTest 4 | 5 | final class PBXBuildRuleTests: XCTestCase { 6 | var subject: PBXBuildRule! 7 | 8 | override func setUp() { 9 | super.setUp() 10 | subject = PBXBuildRule(compilerSpec: "spec", 11 | fileType: "type", 12 | isEditable: true, 13 | filePatterns: "pattern", 14 | name: "rule", 15 | outputFiles: ["a", "b"], 16 | outputFilesCompilerFlags: ["-1", "-2"], 17 | script: "script", 18 | runOncePerArchitecture: false) 19 | } 20 | 21 | func test_init_initializesTheBuildRuleWithTheRightAttributes() { 22 | XCTAssertEqual(subject.compilerSpec, "spec") 23 | XCTAssertEqual(subject.filePatterns, "pattern") 24 | XCTAssertEqual(subject.fileType, "type") 25 | XCTAssertEqual(subject.isEditable, true) 26 | XCTAssertEqual(subject.name, "rule") 27 | XCTAssertEqual(subject.outputFiles, ["a", "b"]) 28 | XCTAssertEqual(subject.outputFilesCompilerFlags ?? [], ["-1", "-2"]) 29 | XCTAssertEqual(subject.script, "script") 30 | XCTAssertEqual(subject.runOncePerArchitecture, false) 31 | } 32 | 33 | func test_isa_returnsTheCorrectValue() { 34 | XCTAssertEqual(PBXBuildRule.isa, "PBXBuildRule") 35 | } 36 | 37 | func test_equal_shouldReturnTheCorrectValue() { 38 | let another = PBXBuildRule(compilerSpec: "spec", 39 | fileType: "type", 40 | isEditable: true, 41 | filePatterns: "pattern", 42 | name: "rule", 43 | outputFiles: ["a", "b"], 44 | outputFilesCompilerFlags: ["-1", "-2"], 45 | script: "script", 46 | runOncePerArchitecture: false) 47 | XCTAssertEqual(subject, another) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/BuildPhase/PBXFrameworksBuildPhaseTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | @testable import XcodeProj 4 | 5 | final class PBXFrameworksBuildPhaseTests: XCTestCase { 6 | func test_isa_returnsTheRightValue() { 7 | XCTAssertEqual(PBXFrameworksBuildPhase.isa, "PBXFrameworksBuildPhase") 8 | } 9 | 10 | func test_init_fails_whenTheFilesAreMissing() { 11 | var dictionary = testDictionary() 12 | dictionary.removeValue(forKey: "files") 13 | let data = try! JSONSerialization.data(withJSONObject: dictionary, options: []) 14 | let decoder = XcodeprojJSONDecoder() 15 | do { 16 | let phase = try decoder.decode(PBXCopyFilesBuildPhase.self, from: data) 17 | XCTAssertNil(phase.files, "Expected files to be nil but it's present") 18 | } catch {} 19 | } 20 | 21 | private func testDictionary() -> [String: Any] { 22 | [ 23 | "files": ["file1"], 24 | "runOnlyForDeploymentPostprocessing": 0, 25 | "reference": "reference", 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/BuildPhase/PBXHeadersBuildPhaseTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | @testable import XcodeProj 4 | 5 | final class PBXHeadersBuildPhaseTests: XCTestCase { 6 | func test_isa_returnsTheCorrectValue() { 7 | XCTAssertEqual(PBXHeadersBuildPhase.isa, "PBXHeadersBuildPhase") 8 | } 9 | 10 | func test_init_failsWhenTheBuildActionMaskIsMissing() { 11 | var dictionary = testDictionary() 12 | dictionary.removeValue(forKey: "buildActionMask") 13 | let data = try! JSONSerialization.data(withJSONObject: dictionary, options: []) 14 | let decoder = XcodeprojJSONDecoder() 15 | do { 16 | let phase = try decoder.decode(PBXCopyFilesBuildPhase.self, from: data) 17 | XCTAssertEqual(phase.buildActionMask, PBXBuildPhase.defaultBuildActionMask, "Expected buildActionMask to be equal to \(PBXBuildPhase.defaultBuildActionMask) but it's \(phase.buildActionMask)") 18 | } catch {} 19 | } 20 | 21 | func test_init_failWhenFilesIsMissing() { 22 | var dictionary = testDictionary() 23 | dictionary.removeValue(forKey: "files") 24 | let data = try! JSONSerialization.data(withJSONObject: dictionary, options: []) 25 | let decoder = XcodeprojJSONDecoder() 26 | do { 27 | let phase = try decoder.decode(PBXCopyFilesBuildPhase.self, from: data) 28 | XCTAssertNil(phase.files, "Expected files to be nil but it's present") 29 | } catch {} 30 | } 31 | 32 | func test_init_failsWhenRunOnlyForDeploymentPostProcessingIsMissing() { 33 | var dictionary = testDictionary() 34 | dictionary.removeValue(forKey: "runOnlyForDeploymentPostprocessing") 35 | let data = try! JSONSerialization.data(withJSONObject: dictionary, options: []) 36 | let decoder = XcodeprojJSONDecoder() 37 | do { 38 | let phase = try decoder.decode(PBXCopyFilesBuildPhase.self, from: data) 39 | XCTAssertFalse(phase.runOnlyForDeploymentPostprocessing, "Expected runOnlyForDeploymentPostprocessing to default to false") 40 | } catch {} 41 | } 42 | 43 | private func testDictionary() -> [String: Any] { 44 | [ 45 | "buildActionMask": 3, 46 | "files": ["file"], 47 | "runOnlyForDeploymentPostprocessing": 2, 48 | "reference": "reference", 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/BuildPhase/PBXResourcesBuildPhaseTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XcodeProj 3 | import XCTest 4 | 5 | final class PBXResourcesBuildPhaseTests: XCTestCase { 6 | func test_isa_returnsTheCorrectValue() { 7 | XCTAssertEqual(PBXResourcesBuildPhase.isa, "PBXResourcesBuildPhase") 8 | } 9 | 10 | private func testDictionary() -> [String: Any] { 11 | [ 12 | "files": ["file1"], 13 | "buildActionMask": "333", 14 | "runOnlyForDeploymentPostprocessing": "3", 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/BuildPhase/PBXRezBuildPhaseTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XcodeProj 3 | import XCTest 4 | 5 | final class PBXRezBuildPhaseTests: XCTestCase { 6 | func test_isa_returnsTheCorrectValue() { 7 | XCTAssertEqual(PBXRezBuildPhase.isa, "PBXRezBuildPhase") 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/BuildPhase/PBXSourcesBuildPhase+Fixtures.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @testable import XcodeProj 4 | 5 | extension PBXSourcesBuildPhase { 6 | static func fixture(files: [PBXBuildFile] = []) -> PBXSourcesBuildPhase { 7 | PBXSourcesBuildPhase(files: files, 8 | inputFileListPaths: nil, 9 | outputFileListPaths: nil, 10 | buildActionMask: PBXBuildPhase.defaultBuildActionMask, 11 | runOnlyForDeploymentPostprocessing: false) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/BuildPhase/PBXSourcesBuildPhaseTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XcodeProj 3 | import XCTest 4 | 5 | class PBXSourcesBuildPhaseTests: XCTestCase { 6 | func test_itHasTheCorrectIsa() { 7 | XCTAssertEqual(PBXSourcesBuildPhase.isa, "PBXSourcesBuildPhase") 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Configuration/BuildFileSettingTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Testing 3 | @testable import XcodeProj 4 | 5 | struct BuildFileSettingTests { 6 | @Test func test_BuildSettings_encodes_to_JSON() async throws { 7 | let expectedJSON = #"{"one":"one","two":["two","two"]}"# 8 | 9 | let settings: [String: BuildFileSetting] = [ 10 | "one": .string("one"), 11 | "two": .array(["two", "two"]), 12 | ] 13 | 14 | let encoder = JSONEncoder() 15 | encoder.outputFormatting = .sortedKeys 16 | 17 | let result = try encoder.encode(settings) 18 | 19 | #expect(result == expectedJSON.data(using: .utf8)) 20 | } 21 | 22 | @Test func test_buildSettings_decodes_from_JSON() async throws { 23 | let json = #"{"one":"one","two":["two","two"]}"# 24 | 25 | let expectedSettings: [String: BuildFileSetting] = [ 26 | "one": .string("one"), 27 | "two": .array(["two", "two"]), 28 | ] 29 | 30 | let result = try JSONDecoder().decode([String: BuildFileSetting].self, from: json.data(using: .utf8)!) 31 | 32 | #expect(result == expectedSettings) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Testing 3 | @testable import XcodeProj 4 | 5 | struct BuildSettingTests { 6 | @Test func test_BuildSettings_encode_to_string() async throws { 7 | #expect(BuildSetting.string("one").description == "one") 8 | #expect(BuildSetting.array(["one", "two"]).description == "one two") 9 | } 10 | 11 | @Test func test_buildSettings_decodes_from_JSON() async throws { 12 | let json = #"{"one":"one","two":["two","two"]}"# 13 | 14 | let expectedSettings: BuildSettings = [ 15 | "one": .string("one"), 16 | "two": .array(["two", "two"]), 17 | ] 18 | 19 | let result = try JSONDecoder().decode(BuildSettings.self, from: json.data(using: .utf8)!) 20 | 21 | #expect(result == expectedSettings) 22 | } 23 | 24 | @Test func test_buildSettings_bool_conversion() async throws { 25 | let settings: [BuildSetting] = [ 26 | BuildSetting.string("YES"), 27 | BuildSetting.string("NO"), 28 | BuildSetting.string("tuist"), 29 | BuildSetting.string("No"), 30 | BuildSetting.string("yES"), 31 | BuildSetting.array(["YES", "yES"]), 32 | true, 33 | false, 34 | ] 35 | 36 | let expected: [Bool?] = [ 37 | true, 38 | false, 39 | nil, 40 | nil, 41 | nil, 42 | nil, 43 | true, 44 | false, 45 | ] 46 | 47 | #expect(settings.map(\.boolValue) == expected) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Configuration/XCBuildConfiguration+Fixtures.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import XcodeProj 3 | 4 | extension XCBuildConfiguration { 5 | static func fixture(name: String = "Debug") -> XCBuildConfiguration { 6 | XCBuildConfiguration(name: name) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Configuration/XCConfigurationList+Fixtures.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import XcodeProj 3 | 4 | extension XCConfigurationList { 5 | static func fixture(buildConfigurations: [XCBuildConfiguration] = [XCBuildConfiguration.fixture(name: "Debug"), 6 | XCBuildConfiguration.fixture(name: "Release")], 7 | defaultConfigurationName: String? = "Debug", 8 | defaultConfigurationIsVisible _: Bool = true) -> XCConfigurationList 9 | { 10 | XCConfigurationList(buildConfigurations: buildConfigurations, 11 | defaultConfigurationName: defaultConfigurationName) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Configuration/XCConfigurationListTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | @testable import XcodeProj 4 | 5 | final class XCConfigurationListTests: XCTestCase { 6 | func test_isa_returnsTheCorrectValue() { 7 | XCTAssertEqual(XCConfigurationList.isa, "XCConfigurationList") 8 | } 9 | 10 | func test_addDefaultConfigurations() throws { 11 | let objects = PBXObjects() 12 | let configurationList = XCConfigurationList(buildConfigurations: []) 13 | objects.add(object: configurationList) 14 | let configurations = try configurationList.addDefaultConfigurations() 15 | let names = configurations.map(\.name) 16 | 17 | XCTAssertEqual(configurations.count, 2) 18 | XCTAssertTrue(names.contains("Debug")) 19 | XCTAssertTrue(names.contains("Release")) 20 | } 21 | 22 | func test_configuration_with_name() throws { 23 | let objects = PBXObjects() 24 | let configurationList = XCConfigurationList(buildConfigurations: []) 25 | objects.add(object: configurationList) 26 | let configurations = try configurationList.addDefaultConfigurations() 27 | 28 | XCTAssertEqual( 29 | configurationList.configuration(name: "Debug"), 30 | configurations.first(where: { $0.name == "Debug" }) 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Files/PBXContainerItemProxyTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | @testable import XcodeProj 4 | 5 | final class PBXContainerItemProxyTests: XCTestCase { 6 | func test_itHasTheCorrectIsa() { 7 | XCTAssertEqual(PBXContainerItemProxy.isa, "PBXContainerItemProxy") 8 | } 9 | 10 | func test_init_shouldFail_ifContainerPortalIsMissing() { 11 | var dictionary = testDictionary() 12 | dictionary.removeValue(forKey: "containerPortal") 13 | let data = try! JSONSerialization.data(withJSONObject: dictionary, options: []) 14 | let decoder = XcodeprojJSONDecoder() 15 | do { 16 | _ = try decoder.decode(PBXContainerItemProxy.self, from: data) 17 | XCTAssertTrue(false, "Expected to throw an error but it didn't") 18 | } catch {} 19 | } 20 | 21 | func test_maintains_remoteID() { 22 | let target = PBXNativeTarget(name: "") 23 | let project = PBXProject(name: "", buildConfigurationList: XCConfigurationList(), compatibilityVersion: "", preferredProjectObjectVersion: nil, minimizedProjectReferenceProxies: nil, mainGroup: PBXGroup()) 24 | let containerProxy = PBXContainerItemProxy(containerPortal: .project(project), remoteGlobalID: .object(target)) 25 | 26 | XCTAssertEqual(target.uuid, containerProxy.remoteGlobalID?.uuid) 27 | } 28 | 29 | private func testDictionary() -> [String: Any] { 30 | [ 31 | "containerPortal": "containerPortal", 32 | "remoteGlobalIDString": "remoteGlobalIDString", 33 | "remoteInfo": "remoteInfo", 34 | "reference": "reference", 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Files/PBXFileReference+Fixtures.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @testable import XcodeProj 4 | 5 | extension PBXFileReference { 6 | static func fixture(sourceTree _: PBXSourceTree = .group, 7 | name: String? = "Test") -> PBXFileReference 8 | { 9 | PBXFileReference(sourceTree: .group, name: name) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Files/PBXFileReferenceTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XcodeProj 3 | import XCTest 4 | 5 | final class PBXFileReferenceTests: XCTestCase { 6 | var subject: PBXFileReference! 7 | 8 | override func setUp() { 9 | super.setUp() 10 | subject = PBXFileReference(sourceTree: .absolute, 11 | name: "name", 12 | fileEncoding: 1, 13 | explicitFileType: "type", 14 | lastKnownFileType: "last", 15 | path: "path") 16 | } 17 | 18 | func test_init_initializesTheReferenceWithTheRightAttributes() { 19 | XCTAssertEqual(subject.name, "name") 20 | XCTAssertEqual(subject.sourceTree, .absolute) 21 | XCTAssertEqual(subject.fileEncoding, 1) 22 | XCTAssertEqual(subject.explicitFileType, "type") 23 | XCTAssertEqual(subject.lastKnownFileType, "last") 24 | XCTAssertEqual(subject.path, "path") 25 | } 26 | 27 | func test_isa_hashTheCorrectValue() { 28 | XCTAssertEqual(PBXFileReference.isa, "PBXFileReference") 29 | } 30 | 31 | func test_equal_returnsTheCorrectValue() { 32 | let another = PBXFileReference(sourceTree: .absolute, 33 | name: "name", 34 | fileEncoding: 1, 35 | explicitFileType: "type", 36 | lastKnownFileType: "last", 37 | path: "path") 38 | XCTAssertEqual(subject, another) 39 | } 40 | 41 | private func testDictionary() -> [String: Any] { 42 | [ 43 | "name": "name", 44 | "sourceTree": "group", 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | extension PBXFileSystemSynchronizedBuildFileExceptionSet { 4 | static func fixture(target: PBXTarget = .fixture(), 5 | membershipExceptions: [String]? = [], 6 | publicHeaders: [String]? = [], 7 | privateHeaders: [String]? = [], 8 | additionalCompilerFlagsByRelativePath: [String: String]? = nil, 9 | attributesByRelativePath: [String: [String]]? = nil) -> PBXFileSystemSynchronizedBuildFileExceptionSet { 10 | PBXFileSystemSynchronizedBuildFileExceptionSet(target: target, 11 | membershipExceptions: membershipExceptions, 12 | publicHeaders: publicHeaders, 13 | privateHeaders: privateHeaders, 14 | additionalCompilerFlagsByRelativePath: additionalCompilerFlagsByRelativePath, 15 | attributesByRelativePath: attributesByRelativePath) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSetTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | @testable import XcodeProj 4 | 5 | final class PBXFileSystemSynchronizedBuildFileExceptionSetTests: XCTestCase { 6 | var target: PBXTarget! 7 | var subject: PBXFileSystemSynchronizedBuildFileExceptionSet! 8 | 9 | override func setUp() { 10 | super.setUp() 11 | target = PBXTarget.fixture() 12 | subject = PBXFileSystemSynchronizedBuildFileExceptionSet.fixture(target: target) 13 | } 14 | 15 | override func tearDown() { 16 | target = nil 17 | subject = nil 18 | super.tearDown() 19 | } 20 | 21 | func test_itHasTheCorrectIsa() { 22 | XCTAssertEqual(PBXFileSystemSynchronizedBuildFileExceptionSet.isa, "PBXFileSystemSynchronizedBuildFileExceptionSet") 23 | } 24 | 25 | func test_equal_returnsTheCorrectValue() { 26 | let another = PBXFileSystemSynchronizedBuildFileExceptionSet.fixture(target: target) 27 | XCTAssertEqual(subject, another) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroup+Fixtures.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | extension PBXFileSystemSynchronizedRootGroup { 4 | static func fixture(sourceTree: PBXSourceTree? = nil, 5 | path: String? = nil, 6 | name: String? = nil, 7 | includeInIndex: Bool? = nil, 8 | usesTabs: Bool? = nil, 9 | indentWidth: UInt? = nil, 10 | tabWidth: UInt? = nil, 11 | wrapsLines: Bool? = nil, 12 | explicitFileTypes: [String: String] = [:], 13 | exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet] = [], 14 | explicitFolders: [String] = []) -> PBXFileSystemSynchronizedRootGroup { 15 | PBXFileSystemSynchronizedRootGroup(sourceTree: sourceTree, 16 | path: path, 17 | name: name, 18 | includeInIndex: includeInIndex, 19 | usesTabs: usesTabs, 20 | indentWidth: indentWidth, 21 | tabWidth: tabWidth, 22 | wrapsLines: wrapsLines, 23 | explicitFileTypes: explicitFileTypes, 24 | exceptions: exceptions, 25 | explicitFolders: explicitFolders) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Files/PBXGroup+Fixtures.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import XcodeProj 3 | 4 | extension PBXGroup { 5 | static func fixture(children _: [PBXFileElement] = [], 6 | sourceTree: PBXSourceTree = .group, 7 | name: String = "test") -> PBXGroup 8 | { 9 | PBXGroup(children: [], 10 | sourceTree: sourceTree, 11 | name: name) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Files/PBXVariantGroupTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XcodeProj 3 | import XCTest 4 | 5 | final class PBXVariantGroupTests: XCTestCase { 6 | func test_itHasTheCorrectIsa() { 7 | XCTAssertEqual(PBXVariantGroup.isa, "PBXVariantGroup") 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Files/XCVersionGroup+Fixtures.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import XcodeProj 3 | 4 | extension XCVersionGroup { 5 | static func fixture(objects: PBXObjects, 6 | currentVersion: PBXFileReference? = PBXFileReference(name: "currentVersion"), 7 | path: String = "path", 8 | name: String? = "name", 9 | sourceTree: PBXSourceTree = .group, 10 | versionGroupType: String = "versionGroupType", 11 | children: [PBXFileReference] = [PBXFileReference(name: "currentVersion")]) -> XCVersionGroup 12 | { 13 | let group = XCVersionGroup(currentVersion: currentVersion, 14 | path: path, 15 | name: name, 16 | sourceTree: sourceTree, 17 | versionGroupType: versionGroupType, 18 | children: children) 19 | if let currentVersion { 20 | objects.add(object: currentVersion) 21 | } 22 | children.forEach { objects.add(object: $0) } 23 | objects.add(object: group) 24 | return group 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Files/XCVersionGroupTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | @testable import XcodeProj 4 | 5 | final class XCVersionGroupTests: XCTestCase {} 6 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Project/PBXProj+Fixtures.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import XcodeProj 3 | 4 | extension PBXProj { 5 | static func fixture(rootObject: PBXProject? = PBXProject.fixture(), 6 | objectVersion: UInt = Xcode.LastKnown.objectVersion, 7 | archiveVersion: UInt = Xcode.LastKnown.archiveVersion, 8 | classes: [String: [String]] = [:], 9 | objects: [PBXObject] = []) -> PBXProj 10 | { 11 | PBXProj(rootObject: rootObject, 12 | objectVersion: objectVersion, 13 | archiveVersion: archiveVersion, 14 | classes: classes, 15 | objects: objects) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Project/PBXProj+XCTest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import XcodeProj 3 | 4 | extension PBXProj { 5 | func encode() throws -> String { 6 | let outputSettings = PBXOutputSettings() 7 | let encoder = PBXProjEncoder(outputSettings: outputSettings) 8 | return try encoder.encode(proj: self) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Project/PBXProjObjectsHelpersTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | @testable import XcodeProj 4 | 5 | final class PBXProjObjectsHelpersTests: XCTestCase { 6 | var subject: PBXObjects! 7 | 8 | override func setUp() { 9 | super.setUp() 10 | subject = PBXObjects(objects: []) 11 | } 12 | 13 | func test_targetsNamed_returnsTheCorrectValue() { 14 | let nativeTarget = PBXNativeTarget(name: "test", buildConfigurationList: nil) 15 | let legacyTarget = PBXLegacyTarget(name: "test", buildConfigurationList: nil) 16 | let aggregateTarget = PBXAggregateTarget(name: "test", buildConfigurationList: nil) 17 | subject.add(object: nativeTarget) 18 | subject.add(object: legacyTarget) 19 | subject.add(object: aggregateTarget) 20 | let got = subject.targets(named: "test").map { $0 } 21 | XCTAssertTrue(got.contains(nativeTarget)) 22 | XCTAssertTrue(got.contains(legacyTarget)) 23 | XCTAssertTrue(got.contains(aggregateTarget)) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Project/PBXProject+Fixtures.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import XcodeProj 3 | 4 | extension PBXProject { 5 | static func fixture(name: String = "test", 6 | buildConfigurationList: XCConfigurationList = XCConfigurationList.fixture(), 7 | compatibilityVersion: String = Xcode.Default.compatibilityVersion, 8 | mainGroup: PBXGroup = PBXGroup.fixture()) -> PBXProject 9 | { 10 | PBXProject(name: name, 11 | buildConfigurationList: buildConfigurationList, 12 | compatibilityVersion: compatibilityVersion, 13 | preferredProjectObjectVersion: nil, 14 | minimizedProjectReferenceProxies: nil, 15 | mainGroup: mainGroup) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/SwiftPackage/XCLocalSwiftPackageReferenceTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeProj 5 | 6 | final class XCLocalSwiftPackageReferenceTests: XCTestCase { 7 | func test_init() throws { 8 | // Given 9 | let decoder = XcodeprojPropertyListDecoder() 10 | let plist: [String: [String: Any]] = ["ref": ["relativePath": "path"]] 11 | 12 | let data = try PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0) 13 | 14 | // When 15 | let decoded = try decoder.decode([String: XCLocalSwiftPackageReference].self, from: data) 16 | let got = try XCTUnwrap(decoded["ref"]) 17 | 18 | // Then 19 | XCTAssertEqual(got.reference.value, "ref") 20 | XCTAssertEqual(got.relativePath, "path") 21 | } 22 | 23 | func test_plistValues() throws { 24 | // When 25 | let proj = PBXProj() 26 | let subject = XCLocalSwiftPackageReference(relativePath: "repository") 27 | 28 | // Given 29 | let got = try subject.plistKeyAndValue(proj: proj, reference: "ref") 30 | 31 | // Then 32 | XCTAssertEqual(got.value, .dictionary([ 33 | "isa": "XCLocalSwiftPackageReference", 34 | "relativePath": "repository", 35 | ])) 36 | } 37 | 38 | func test_equal() { 39 | // When 40 | let first = XCLocalSwiftPackageReference(relativePath: "repository") 41 | let second = XCLocalSwiftPackageReference(relativePath: "repository") 42 | 43 | // Then 44 | XCTAssertEqual(first, second) 45 | } 46 | 47 | func test_name() { 48 | // When 49 | let subject = XCLocalSwiftPackageReference(relativePath: "tuist/xcodeproj") 50 | 51 | // Then 52 | XCTAssertEqual(subject.name, "tuist/xcodeproj") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Targets/PBXLegacyTargetTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | @testable import XcodeProj 4 | 5 | final class PBXLegacyTargetTests: XCTestCase { 6 | var subject: PBXLegacyTarget! 7 | 8 | override func setUp() { 9 | super.setUp() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Targets/PBXReferenceProxyTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | @testable import XcodeProj 4 | 5 | final class PBXReferenceProxyTests: XCTestCase {} 6 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Targets/PBXTarget+Fixtures.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @testable import XcodeProj 4 | 5 | extension PBXTarget { 6 | static func fixture(name: String = "Test", 7 | buildConfigurationList: XCConfigurationList = XCConfigurationList.fixture(), 8 | buildPhases: [PBXBuildPhase] = [], 9 | buildRules: [PBXBuildRule] = [], 10 | dependencies: [PBXTargetDependency] = [], 11 | productName: String? = "Test", 12 | product: PBXFileReference = PBXFileReference.fixture(name: "Test.app"), 13 | productType: PBXProductType = PBXProductType.application) -> PBXTarget 14 | { 15 | PBXTarget(name: name, 16 | buildConfigurationList: buildConfigurationList, 17 | buildPhases: buildPhases, 18 | buildRules: buildRules, 19 | dependencies: dependencies, 20 | productName: productName, 21 | product: product, 22 | productType: productType) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Objects/Targets/PBXTargetDependencyTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | @testable import XcodeProj 4 | 5 | final class PBXTargetDependencyTests: XCTestCase { 6 | func test_hasTheCorrectIsa() { 7 | XCTAssertEqual(PBXTargetDependency.isa, "PBXTargetDependency") 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Project/XCUserDataTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PathKit 3 | import XCTest 4 | @testable import XcodeProj 5 | 6 | final class XCUserDataTests: XCTestCase { 7 | func test_read_userData() throws { 8 | let subject = try XCUserData(path: userDataPath) 9 | assert(userData: subject, userName: "username1") 10 | } 11 | 12 | func test_write_userData() throws { 13 | try testWrite(from: userDataPath, 14 | initModel: { try? XCUserData(path: $0) }, 15 | modify: { userData in 16 | // XCScheme's that are already in place (the removed element) should not be removed by a write 17 | userData.schemes = userData.schemes.filter { $0.name != "iOS-other" } 18 | return userData 19 | }, 20 | assertion: { 21 | assert(userData: $1, userName: "copy") 22 | }) 23 | } 24 | 25 | func test_read_write_produces_no_diff() throws { 26 | try testReadWriteProducesNoDiff(from: userDataPath, initModel: XCUserData.init(path:)) 27 | } 28 | 29 | // MARK: - Private 30 | 31 | private func assert(userData: XCUserData, userName: String) { 32 | XCTAssertEqual(userData.userName, userName) 33 | XCTAssertEqual(userData.schemes.count, 3) 34 | XCTAssertEqual(userData.breakpoints?.breakpoints.count, 2) 35 | XCTAssertEqual(userData.schemeManagement?.schemeUserState?.count, 6) 36 | } 37 | 38 | private var userDataPath: Path { 39 | fixturesPath() + "iOS/Project.xcodeproj/xcuserdata/username1.xcuserdatad" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Scheme/XCScheme+BuildableReferenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import XcodeProj 3 | 4 | final class XCSchemeBuildableReferenceTests: XCTestCase { 5 | func test_hash() throws { 6 | // Values that are equal must generate the same hash value 7 | let aBuildRef = XCScheme.BuildableReference( 8 | referencedContainer: "container ref", 9 | blueprint: nil, 10 | buildableName: "buildable name", 11 | blueprintName: "blueprint name" 12 | ) 13 | let bBuildRef = XCScheme.BuildableReference( 14 | referencedContainer: "container ref", 15 | blueprint: nil, 16 | buildableName: "buildable name", 17 | blueprintName: "blueprint name" 18 | ) 19 | XCTAssertEqual(aBuildRef, bBuildRef) 20 | 21 | let buildRefs: Set = [aBuildRef] 22 | XCTAssertTrue(buildRefs.contains(bBuildRef)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Tests/Fixtures.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PathKit 3 | @testable import XcodeProj 4 | 5 | func fixturesPath() -> Path { 6 | Path(#filePath).parent().parent().parent().parent() + "Fixtures" 7 | } 8 | 9 | func synchronizedRootGroupsFixture() throws -> Data { 10 | let synchronizedRootGroups = fixturesPath() + "SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj/project.pbxproj" 11 | return try Data(contentsOf: synchronizedRootGroups.url) 12 | } 13 | 14 | func iosProjectData() throws -> Data { 15 | let iosProject = fixturesPath() + "iOS/Project.xcodeproj/project.pbxproj" 16 | return try Data(contentsOf: iosProject.url) 17 | } 18 | 19 | func fileSharedAcrossTargetsData() throws -> Data { 20 | let fileSharedAcrossTargetsProject = fixturesPath() + "FileSharedAcrossTargets/FileSharedAcrossTargets.xcodeproj/project.pbxproj" 21 | return try Data(contentsOf: fileSharedAcrossTargetsProject.url) 22 | } 23 | 24 | func targetWithCustomBuildRulesData() throws -> Data { 25 | let targetWithCustomBuildRulesProject = fixturesPath() + "TargetWithCustomBuildRules/TargetWithCustomBuildRules.xcodeproj/project.pbxproj" 26 | return try Data(contentsOf: targetWithCustomBuildRulesProject.url) 27 | } 28 | 29 | func iosProjectWithXCLocalSwiftPackageReference() throws -> Data { 30 | let iosProjectWithXCLocalSwiftPackageReference = fixturesPath() + "iOS/ProjectWithXCLocalSwiftPackageReference.xcodeproj/project.pbxproj" 31 | return try Data(contentsOf: iosProjectWithXCLocalSwiftPackageReference.url) 32 | } 33 | 34 | func iosProjectWithRelativeXCLocalSwiftPackageReferences() throws -> Data { 35 | let iosProjectWithXCLocalSwiftPackageReference = fixturesPath() + "iOS/ProjectWithRelativeXCLocalSwiftPackageReference/ProjectWithRelativeXCLocalSwiftPackageReference.xcodeproj/project.pbxproj" 36 | return try Data(contentsOf: iosProjectWithXCLocalSwiftPackageReference.url) 37 | } 38 | 39 | func iosProjectWithXCLocalSwiftPackageReferences() throws -> Data { 40 | let iosProjectWithXCLocalSwiftPackageReference = fixturesPath() + "iOS/ProjectWithXCLocalSwiftPackageReferences.xcodeproj/project.pbxproj" 41 | return try Data(contentsOf: iosProjectWithXCLocalSwiftPackageReference.url) 42 | } 43 | 44 | func projectWithWrongProjectReferencesOrder() throws -> Data { 45 | let iosProjectWithProjectReferences = fixturesPath() + "Xcode16ProjectReferenceOrder/Wrong.xcodeproj/project.pbxproj" 46 | return try Data(contentsOf: iosProjectWithProjectReferences.url) 47 | } 48 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Utils/PlistValueTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PathKit 3 | import XCTest 4 | @testable import XcodeProj 5 | 6 | class Dictionary_PlistValueTests: XCTestCase { 7 | func test_dictionaryPlistValue_returnsTheCorrectValue() { 8 | let dictionary: [String: Any] = [ 9 | "a": "b", 10 | "c": ["d", "e"], 11 | "f": [ 12 | "g": "h", 13 | ], 14 | ] 15 | let plist = dictionary.plist() 16 | XCTAssertEqual(plist.dictionary?[CommentedString("a")], .string(CommentedString("b"))) 17 | XCTAssertEqual(plist.dictionary?[CommentedString("c")], .array([.string(CommentedString("d")), .string(CommentedString("e"))])) 18 | XCTAssertEqual(plist.dictionary?[CommentedString("f")], .dictionary([CommentedString("g"): .string(CommentedString("h"))])) 19 | } 20 | } 21 | 22 | class Array_PlistValueTests: XCTestCase { 23 | func test_arrayPlistValue_returnsTheCorrectValue() { 24 | let array: [Any] = [ 25 | "a", 26 | ["b", "c"], 27 | ["d": "e"], 28 | ] 29 | let plist = array.plist() 30 | XCTAssertEqual(plist.array?[0], .string(CommentedString("a"))) 31 | XCTAssertEqual(plist.array?[1], .array([.string(CommentedString("b")), .string(CommentedString("c"))])) 32 | XCTAssertEqual(plist.array?[2], .dictionary([CommentedString("d"): .string(CommentedString("e"))])) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Tests/XcodeProjTests/Workspace/XCWorkspaceTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PathKit 3 | import XcodeProj 4 | import XCTest 5 | 6 | final class XCWorkspaceIntegrationTests: XCTestCase { 7 | func test_initTheWorkspaceWithTheRightPropeties() { 8 | let path = fixturesPath() + "iOS/Project.xcodeproj/project.xcworkspace" 9 | let got = try? XCWorkspace(path: path) 10 | XCTAssertNotNil(got) 11 | } 12 | 13 | func test_initFailsIfThePathIsWrong() { 14 | do { 15 | _ = try XCWorkspace(path: Path("/test")) 16 | XCTAssertTrue(false, "Expected to throw an error but it didn't") 17 | } catch {} 18 | } 19 | 20 | func test_init_returnsAWorkspaceWithTheCorrectReference() { 21 | XCTAssertEqual(XCWorkspace().data.children.count, 1) 22 | XCTAssertEqual(XCWorkspace().data.children.first, .file(.init(location: .current("")))) 23 | } 24 | 25 | func test_equatable_emptyWorkspacesAreEqual() { 26 | // When 27 | let firstWorkspace = XCWorkspace(data: .init(children: [])) 28 | let secondWorkspace = XCWorkspace(data: .init(children: [])) 29 | 30 | // Then 31 | XCTAssertEqual(firstWorkspace, secondWorkspace) 32 | } 33 | 34 | func test_equatable_unEqualWorkspacesAreNotEqual() { 35 | // Given 36 | let pathOne = fixturesPath() + "iOS/Project.xcodeproj/project.xcworkspace" 37 | let pathTwo = fixturesPath() + "iOS/Workspace.xcworkspace" 38 | 39 | // When 40 | let firstWorkspace = try? XCWorkspace(path: pathOne) 41 | let secondWorkspace = try? XCWorkspace(path: pathTwo) 42 | 43 | // Then 44 | XCTAssertNotEqual(firstWorkspace, secondWorkspace) 45 | } 46 | 47 | func test_equatable_equalWorkspacesAreEqual() { 48 | // Given 49 | let pathOne = fixturesPath() + "iOS/Workspace.xcworkspace" 50 | let pathTwo = fixturesPath() + "iOS/Workspace.xcworkspace" 51 | 52 | // When 53 | let firstWorkspace = try? XCWorkspace(path: pathOne) 54 | let secondWorkspace = try? XCWorkspace(path: pathTwo) 55 | 56 | // Then 57 | XCTAssertEqual(firstWorkspace, secondWorkspace) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Tuist.swift: -------------------------------------------------------------------------------- 1 | import ProjectDescription 2 | 3 | let tuist = Tuist(generationOptions: .options()) 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | ":semanticCommits", 5 | ":disableDependencyDashboard", 6 | "config:recommended" 7 | ], 8 | "packageRules": [ 9 | { 10 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 11 | "automerge": true, 12 | "automergeType": "pr", 13 | "automergeStrategy": "auto", 14 | "semanticCommitScope": "deps" 15 | }, 16 | { 17 | "matchManagers": ["swift"], 18 | "semanticCommitScope": "spm" 19 | } 20 | ], 21 | "lockFileMaintenance": { 22 | "enabled": true, 23 | "automerge": true 24 | } 25 | } 26 | --------------------------------------------------------------------------------