├── .all-contributorsrc ├── .gitattributes ├── .github ├── changelog-configuration.json └── workflows │ ├── XcodeGraph.yml │ ├── conventional-pr.yml │ ├── deploy.yml │ ├── docs.yml │ └── release.yml ├── .gitignore ├── .mise.toml ├── .mise └── tasks │ ├── docs │ └── build │ ├── lint │ └── lint-fix ├── .swiftformat ├── .swiftlint.yml ├── BREAKME.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── Package.resolved ├── Package.swift ├── README.md ├── RELEASE.md ├── Sources ├── XcodeGraph │ ├── DependenciesGraph │ │ └── DependenciesGraph.swift │ ├── Extensions │ │ └── SettingsDictionary+Extras.swift │ ├── Graph │ │ ├── ConditionalGraphTarget.swift │ │ ├── Graph.swift │ │ ├── GraphDependency.swift │ │ ├── GraphEdge.swift │ │ └── GraphTarget.swift │ ├── Models │ │ ├── AnalyzeAction.swift │ │ ├── ArchiveAction.swift │ │ ├── Arguments.swift │ │ ├── AutogenerationOptions.swift │ │ ├── BinaryArchitecture.swift │ │ ├── BuildAction.swift │ │ ├── BuildConfiguration.swift │ │ ├── BuildRule+CompilerSpec.swift │ │ ├── BuildRule+FileType.swift │ │ ├── BuildRule.swift │ │ ├── CompatibleXcodeVersions.swift │ │ ├── CopyFileElement.swift │ │ ├── CopyFilesAction.swift │ │ ├── CoreDataModel.swift │ │ ├── DeploymentTargets.swift │ │ ├── Destination.swift │ │ ├── EnvironmentVariable.swift │ │ ├── ExecutionAction.swift │ │ ├── FileCodeGen.swift │ │ ├── FileElement.swift │ │ ├── Headers.swift │ │ ├── IDETemplateMacros.swift │ │ ├── LaunchArgument.swift │ │ ├── LaunchStyle.swift │ │ ├── MergedBinaryType.swift │ │ ├── Metadata │ │ │ ├── FrameworkMetadata.swift │ │ │ ├── LibraryMetadata.swift │ │ │ ├── SystemFrameworkMetadata.swift │ │ │ ├── TargetMetadata.swift │ │ │ └── XCFrameworkMetadata.swift │ │ ├── MetalOptions.swift │ │ ├── OnDemandResourcesTags.swift │ │ ├── Package.swift │ │ ├── Platform.swift │ │ ├── PlatformCondition.swift │ │ ├── PlatformFilter.swift │ │ ├── Plist.swift │ │ ├── PrivacyManifest.swift │ │ ├── Product.swift │ │ ├── ProfileAction.swift │ │ ├── Project.swift │ │ ├── ProjectGroup.swift │ │ ├── ProjectOptions.swift │ │ ├── RawScriptBuildPhase.swift │ │ ├── Requirement.swift │ │ ├── ResourceFileElement.swift │ │ ├── ResourceFileElements.swift │ │ ├── ResourceSynthesizer.swift │ │ ├── RunAction.swift │ │ ├── RunActionOptions.swift │ │ ├── SDKSource.swift │ │ ├── SDKType.swift │ │ ├── Scheme.swift │ │ ├── SchemeDiagnosticsOptions.swift │ │ ├── ScreenCaptureFormat.swift │ │ ├── Settings.swift │ │ ├── SimulatedLocation.swift │ │ ├── SourceFile.swift │ │ ├── SourceFileGlob.swift │ │ ├── Target.swift │ │ ├── TargetDependency.swift │ │ ├── TargetReference.swift │ │ ├── TargetScript.swift │ │ ├── TargetType.swift │ │ ├── TestAction.swift │ │ ├── TestPlan.swift │ │ ├── TestableTarget.swift │ │ ├── TestingOptions.swift │ │ ├── Version.swift │ │ ├── Workspace.swift │ │ ├── WorkspaceGenerationOptions.swift │ │ └── XCFrameworkInfoPlist.swift │ └── PackageInfo.swift ├── XcodeGraphMapper │ ├── Documentation.docc │ │ └── XcodeProjMapper.md │ ├── Extensions │ │ ├── PBXProject+Extensions.swift │ │ ├── Package+Extensions.swift │ │ ├── Platform+Extensions.swift │ │ ├── PlatformFilter+Extensions.swift │ │ ├── TargetDependency+Extensions.swift │ │ ├── XCWorkspace+Extensions.swift │ │ └── XCWorkspaceDataFileRef+Extensions.swift │ ├── Mappers │ │ ├── Graph │ │ │ └── XcodeGraphMapper.swift │ │ ├── Packages │ │ │ ├── PackageMapper.swift │ │ │ └── XCPackageMapper.swift │ │ ├── Phases │ │ │ ├── BuildPhaseConstants.swift │ │ │ ├── PBXCopyFilesBuildPhaseMapper.swift │ │ │ ├── PBXCoreDataModelsBuildPhaseMapper.swift │ │ │ ├── PBXFrameworksBuildPhaseMapper.swift │ │ │ ├── PBXHeadersBuildPhaseMapper.swift │ │ │ ├── PBXResourcesBuildPhaseMapper.swift │ │ │ ├── PBXScriptsBuildPhaseMapper.swift │ │ │ └── PBXSourcesBuildPhaseMapper.swift │ │ ├── Project │ │ │ ├── PBXProjectMapper.swift │ │ │ ├── ProjectAttribute.swift │ │ │ └── XcodeProj+Extensions.swift │ │ ├── Schemes │ │ │ ├── SchemeDiagnosticsOptions+XCScheme.swift │ │ │ ├── XCSchemeMapper.swift │ │ │ └── XCTestPlan.swift │ │ ├── Settings │ │ │ ├── BuildSettings.swift │ │ │ ├── XCConfigurationList+Helpers.swift │ │ │ └── XCConfigurationMapper.swift │ │ ├── Targets │ │ │ ├── PBXBuildRuleMapper.swift │ │ │ ├── PBXTarget+BuildHeaders.swift │ │ │ ├── PBXTarget+BuildSettings.swift │ │ │ ├── PBXTarget+GraphMapping.swift │ │ │ ├── PBXTarget+PlatformInference.swift │ │ │ ├── PBXTargetDependency+PlatformCondition.swift │ │ │ ├── PBXTargetDependencyMapper.swift │ │ │ ├── PBXTargetMapper.swift │ │ │ └── TargetDependency+GraphMapping.swift │ │ └── Workspace │ │ │ └── XCWorkspaceMapper.swift │ └── Utilities │ │ ├── ConfigurationMatcher.swift │ │ ├── DeveloperDirectoryProvider.swift │ │ ├── Optional+Throwing.swift │ │ ├── PackageInfoLoader.swift │ │ ├── PathDependencyMapper.swift │ │ └── ProjectNativeTarget.swift └── XcodeMetadata │ ├── Extensions │ ├── BinaryArchitecture+Extension.swift │ ├── FileHandle+ReadHelpers.swift │ ├── MachOByteSwapExtensions.swift │ └── Sequence+ExecutionContext.swift │ └── Providers │ ├── FrameworkMetadataProvider.swift │ ├── LibraryMetadataProvider.swift │ ├── PrecompiledMetadataProvider.swift │ ├── SystemFrameworkMetadataProvider.swift │ └── XCFrameworkMetadataProvider.swift ├── Tests ├── Fixtures │ ├── MyFramework.xcframework │ │ ├── Info.plist │ │ ├── ios-arm64 │ │ │ └── MyFramework.framework │ │ │ │ ├── Headers │ │ │ │ └── MyFramework-Swift.h │ │ │ │ ├── Info.plist │ │ │ │ ├── Modules │ │ │ │ ├── MyFramework.swiftmodule │ │ │ │ │ ├── arm64-apple-ios.swiftdoc │ │ │ │ │ ├── arm64-apple-ios.swiftinterface │ │ │ │ │ ├── arm64.swiftdoc │ │ │ │ │ └── arm64.swiftinterface │ │ │ │ └── module.modulemap │ │ │ │ └── MyFramework │ │ └── ios-x86_64-simulator │ │ │ └── MyFramework.framework │ │ │ ├── Headers │ │ │ └── MyFramework-Swift.h │ │ │ ├── Info.plist │ │ │ ├── Modules │ │ │ ├── MyFramework.swiftmodule │ │ │ │ ├── x86_64-apple-ios-simulator.swiftdoc │ │ │ │ ├── x86_64-apple-ios-simulator.swiftinterface │ │ │ │ ├── x86_64.swiftdoc │ │ │ │ └── x86_64.swiftinterface │ │ │ └── module.modulemap │ │ │ ├── MyFramework │ │ │ └── _CodeSignature │ │ │ └── CodeResources │ ├── MyFrameworkMissingArch.xcframework │ │ ├── Info.plist │ │ └── ios-arm64 │ │ │ └── MyFrameworkMissingArch.framework │ │ │ ├── Headers │ │ │ └── MyFramework-Swift.h │ │ │ ├── Info.plist │ │ │ ├── Modules │ │ │ ├── MyFramework.swiftmodule │ │ │ │ ├── arm64-apple-ios.swiftdoc │ │ │ │ ├── arm64-apple-ios.swiftinterface │ │ │ │ ├── arm64.swiftdoc │ │ │ │ └── arm64.swiftinterface │ │ │ └── module.modulemap │ │ │ └── MyFrameworkMissingArch │ ├── MyMergeableFramework.xcframework │ │ ├── Info.plist │ │ ├── ios-arm64 │ │ │ └── MyMergeableFramework.framework │ │ │ │ ├── Headers │ │ │ │ └── MyMergeableFramework-Swift.h │ │ │ │ ├── Info.plist │ │ │ │ ├── Modules │ │ │ │ ├── MyMergeableFramework.swiftmodule │ │ │ │ │ ├── arm64-apple-ios.abi.json │ │ │ │ │ ├── arm64-apple-ios.private.swiftinterface │ │ │ │ │ ├── arm64-apple-ios.swiftdoc │ │ │ │ │ ├── arm64-apple-ios.swiftinterface │ │ │ │ │ └── arm64-apple-ios.swiftmodule │ │ │ │ └── module.modulemap │ │ │ │ └── MyMergeableFramework │ │ └── ios-x86_64-simulator │ │ │ └── MyMergeableFramework.framework │ │ │ ├── Headers │ │ │ └── MyMergeableFramework-Swift.h │ │ │ ├── Info.plist │ │ │ ├── Modules │ │ │ ├── MyMergeableFramework.swiftmodule │ │ │ │ ├── x86_64-apple-ios-simulator.abi.json │ │ │ │ ├── x86_64-apple-ios-simulator.private.swiftinterface │ │ │ │ ├── x86_64-apple-ios-simulator.swiftdoc │ │ │ │ ├── x86_64-apple-ios-simulator.swiftinterface │ │ │ │ └── x86_64-apple-ios-simulator.swiftmodule │ │ │ └── module.modulemap │ │ │ ├── MyMergeableFramework │ │ │ └── _CodeSignature │ │ │ └── CodeResources │ ├── MyStaticLibrary.xcframework │ │ ├── Info.plist │ │ ├── ios-arm64 │ │ │ └── libMyStaticLibrary.a │ │ └── ios-x86_64-simulator │ │ │ └── libMyStaticLibrary.a │ ├── libStaticLibrary.a │ ├── xpm.framework.dSYM │ │ └── Contents │ │ │ ├── Info.plist │ │ │ └── Resources │ │ │ └── DWARF │ │ │ └── xpm │ └── xpm.framework │ │ ├── Headers │ │ └── xpm.h │ │ ├── Info.plist │ │ ├── Modules │ │ └── module.modulemap │ │ ├── PrivateHeaders │ │ └── private.h │ │ └── xpm ├── XcodeGraphMapperTests │ ├── MapperTests │ │ ├── Graph │ │ │ └── XcodeGraphMapperTests.swift │ │ ├── Package │ │ │ ├── PackageMapperTests.swift │ │ │ └── XCPackageMapperTests.swift │ │ ├── Phases │ │ │ ├── PBXCopyFilesBuildPhaseMapperTests.swift │ │ │ ├── PBXCoreDataModelsBuildPhaseMapperTests.swift │ │ │ ├── PBXFrameworksBuildPhaseMapperTests.swift │ │ │ ├── PBXHeadersBuildPhaseMapperTests.swift │ │ │ ├── PBXResourcesBuildPhaseMapperTests.swift │ │ │ ├── PBXScriptsBuildPhaseMapperTests.swift │ │ │ └── PBXSourcesBuildPhaseMapperTests.swift │ │ ├── Project │ │ │ └── PBXProjectMapperTests.swift │ │ ├── Schemes │ │ │ └── XCSchemeMapperTests.swift │ │ ├── Settings │ │ │ ├── ConfigurationMatcherTests.swift │ │ │ └── XCConfigurationMapperTests.swift │ │ ├── Target │ │ │ ├── PBXBuildRuleMapperTests.swift │ │ │ ├── PBXTargetDependencyMapper.swift │ │ │ ├── PBXTargetMapperTests.swift │ │ │ └── TargetDependencyExtensionsTests.swift │ │ └── Workspace │ │ │ └── XCWorkspaceMapperTests.swift │ ├── Mocks │ │ ├── AssertionsTesting.swift │ │ └── MockDefaults.swift │ └── TestData │ │ ├── .swift │ │ ├── PBXBuildRule+TestData.swift │ │ ├── PBXFileReference+TestData.swift │ │ ├── PBXGroup+TestData.swift │ │ ├── PBXNativeTarget+TestData.swift │ │ ├── PBXProj+TestData.swift │ │ ├── PBXProject+TestData.swift │ │ ├── PBXShellScriptBuildPhase+TestData.swift │ │ ├── PBXTargetDependency+TestData.swift │ │ ├── PBXVariantGroup+TestData.swift │ │ ├── XCBuildConfiguration+TestData.swift │ │ ├── XCConfigurationList+TestData.swift │ │ ├── XCScheme+TestData.swift │ │ ├── XCSchemeTestableReference+TestData.swift │ │ ├── XCUserData+TestData.swift │ │ ├── XCVersionGroup+TestData.swift │ │ ├── XCWorkspace+TestData.swift │ │ ├── XCWorkspaceDataElement+TestData.swift │ │ ├── XCWorkspaceDataGroup+TestData.swift │ │ └── XcodeProj+TestData.swift ├── XcodeGraphTests │ ├── DependenciesGraph │ │ └── DependenciesGraphTests.swift │ ├── Extensions │ │ ├── SettingsDictionary+ExtrasTests.swift │ │ └── XCTestCase+Extras.swift │ ├── Graph │ │ ├── GraphDependencyTests.swift │ │ ├── GraphTargetTests.swift │ │ └── GraphTests.swift │ └── Models │ │ ├── AnalyzeActionTests.swift │ │ ├── ArchiveActionTests.swift │ │ ├── ArgumentsTests.swift │ │ ├── BinaryArchitectureTests.swift │ │ ├── BuildActionTests.swift │ │ ├── BuildConfigurationTests.swift │ │ ├── BuildRule.CompilerSpecTests.swift │ │ ├── BuildRule.FileTypeTests.swift │ │ ├── BuildRuleTests.swift │ │ ├── CompatibleXcodeVersionsTests.swift │ │ ├── CopyFileElementTests.swift │ │ ├── CopyFilesActionTests.swift │ │ ├── CoreDataModelTests.swift │ │ ├── ExecutionActionTests.swift │ │ ├── FileElementTests.swift │ │ ├── HeadersTests.swift │ │ ├── IDETemplateMacrosTests.swift │ │ ├── InfoPlistTests.swift │ │ ├── PackageInfoTests.swift │ │ ├── PackageTests.swift │ │ ├── PlatformFilterTests.swift │ │ ├── PlatformTests.swift │ │ ├── ProductTests.swift │ │ ├── ProfileActionTests.swift │ │ ├── ProjectGroupTests.swift │ │ ├── ProjectTests.swift │ │ ├── RawScriptBuildPhaseTests.swift │ │ ├── RequirementTests.swift │ │ ├── ResourceFileElementTests.swift │ │ ├── ResourceSynthesizerTests.swift │ │ ├── RunActionTests.swift │ │ ├── SDKSourceTests.swift │ │ ├── SchemeTests.swift │ │ ├── SettingsTests.swift │ │ ├── SourceFileTests.swift │ │ ├── TargetDependencyTests.swift │ │ ├── TargetReferenceTests.swift │ │ ├── TargetScriptTests.swift │ │ ├── TargetTests.swift │ │ ├── TestActionTests.swift │ │ ├── TestPlanTests.swift │ │ ├── TestableTargetTests.swift │ │ ├── VersionTests.swift │ │ ├── WorkspaceGenerationOptionsTests.swift │ │ ├── WorkspaceTests.swift │ │ └── XCFrameworkInfoPlistTests.swift └── XcodeMetadataTests │ ├── AssertionsTesting.swift │ ├── FrameworkMetadataProviderTests.swift │ ├── LibraryMetadataProviderTests.swift │ ├── PrecompiledMetadataProviderTests.swift │ ├── SystemFrameworkMetadataProviderTests.swift │ └── XCFrameworkMetadataProviderTests.swift ├── Tuist.swift ├── Tuist └── ProjectDescriptionHelpers │ └── Module.swift ├── Workspace.swift ├── cliff.toml └── renovate.json /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "commitType": "docs", 8 | "commitConvention": "angular", 9 | "contributors": [ 10 | { 11 | "login": "darrarski", 12 | "name": "Dariusz Rybicki", 13 | "avatar_url": "https://avatars.githubusercontent.com/u/1384684?v=4", 14 | "profile": "http://darrarski.pl", 15 | "contributions": [ 16 | "code" 17 | ] 18 | }, 19 | { 20 | "login": "Binlogo", 21 | "name": "Binlogo", 22 | "avatar_url": "https://avatars.githubusercontent.com/u/7845507?v=4", 23 | "profile": "https://github.com/Binlogo", 24 | "contributions": [ 25 | "code" 26 | ] 27 | }, 28 | { 29 | "login": "filipracki", 30 | "name": "Filip Racki", 31 | "avatar_url": "https://avatars.githubusercontent.com/u/27164368?v=4", 32 | "profile": "https://github.com/filipracki", 33 | "contributions": [ 34 | "code" 35 | ] 36 | }, 37 | { 38 | "login": "rgnns", 39 | "name": "Gabriel Liévano", 40 | "avatar_url": "https://avatars.githubusercontent.com/u/811827?v=4", 41 | "profile": "https://github.com/rgnns", 42 | "contributions": [ 43 | "code" 44 | ] 45 | }, 46 | { 47 | "login": "fila95", 48 | "name": "Giovanni Filaferro", 49 | "avatar_url": "https://avatars.githubusercontent.com/u/7265334?v=4", 50 | "profile": "https://github.com/fila95", 51 | "contributions": [ 52 | "code" 53 | ] 54 | }, 55 | { 56 | "login": "Garfeild", 57 | "name": "Anton Kolchunov", 58 | "avatar_url": "https://avatars.githubusercontent.com/u/12799?v=4", 59 | "profile": "https://github.com/Garfeild", 60 | "contributions": [ 61 | "code" 62 | ] 63 | }, 64 | { 65 | "login": "alexmx", 66 | "name": "Alex Maimescu", 67 | "avatar_url": "https://avatars.githubusercontent.com/u/1628901?v=4", 68 | "profile": "https://github.com/alexmx", 69 | "contributions": [ 70 | "code" 71 | ] 72 | } 73 | ], 74 | "contributorsPerLine": 7, 75 | "skipCi": true, 76 | "repoType": "github", 77 | "repoHost": "https://github.com", 78 | "projectName": "XcodeGraph", 79 | "projectOwner": "tuist" 80 | } 81 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | fixtures/* binary 2 | -------------------------------------------------------------------------------- /.github/changelog-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": [ 3 | { 4 | "title": "#### Changed", 5 | "labels": ["changelog:changed"] 6 | }, 7 | { 8 | "title": "#### Added", 9 | "labels": ["changelog:added"] 10 | }, 11 | { 12 | "title": "#### Fixed", 13 | "labels": ["changelog:fixed"] 14 | }, 15 | { 16 | "title": "#### Updated dependencies", 17 | "labels": ["changelog:updated-dependencies"] 18 | } 19 | ], 20 | "pr_template": "- ${{TITLE}} [#${{NUMBER}}](https://github.com/tuist/XcodeGraph/pull/${{NUMBER}}) by [@${{AUTHOR}}](https://github.com/${{AUTHOR}})" 21 | } -------------------------------------------------------------------------------- /.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@v4 16 | - uses: CondeNast/conventional-pull-request-action@v0.2.0 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: read 11 | deployments: write 12 | name: Publish to Cloudflare Pages 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | - uses: jdx/mise-action@v2 17 | with: 18 | experimental: true 19 | - name: Setup Swift 20 | uses: SwiftyLab/setup-swift@latest 21 | with: 22 | swift-version: 6.0.3 23 | - name: Build docs 24 | run: mise run docs:build 25 | - name: Deploy 26 | uses: cloudflare/wrangler-action@v3 27 | with: 28 | apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} 29 | accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} 30 | command: pages deploy .build/documentation --project-name=xcodegraph 31 | gitHubToken: ${{ secrets.GITHUB_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | merge_group: 9 | 10 | concurrency: 11 | group: docs-${{ github.head_ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | build: 16 | name: Build 17 | runs-on: "ubuntu-latest" 18 | timeout-minutes: 15 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: jdx/mise-action@v2 22 | with: 23 | experimental: true 24 | - name: Setup Swift 25 | uses: SwiftyLab/setup-swift@latest 26 | with: 27 | swift-version: 6.0.3 28 | - name: Build docs 29 | run: mise run docs:build 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/swift,macos 3 | 4 | ### macOS ### 5 | *.DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | .com.apple.timemachine.donotpresent 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | ### Swift ### 32 | # Xcode 33 | # 34 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 35 | 36 | ## Build generated 37 | DerivedData/ 38 | 39 | ## Various settings 40 | *.pbxuser 41 | !default.pbxuser 42 | *.mode1v3 43 | !default.mode1v3 44 | *.mode2v3 45 | !default.mode2v3 46 | *.perspectivev3 47 | !default.perspectivev3 48 | xcuserdata/ 49 | 50 | ## Other 51 | *.moved-aside 52 | *.xccheckout 53 | *.xcscmblueprint 54 | 55 | ## Obj-C/Swift specific 56 | *.hmap 57 | *.ipa 58 | 59 | ## Playgrounds 60 | timeline.xctimeline 61 | playground.xcworkspace 62 | 63 | # Swift Package Manager 64 | # 65 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 66 | # Packages/ 67 | # Package.pins 68 | .build/ 69 | .swiftpm 70 | 71 | # CocoaPods - Refactored to standalone file 72 | 73 | # Carthage - Refactored to standalone file 74 | 75 | # End of https://www.gitignore.io/api/swift,macos 76 | 77 | **/*.xcodeproj 78 | **/*.xcworkspace 79 | .byebug_history 80 | *.xcarchive 81 | tmp/ 82 | /build/ 83 | 84 | .env 85 | .idea/ 86 | /Carthage/Checkouts 87 | *.profraw 88 | tuist.zip 89 | TODO 90 | TODO.md 91 | mkmf.log 92 | .rubocop-http---shopify-github-io-ruby-style-guide-rubocop-yml 93 | generated_fixtures 94 | .fixtures.generated.json 95 | **/Derived/ 96 | XcodeGraph.xcworkspace 97 | node_modules 98 | Fixture 99 | 100 | Tuist/Dependencies/SwiftPackageManager 101 | Tuist/Dependencies/graph.json 102 | 103 | # VSCode Settings 104 | .vscode/launch.json 105 | .vscode 106 | 107 | # Release artifacts 108 | .bundle -------------------------------------------------------------------------------- /.mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | tuist = "4.50.2" 3 | swiftlint = "0.54.0" 4 | swiftformat = "0.53.3" 5 | "git-cliff" = "2.6.1" 6 | -------------------------------------------------------------------------------- /.mise/tasks/docs/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # mise description="Build docs" 3 | set -euo pipefail 4 | 5 | swift package --package-path $MISE_PROJECT_ROOT --allow-writing-to-directory .build/documentation generate-documentation --disable-indexing --output-path .build/documentation --transform-for-static-hosting --enable-experimental-combined-documentation --target XcodeGraph --target XcodeGraphMapper 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | # file options 2 | 3 | --symlinks ignore 4 | --exclude Tests/XCTestManifests.swift,Sources/TuistSupport/Vendored,fixtures/Targets/SlothCreator,Sources/TuistAutomation/XcodeBuild/XcodeBuildController.swift 5 | --exclude fixtures/tuist_plugin,Sources/TuistServer/OpenAPI 6 | --disable conditionalAssignment 7 | --disable hoistAwait 8 | --disable hoistTry 9 | --disable redundantReturn 10 | --swiftversion 5.10 11 | --minversion 0.53.0 12 | 13 | # format options 14 | 15 | --allman false 16 | --binarygrouping 4,8 17 | --closingparen balanced 18 | --commas always 19 | --comments indent 20 | --decimalgrouping 3,6 21 | --elseposition same-line 22 | --empty void 23 | --exponentcase lowercase 24 | --exponentgrouping disabled 25 | --extensionacl on-declarations 26 | --fractiongrouping disabled 27 | --header strip 28 | --hexgrouping 4,8 29 | --hexliteralcase uppercase 30 | --ifdef indent 31 | --indent 4 32 | --indentcase false 33 | --importgrouping testable-bottom 34 | --linebreaks lf 35 | --octalgrouping 4,8 36 | --operatorfunc spaced 37 | --patternlet hoist 38 | --ranges spaced 39 | --self remove 40 | --semicolons inline 41 | --stripunusedargs always 42 | --trimwhitespace always 43 | --maxwidth 130 44 | --wraparguments before-first 45 | --wrapcollections before-first 46 | --wrapconditions after-first 47 | --wrapparameters before-first 48 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - trailing_whitespace 3 | - trailing_comma 4 | - nesting 5 | - cyclomatic_complexity 6 | - file_length 7 | - todo 8 | - function_parameter_count 9 | - opening_brace 10 | - line_length 11 | - large_tuple 12 | identifier_name: 13 | min_length: 14 | error: 1 15 | warning: 1 16 | max_length: 17 | warning: 60 18 | error: 80 19 | inclusive_language: 20 | override_allowed_terms: 21 | - masterKey 22 | type_name: 23 | min_length: 24 | error: 1 25 | warning: 1 26 | custom_rules: 27 | error_must_conform_to_localizederror: 28 | name: "Error must conform to LocalizedError" 29 | regex: '(struct|class|enum)\s+\w+Error:\s*Error\s*(?!,\s*LocalizedError)' 30 | message: "Errors must conform to LocalizedError to provide user-friendly descriptions." 31 | severity: error 32 | -------------------------------------------------------------------------------- /BREAKME.md: -------------------------------------------------------------------------------- 1 | # BREAKME 2 | 3 | This document lists breaking changes to introduce in the next major version of the project. 4 | Every change must include what needs to be changed, and the rationale behind it. 5 | 6 | ## Changes 7 | 8 | ## Drop `TargetMetadata.init` 9 | 10 | We added the `TargetMetadata.metadata` API that gives us more flexibility and makes the instantiation of metadata more idiomatic. Therefore, we should remove `TargetMetadata.init`. -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Tuist GmbH 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. -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release 2 | 3 | This document describes the release process for the project. 4 | 5 | 1. Determine the next version number based on the changes since the last release. 6 | 2. Create a branch named `vX.Y.Z` where `X.Y.Z` is the version number. Tag the HEAD of the branch with the version number. 7 | 3. Push the changes upstream including the tags by running `git push origin vX.Y.Z --tags`. 8 | 4. Once continuous integration passes, merge the branch, and create the release through GitHub's UI generating the release notes automatically. -------------------------------------------------------------------------------- /Sources/XcodeGraph/Extensions/SettingsDictionary+Extras.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension SettingsDictionary { 4 | /// Overlays a SettingsDictionary by adding a `[sdk=*]` qualifier 5 | /// e.g. for a multiplatform target 6 | /// `LD_RUNPATH_SEARCH_PATHS = @executable_path/Frameworks` 7 | /// `LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = @executable_path/../Frameworks` 8 | public mutating func overlay( 9 | with other: SettingsDictionary, 10 | for platform: Platform 11 | ) { 12 | for (key, newValue) in other where self[key] != newValue { 13 | let newKey = "\(key)[sdk=\(platform.xcodeSdkRoot)*]" 14 | self[newKey] = newValue 15 | if platform.hasSimulators, let simulatorSDK = platform.xcodeSimulatorSDK { 16 | let newKey = "\(key)[sdk=\(simulatorSDK)*]" 17 | self[newKey] = newValue 18 | } 19 | } 20 | } 21 | 22 | /// Combines two `SettingsDictionary`. Instead of overriding values for a duplicate key, it combines them. 23 | public func combine(with settings: SettingsDictionary) -> SettingsDictionary { 24 | merging(settings, uniquingKeysWith: { oldValue, newValue in 25 | let newValues: [String] 26 | switch newValue { 27 | case let .string(value): 28 | newValues = [value] 29 | case let .array(values): 30 | newValues = values 31 | } 32 | switch oldValue { 33 | case let .array(values): 34 | return .array(values + newValues) 35 | case let .string(value): 36 | return .array(value.split(separator: " ").map(String.init) + newValues) 37 | } 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Graph/ConditionalGraphTarget.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | public struct GraphTargetReference: Equatable, Comparable, Hashable, CustomDebugStringConvertible, CustomStringConvertible, 5 | Codable 6 | { 7 | /// Path to the directory that contains the project where the target is defined. 8 | public let graphTarget: GraphTarget 9 | 10 | public var target: Target { graphTarget.target } 11 | 12 | /// Platforms the target is conditionally deployed to. 13 | public let condition: PlatformCondition? 14 | 15 | public init(target: GraphTarget, condition: PlatformCondition? = nil) { 16 | graphTarget = target 17 | self.condition = condition 18 | } 19 | 20 | public static func < (lhs: GraphTargetReference, rhs: GraphTargetReference) -> Bool { 21 | lhs.graphTarget < rhs.graphTarget 22 | // guard let 23 | // return (lhs.condition, lhs.graphTarget) < (rhs.condtion, rhs.graphTarget) 24 | // 25 | // 26 | // switch (lhs.condition, rhs.condition) { 27 | // case (let lhsCondtion, let rhsCondtion): 28 | // return (lhsCondition, lhs.graphTarget) < (rhsCondtion, rhs.graphTarget) 29 | // case 30 | // } 31 | } 32 | 33 | // MARK: - CustomDebugStringConvertible/CustomStringConvertible 34 | 35 | public var debugDescription: String { 36 | description 37 | } 38 | 39 | public var description: String { 40 | "Target '\(target.name)' at path '\(graphTarget.project.path)'" 41 | } 42 | } 43 | 44 | // 45 | // extension GraphTarget { 46 | // public func reference(_ condition: PlatformCondition?) -> GraphTargetReference { 47 | // return GraphTargetReference(target: self, condition: condition) 48 | // } 49 | // } 50 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Graph/GraphEdge.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A directed edge linking representing a dependent relationship 4 | /// e.g. `from` (MainApp) depends on `to` (UIKit) 5 | public struct GraphEdge: Hashable, Codable, Sendable { 6 | public let from: GraphDependency 7 | public let to: GraphDependency 8 | public init(from: GraphDependency, to: GraphDependency) { 9 | self.from = from 10 | self.to = to 11 | } 12 | 13 | public init(from: GraphDependency, to: GraphTarget) { 14 | self.from = from 15 | self.to = .target(name: to.target.name, path: to.path) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Graph/GraphTarget.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | public struct GraphTarget: Equatable, Hashable, Comparable, CustomDebugStringConvertible, CustomStringConvertible, Codable { 5 | /// Path to the directory that contains the project where the target is defined. 6 | public let path: AbsolutePath 7 | 8 | /// Target representation. 9 | public let target: Target 10 | 11 | /// Project that contains the target. 12 | public let project: Project 13 | 14 | public init(path: AbsolutePath, target: Target, project: Project) { 15 | self.path = path 16 | self.target = target 17 | self.project = project 18 | } 19 | 20 | public static func < (lhs: GraphTarget, rhs: GraphTarget) -> Bool { 21 | (lhs.path, lhs.target) < (rhs.path, rhs.target) 22 | } 23 | 24 | // MARK: - CustomDebugStringConvertible/CustomStringConvertible 25 | 26 | public var debugDescription: String { 27 | description 28 | } 29 | 30 | public var description: String { 31 | "Target '\(target.name)' at path '\(project.path)'" 32 | } 33 | } 34 | 35 | #if DEBUG 36 | extension GraphTarget { 37 | public static func test( 38 | path: AbsolutePath = .root, 39 | target: Target = .test(), 40 | project: Project = .test(type: .local) 41 | ) -> GraphTarget { 42 | GraphTarget( 43 | path: path, 44 | target: target, 45 | project: project 46 | ) 47 | } 48 | } 49 | #endif 50 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/AnalyzeAction.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct AnalyzeAction: Equatable, Codable, Sendable { 4 | // MARK: - Attributes 5 | 6 | public let configurationName: String 7 | 8 | // MARK: - Init 9 | 10 | public init(configurationName: String) { 11 | self.configurationName = configurationName 12 | } 13 | } 14 | 15 | #if DEBUG 16 | extension AnalyzeAction { 17 | public static func test(configurationName: String = "Beta Release") -> AnalyzeAction { 18 | AnalyzeAction(configurationName: configurationName) 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/ArchiveAction.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | public struct ArchiveAction: Equatable, Codable, Sendable { 5 | // MARK: - Attributes 6 | 7 | public let configurationName: String 8 | public let revealArchiveInOrganizer: Bool 9 | public let customArchiveName: String? 10 | public let preActions: [ExecutionAction] 11 | public let postActions: [ExecutionAction] 12 | 13 | // MARK: - Init 14 | 15 | public init( 16 | configurationName: String, 17 | revealArchiveInOrganizer: Bool = true, 18 | customArchiveName: String? = nil, 19 | preActions: [ExecutionAction] = [], 20 | postActions: [ExecutionAction] = [] 21 | ) { 22 | self.configurationName = configurationName 23 | self.revealArchiveInOrganizer = revealArchiveInOrganizer 24 | self.customArchiveName = customArchiveName 25 | self.preActions = preActions 26 | self.postActions = postActions 27 | } 28 | } 29 | 30 | #if DEBUG 31 | extension ArchiveAction { 32 | public static func test( 33 | configurationName: String = "Beta Release", 34 | revealArchiveInOrganizer: Bool = true, 35 | customArchiveName: String? = nil, 36 | preActions: [ExecutionAction] = [], 37 | postActions: [ExecutionAction] = [] 38 | ) -> ArchiveAction { 39 | ArchiveAction( 40 | configurationName: configurationName, 41 | revealArchiveInOrganizer: revealArchiveInOrganizer, 42 | customArchiveName: customArchiveName, 43 | preActions: preActions, 44 | postActions: postActions 45 | ) 46 | } 47 | } 48 | #endif 49 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/Arguments.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Arguments contain commandline arguments passed on launch and Environment variables. 4 | public struct Arguments: Codable, Sendable { 5 | // MARK: - Attributes 6 | 7 | /// Launch arguments that are passed by the scheme when running a scheme action. 8 | public let launchArguments: [LaunchArgument] 9 | /// The environment variables that are passed by the scheme when running a scheme action. 10 | public let environmentVariables: [String: EnvironmentVariable] 11 | 12 | // MARK: - Init 13 | 14 | public init( 15 | environmentVariables: [String: EnvironmentVariable] = [:], 16 | launchArguments: [LaunchArgument] = [] 17 | ) { 18 | self.environmentVariables = environmentVariables 19 | self.launchArguments = launchArguments 20 | } 21 | } 22 | 23 | extension Arguments: Equatable { 24 | /// Implement `Equatable` manually so order of arguments doesn't matter. 25 | public static func == (lhs: Arguments, rhs: Arguments) -> Bool { 26 | lhs.environmentVariables == rhs.environmentVariables 27 | && lhs.launchArguments.sorted { $0.name < $1.name } 28 | == rhs.launchArguments.sorted { $0.name == $1.name } 29 | } 30 | } 31 | 32 | #if DEBUG 33 | extension Arguments { 34 | public static func test( 35 | environmentVariables: [String: EnvironmentVariable] = [:], 36 | launchArguments: [LaunchArgument] = [] 37 | ) -> Arguments { 38 | Arguments( 39 | environmentVariables: environmentVariables, 40 | launchArguments: launchArguments 41 | ) 42 | } 43 | } 44 | #endif 45 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/AutogenerationOptions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum AutogenerationOptions: Hashable { 4 | case disabled 5 | case enabled(TestingOptions) 6 | } 7 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/BinaryArchitecture.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum BinaryArchitecture: String, CaseIterable, Codable, Sendable { 4 | case x8664 = "x86_64" 5 | case i386 6 | case armv7 7 | case armv7s 8 | case arm64 9 | case armv7k 10 | case arm6432 = "arm64_32" 11 | case arm64e 12 | } 13 | 14 | public enum BinaryLinking: String, Hashable, Codable, Sendable { 15 | case `static`, dynamic 16 | } 17 | 18 | extension Sequence { 19 | /// Returns true if all the architectures are only for simulator. 20 | public var onlySimulator: Bool { 21 | let simulatorArchitectures: [BinaryArchitecture] = [.x8664, .i386, .arm64] 22 | return allSatisfy { simulatorArchitectures.contains($0) } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/BuildAction.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | public struct BuildAction: Equatable, Codable, Sendable { 5 | // MARK: - Attributes 6 | 7 | public var targets: [TargetReference] 8 | public var preActions: [ExecutionAction] 9 | public var postActions: [ExecutionAction] 10 | public var parallelizeBuild: Bool 11 | public var runPostActionsOnFailure: Bool 12 | public var findImplicitDependencies: Bool 13 | 14 | // MARK: - Init 15 | 16 | public init( 17 | targets: [TargetReference] = [], 18 | preActions: [ExecutionAction] = [], 19 | postActions: [ExecutionAction] = [], 20 | parallelizeBuild: Bool = true, 21 | runPostActionsOnFailure: Bool = false, 22 | findImplicitDependencies: Bool = true 23 | ) { 24 | self.targets = targets 25 | self.preActions = preActions 26 | self.postActions = postActions 27 | self.parallelizeBuild = parallelizeBuild 28 | self.runPostActionsOnFailure = runPostActionsOnFailure 29 | self.findImplicitDependencies = findImplicitDependencies 30 | } 31 | } 32 | 33 | #if DEBUG 34 | extension BuildAction { 35 | public static func test( 36 | // swiftlint:disable:next force_try 37 | targets: [TargetReference] = [TargetReference(projectPath: try! AbsolutePath(validating: "/Project"), name: "App")], 38 | preActions: [ExecutionAction] = [], 39 | postActions: [ExecutionAction] = [] 40 | ) -> BuildAction { 41 | BuildAction(targets: targets, preActions: preActions, postActions: postActions) 42 | } 43 | } 44 | #endif 45 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/BuildConfiguration.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A build configuration acts as a configuration identifier. 4 | /// 5 | /// 6 | /// 7 | /// It hosts the name as well as the variant of 8 | /// a configuration to help infer the appropriate 9 | /// default settings. 10 | public struct BuildConfiguration: Codable, Sendable { 11 | public enum Variant: String, Codable, Sendable { 12 | case debug, release 13 | } 14 | 15 | public static let debug = BuildConfiguration(name: "Debug", variant: .debug) 16 | public static let release = BuildConfiguration(name: "Release", variant: .release) 17 | 18 | public let name: String 19 | public let variant: Variant 20 | 21 | public init(name: String, variant: Variant) { 22 | self.name = name 23 | self.variant = variant 24 | } 25 | 26 | public static func release(_ name: String) -> BuildConfiguration { 27 | BuildConfiguration(name: name, variant: .release) 28 | } 29 | 30 | public static func debug(_ name: String) -> BuildConfiguration { 31 | BuildConfiguration(name: name, variant: .debug) 32 | } 33 | } 34 | 35 | extension BuildConfiguration: Equatable { 36 | public static func == (lhs: BuildConfiguration, rhs: BuildConfiguration) -> Bool { 37 | lhs.name.caseInsensitiveCompare(rhs.name) == .orderedSame && lhs.variant == rhs.variant 38 | } 39 | } 40 | 41 | extension BuildConfiguration: Hashable { 42 | public func hash(into hasher: inout Hasher) { 43 | hasher.combine(name.lowercased()) 44 | hasher.combine(variant) 45 | } 46 | } 47 | 48 | extension BuildConfiguration: Comparable { 49 | public static func < (lhs: BuildConfiguration, rhs: BuildConfiguration) -> Bool { 50 | lhs.name < rhs.name 51 | } 52 | } 53 | 54 | extension BuildConfiguration: CustomStringConvertible { 55 | public var description: String { 56 | "\(name) (\(variant.rawValue))" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/BuildRule.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A BuildRule is used to specify a method for transforming an input file in to an output file(s). 4 | public struct BuildRule: Codable, Equatable, Sendable { 5 | /// Element compiler spec. 6 | public let compilerSpec: CompilerSpec 7 | 8 | /// Element file patterns. 9 | public let filePatterns: String? 10 | 11 | /// Element file type. 12 | public let fileType: FileType 13 | 14 | /// Element name. 15 | public let name: String? 16 | 17 | /// Element output files. 18 | public let outputFiles: [String] 19 | 20 | /// Element input files. 21 | public let inputFiles: [String]? 22 | 23 | /// Element output files compiler flags. 24 | public let outputFilesCompilerFlags: [String]? 25 | 26 | /// Element script. 27 | public let script: String? 28 | 29 | /// Element run once per architecture. 30 | public let runOncePerArchitecture: Bool? 31 | 32 | public init( 33 | compilerSpec: CompilerSpec, 34 | fileType: FileType, 35 | filePatterns: String? = nil, 36 | name: String? = nil, 37 | outputFiles: [String] = [], 38 | inputFiles: [String]? = nil, 39 | outputFilesCompilerFlags: [String]? = nil, 40 | script: String? = nil, 41 | runOncePerArchitecture: Bool? = nil 42 | ) { 43 | self.compilerSpec = compilerSpec 44 | self.filePatterns = filePatterns 45 | self.fileType = fileType 46 | self.name = name 47 | self.outputFiles = outputFiles 48 | self.inputFiles = inputFiles 49 | self.outputFilesCompilerFlags = outputFilesCompilerFlags 50 | self.script = script 51 | self.runOncePerArchitecture = runOncePerArchitecture 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/CopyFileElement.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | public enum CopyFileElement: Equatable, Hashable, Codable, Sendable { 5 | case file(path: AbsolutePath, condition: PlatformCondition? = nil, codeSignOnCopy: Bool = false) 6 | case folderReference(path: AbsolutePath, condition: PlatformCondition? = nil, codeSignOnCopy: Bool = false) 7 | 8 | public var path: AbsolutePath { 9 | switch self { 10 | case let .file(path, _, _): 11 | return path 12 | case let .folderReference(path, _, _): 13 | return path 14 | } 15 | } 16 | 17 | public var isReference: Bool { 18 | switch self { 19 | case .file: 20 | return false 21 | case .folderReference: 22 | return true 23 | } 24 | } 25 | 26 | public var condition: PlatformCondition? { 27 | switch self { 28 | case let .file(_, condition, _), let .folderReference(_, condition, _): 29 | return condition 30 | } 31 | } 32 | 33 | public var codeSignOnCopy: Bool { 34 | switch self { 35 | case let .file(_, _, codeSignOnCopy), let .folderReference(_, _, codeSignOnCopy): 36 | return codeSignOnCopy 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/CopyFilesAction.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | public struct CopyFilesAction: Equatable, Codable, Sendable { 5 | /// Name of the build phase when the project gets generated. 6 | public var name: String 7 | 8 | /// Destination to copy files to. 9 | public var destination: Destination 10 | 11 | /// Path to a folder inside the destination. 12 | public var subpath: String? 13 | 14 | /// Relative paths to the files to be copied. 15 | public var files: [CopyFileElement] 16 | 17 | /// Destination path. 18 | public enum Destination: String, Equatable, Codable, Sendable { 19 | case absolutePath 20 | case productsDirectory 21 | case wrapper 22 | case executables 23 | case resources 24 | case javaResources 25 | case frameworks 26 | case sharedFrameworks 27 | case sharedSupport 28 | case plugins 29 | case other 30 | } 31 | 32 | public init( 33 | name: String, 34 | destination: Destination, 35 | subpath: String? = nil, 36 | files: [CopyFileElement] 37 | ) { 38 | self.name = name 39 | self.destination = destination 40 | self.subpath = subpath 41 | self.files = files 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/CoreDataModel.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | /// Represents a Core Data model 5 | public struct CoreDataModel: Equatable, Codable, Sendable { 6 | // MARK: - Attributes 7 | 8 | /// Relative path to the Core Data model. 9 | public let path: AbsolutePath 10 | 11 | /// Paths to the versions. 12 | public let versions: [AbsolutePath] 13 | 14 | /// Current version without the extension. 15 | public let currentVersion: String 16 | 17 | // MARK: - Init 18 | 19 | public init( 20 | path: AbsolutePath, 21 | versions: [AbsolutePath], 22 | currentVersion: String 23 | ) { 24 | self.path = path 25 | self.versions = versions 26 | self.currentVersion = currentVersion 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/DeploymentTargets.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MARK: - DeploymentTargets 4 | 5 | public struct DeploymentTargets: Hashable, Codable, Sendable { 6 | public let iOS: String? 7 | public let macOS: String? 8 | public let watchOS: String? 9 | public let tvOS: String? 10 | public let visionOS: String? 11 | 12 | public init(iOS: String? = nil, macOS: String? = nil, watchOS: String? = nil, tvOS: String? = nil, visionOS: String? = nil) { 13 | self.iOS = iOS 14 | self.macOS = macOS 15 | self.watchOS = watchOS 16 | self.tvOS = tvOS 17 | self.visionOS = visionOS 18 | } 19 | 20 | public subscript(platform: Platform) -> String? { 21 | switch platform { 22 | case .iOS: 23 | return iOS 24 | case .macOS: 25 | return macOS 26 | case .watchOS: 27 | return watchOS 28 | case .tvOS: 29 | return tvOS 30 | case .visionOS: 31 | return visionOS 32 | } 33 | } 34 | 35 | public var configuredVersions: [(platform: Platform, versionString: String)] { 36 | var versions = [(Platform, String)]() 37 | 38 | if let iOS { 39 | versions.append((.iOS, iOS)) 40 | } 41 | 42 | if let macOS { 43 | versions.append((.macOS, macOS)) 44 | } 45 | 46 | if let watchOS { 47 | versions.append((.watchOS, watchOS)) 48 | } 49 | 50 | if let tvOS { 51 | versions.append((.tvOS, tvOS)) 52 | } 53 | 54 | if let visionOS { 55 | versions.append((.visionOS, visionOS)) 56 | } 57 | 58 | return versions 59 | } 60 | 61 | public static func iOS(_ version: String) -> DeploymentTargets { 62 | DeploymentTargets(iOS: version) 63 | } 64 | 65 | public static func macOS(_ version: String) -> DeploymentTargets { 66 | DeploymentTargets(macOS: version) 67 | } 68 | 69 | public static func watchOS(_ version: String) -> DeploymentTargets { 70 | DeploymentTargets(watchOS: version) 71 | } 72 | 73 | public static func tvOS(_ version: String) -> DeploymentTargets { 74 | DeploymentTargets(tvOS: version) 75 | } 76 | 77 | public static func visionOS(_ version: String) -> DeploymentTargets { 78 | DeploymentTargets(visionOS: version) 79 | } 80 | 81 | public static func empty() -> DeploymentTargets { 82 | DeploymentTargets() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/Destination.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public typealias Destinations = Set 4 | 5 | extension Destinations { 6 | public static let watchOS: Destinations = [.appleWatch] 7 | public static let iOS: Destinations = [.iPhone, .iPad, .macWithiPadDesign] 8 | public static let macOS: Destinations = [.mac] 9 | public static let tvOS: Destinations = [.appleTv] 10 | public static let visionOS: Destinations = [.appleVision] 11 | } 12 | 13 | extension Destinations { 14 | public var platforms: Set { 15 | let platforms = map(\.platform) 16 | return Set(platforms) 17 | } 18 | } 19 | 20 | /// A supported platform representation. 21 | public enum Destination: String, Codable, Equatable, CaseIterable, Sendable { 22 | case iPhone 23 | case iPad 24 | case mac 25 | case macWithiPadDesign 26 | case macCatalyst 27 | case appleWatch 28 | case appleTv 29 | case appleVision 30 | case appleVisionWithiPadDesign 31 | 32 | public var platform: Platform { 33 | switch self { 34 | case .iPad, .iPhone, .macCatalyst, .macWithiPadDesign, .appleVisionWithiPadDesign: 35 | return .iOS 36 | case .mac: 37 | return .macOS 38 | case .appleTv: 39 | return .tvOS 40 | case .appleWatch: 41 | return .watchOS 42 | case .appleVision: 43 | return .visionOS 44 | } 45 | } 46 | 47 | public var platformFilter: PlatformFilter { 48 | switch self { 49 | case .iPad, .iPhone, .macWithiPadDesign, .appleVisionWithiPadDesign: 50 | return .ios 51 | case .macCatalyst: 52 | return .catalyst 53 | case .mac: 54 | return .macos 55 | case .appleTv: 56 | return .tvos 57 | case .appleWatch: 58 | return .watchos 59 | case .appleVision: 60 | return .visionos 61 | } 62 | } 63 | } 64 | 65 | extension Collection { 66 | public func supports(_ platform: Platform) -> Bool { 67 | contains(where: { $0.platform == platform }) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/EnvironmentVariable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// It represents an environment variable that is passed when running a scheme's action 4 | public struct EnvironmentVariable: Equatable, Codable, Hashable, ExpressibleByStringLiteral, Sendable { 5 | // MARK: - Attributes 6 | 7 | /// The value of the environment variable 8 | public let value: String 9 | /// Whether the variable is enabled or not 10 | public let isEnabled: Bool 11 | 12 | // MARK: - Init 13 | 14 | public init(value: String, isEnabled: Bool) { 15 | self.value = value 16 | self.isEnabled = isEnabled 17 | } 18 | 19 | public init(stringLiteral value: StringLiteralType) { 20 | self.value = value 21 | isEnabled = true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/ExecutionAction.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | /// A execution action 5 | public struct ExecutionAction: Equatable, Codable, Sendable { 6 | // MARK: - Attributes 7 | 8 | /// Name of a script. 9 | public let title: String 10 | /// An inline shell script. 11 | public let scriptText: String 12 | /// Name of the build or test target that will provide the action's build settings. 13 | public let target: TargetReference? 14 | /// The path to the shell which shall execute this script. if it is nil, Xcode will use default value. 15 | public let shellPath: String? 16 | 17 | public let showEnvVarsInLog: Bool 18 | 19 | // MARK: - Init 20 | 21 | public init( 22 | title: String, 23 | scriptText: String, 24 | target: TargetReference?, 25 | shellPath: String?, 26 | showEnvVarsInLog: Bool = true 27 | ) { 28 | self.title = title 29 | self.scriptText = scriptText 30 | self.target = target 31 | self.shellPath = shellPath 32 | self.showEnvVarsInLog = showEnvVarsInLog 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/FileCodeGen.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// FileCodeGen: Soure file code generation attribues 4 | /// 5 | /// - `public`: public codegen attribute `settings = {ATTRIBUTES = (codegen, )`} 6 | /// - `private`: private codegen attribute `settings = {ATTRIBUTES = (private_codegen, )}` 7 | /// - `project`: project codegen attribute `settings = {ATTRIBUTES = (project_codegen, )}` 8 | /// - `disabled`: disabled codegen attribute `settings = {ATTRIBUTES = (no_codegen, )}` 9 | /// 10 | public enum FileCodeGen: String, Codable, Equatable, Sendable { 11 | case `public` = "codegen" 12 | case `private` = "private_codegen" 13 | case project = "project_codegen" 14 | case disabled = "no_codegen" 15 | } 16 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/FileElement.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | public enum FileElement: Equatable, Hashable, Codable, Sendable { 5 | case file(path: AbsolutePath) 6 | case folderReference(path: AbsolutePath) 7 | 8 | public var path: AbsolutePath { 9 | switch self { 10 | case let .file(path): 11 | return path 12 | case let .folderReference(path): 13 | return path 14 | } 15 | } 16 | 17 | public var isReference: Bool { 18 | switch self { 19 | case .file: 20 | return false 21 | case .folderReference: 22 | return true 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/Headers.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | /// Headers 5 | public struct Headers: Equatable, Codable, Sendable { 6 | // MARK: - Attributes 7 | 8 | public let `public`: [AbsolutePath] 9 | public let `private`: [AbsolutePath] 10 | public let project: [AbsolutePath] 11 | 12 | // MARK: - Init 13 | 14 | public init( 15 | public: [AbsolutePath], 16 | private: [AbsolutePath], 17 | project: [AbsolutePath] 18 | ) { 19 | self.public = `public` 20 | self.private = `private` 21 | self.project = project 22 | } 23 | } 24 | 25 | #if DEBUG 26 | extension Headers { 27 | public static func test( 28 | public: [AbsolutePath] = [], 29 | private: [AbsolutePath] = [], 30 | project: [AbsolutePath] = [] 31 | ) -> Headers { 32 | Headers( 33 | public: `public`, 34 | private: `private`, 35 | project: project 36 | ) 37 | } 38 | } 39 | #endif 40 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/IDETemplateMacros.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct IDETemplateMacros: Codable, Hashable, Sendable { 4 | private enum CodingKeys: String, CodingKey { 5 | case fileHeader = "FILEHEADER" 6 | } 7 | 8 | public let fileHeader: String? 9 | 10 | public init(fileHeader: String?) { 11 | self.fileHeader = fileHeader.map(Self.normalize) 12 | } 13 | 14 | private static func normalize(fileHeader: String) -> String { 15 | var fileHeader = fileHeader 16 | 17 | // As Xcode appends file header directly after `//` it adds to the beginning of template, 18 | // it is desired to also add leading space if it is not already present, 19 | // but if header starts with `//` then I know what I am doing and it should be kept intact 20 | if !fileHeader.hasPrefix("//"), let first = fileHeader.first.map(String.init), 21 | first.trimmingCharacters(in: .whitespacesAndNewlines) == first 22 | { 23 | fileHeader.insert(" ", at: fileHeader.startIndex) 24 | } 25 | 26 | // Xcode by default adds comment slashes to first line of header template 27 | if fileHeader.hasPrefix("//") { 28 | fileHeader.removeFirst(2) 29 | } 30 | 31 | // Xcode by default adds extra newline at the end, so if it is present, just remove it 32 | if fileHeader.last == "\n" { 33 | fileHeader.removeLast() 34 | } 35 | 36 | return fileHeader 37 | } 38 | } 39 | 40 | #if DEBUG 41 | extension IDETemplateMacros { 42 | public static func test(fileHeader: String? = "Header template") -> IDETemplateMacros { 43 | IDETemplateMacros(fileHeader: fileHeader) 44 | } 45 | } 46 | #endif 47 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/LaunchArgument.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// It represents an argument that is passed when running a scheme's action 4 | public struct LaunchArgument: Equatable, Codable, Hashable, Sendable { 5 | // MARK: - Attributes 6 | 7 | /// The name of the launch argument 8 | public let name: String 9 | /// Whether the argument is enabled or not 10 | public let isEnabled: Bool 11 | 12 | // MARK: - Init 13 | 14 | public init(name: String, isEnabled: Bool) { 15 | self.name = name 16 | self.isEnabled = isEnabled 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/LaunchStyle.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum LaunchStyle: Codable, Sendable { 4 | case automatically 5 | case waitForExecutableToBeLaunched 6 | } 7 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/MergedBinaryType.swift: -------------------------------------------------------------------------------- 1 | /// Represents the different options to configure a target for mergeable libraries 2 | /// 3 | /// https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries 4 | public enum MergedBinaryType: Equatable, Codable, Sendable { 5 | /// Target is never going to merge available dependencies 6 | case disabled 7 | 8 | /// Target is going to merge direct target dependencies (just the ones declared as part of it's project). With this build 9 | /// setting, 10 | /// Xcode treats mergeable dependencies like normal dynamic libraries in debug builds, 11 | /// but performs steps in release mode to automatically handle merging for **direct dependencies** 12 | /// 13 | /// A direct dependency is a library that meets two criteria: 14 | /// - The library is listed in your target’s Link Binary with Libraries build phase. 15 | /// - The library is the product of another target in your project. 16 | case automatic 17 | 18 | /// Target is going to merge direct and specified dependencies that are not part of the project. The set of dependencies 19 | /// is going to reflect the list of precompiled dynamic dependencies you want to merge as part of the target. These binaries 20 | /// must be compiled with `MAKE_MERGEABLE` flag set to true 21 | /// 22 | /// In some cases, you may want to manually configure merging between your app or framework target and dependent libraries. 23 | /// For example, you might not want to automatically merge dependencies that you share between an app and an app extension 24 | /// if you’re concerned about the app extension’s binary size. To set up manual merging, configure your app or framework 25 | /// target, 26 | /// then configure your dependent libraries. 27 | /// 28 | /// In your app or framework target, add the flag `mergedBinaryType` and set it to manual. After you add that setting to your 29 | /// target: 30 | /// - In release builds, Xcode merges the products of any of its direct dependencies which have 31 | /// MAKE_MERGEABLE enabled using the linker flags -merge_framework, -merge-l and so on. 32 | /// - In debug builds, Xcode links any of your target’s direct dependencies which have MERGEABLE_LIBRARY 33 | /// enabled, but not MAKE_MERGEABLE with the linker flags -reexport_framework, -reexport-l, and so on. 34 | /// - Xcode uses normal linking for targets that don’t have MERGEABLE_LIBRARY enabled. This is the same linking 35 | /// that Xcode uses for static libraries, or dynamic libraries that aren’t mergeable. 36 | case manual(mergeableDependencies: Set) 37 | } 38 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/Metadata/FrameworkMetadata.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | /// The metadata associated with a precompiled framework (.framework) 5 | public struct FrameworkMetadata: Equatable { 6 | public var path: AbsolutePath 7 | public var binaryPath: AbsolutePath 8 | public var dsymPath: AbsolutePath? 9 | public var bcsymbolmapPaths: [AbsolutePath] 10 | public var linking: BinaryLinking 11 | public var architectures: [BinaryArchitecture] 12 | public var status: LinkingStatus 13 | 14 | public init( 15 | path: AbsolutePath, 16 | binaryPath: AbsolutePath, 17 | dsymPath: AbsolutePath?, 18 | bcsymbolmapPaths: [AbsolutePath], 19 | linking: BinaryLinking, 20 | architectures: [BinaryArchitecture], 21 | status: LinkingStatus 22 | ) { 23 | self.path = path 24 | self.binaryPath = binaryPath 25 | self.dsymPath = dsymPath 26 | self.bcsymbolmapPaths = bcsymbolmapPaths 27 | self.linking = linking 28 | self.architectures = architectures 29 | self.status = status 30 | } 31 | } 32 | 33 | #if DEBUG 34 | extension FrameworkMetadata { 35 | public static func test( 36 | // swiftlint:disable:next force_try 37 | path: AbsolutePath = try! AbsolutePath(validating: "/Frameworks/TestFramework.xframework"), 38 | // swiftlint:disable:next force_try 39 | binaryPath: AbsolutePath = try! AbsolutePath(validating: "/Frameworks/TestFramework.xframework/TestFramework"), 40 | dsymPath: AbsolutePath? = nil, 41 | bcsymbolmapPaths: [AbsolutePath] = [], 42 | linking: BinaryLinking = .dynamic, 43 | architectures: [BinaryArchitecture] = [.arm64], 44 | status: LinkingStatus = .required 45 | ) -> FrameworkMetadata { 46 | FrameworkMetadata( 47 | path: path, 48 | binaryPath: binaryPath, 49 | dsymPath: dsymPath, 50 | bcsymbolmapPaths: bcsymbolmapPaths, 51 | linking: linking, 52 | architectures: architectures, 53 | status: status 54 | ) 55 | } 56 | } 57 | #endif 58 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/Metadata/LibraryMetadata.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | /// The metadata associated with a precompiled library (.a / .dylib) 5 | public struct LibraryMetadata: Equatable { 6 | public var path: AbsolutePath 7 | public var publicHeaders: AbsolutePath 8 | public var swiftModuleMap: AbsolutePath? 9 | public var architectures: [BinaryArchitecture] 10 | public var linking: BinaryLinking 11 | 12 | public init( 13 | path: AbsolutePath, 14 | publicHeaders: AbsolutePath, 15 | swiftModuleMap: AbsolutePath?, 16 | architectures: [BinaryArchitecture], 17 | linking: BinaryLinking 18 | ) { 19 | self.path = path 20 | self.publicHeaders = publicHeaders 21 | self.swiftModuleMap = swiftModuleMap 22 | self.architectures = architectures 23 | self.linking = linking 24 | } 25 | } 26 | 27 | #if DEBUG 28 | extension LibraryMetadata { 29 | public static func test( 30 | // swiftlint:disable:next force_try 31 | path: AbsolutePath = try! AbsolutePath(validating: "/Libraries/libTest/libTest.a"), 32 | // swiftlint:disable:next force_try 33 | publicHeaders: AbsolutePath = try! AbsolutePath(validating: "/Libraries/libTest/include"), 34 | // swiftlint:disable:next force_try 35 | swiftModuleMap: AbsolutePath? = try! AbsolutePath(validating: "/Libraries/libTest/libTest.swiftmodule"), 36 | architectures: [BinaryArchitecture] = [.arm64], 37 | linking: BinaryLinking = .static 38 | ) -> LibraryMetadata { 39 | LibraryMetadata( 40 | path: path, 41 | publicHeaders: publicHeaders, 42 | swiftModuleMap: swiftModuleMap, 43 | architectures: architectures, 44 | linking: linking 45 | ) 46 | } 47 | } 48 | #endif 49 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/Metadata/SystemFrameworkMetadata.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | /// The metadata associated with a system framework or library (e.g. UIKit.framework, libc++.tbd) 5 | public struct SystemFrameworkMetadata: Equatable { 6 | public var name: String 7 | public var path: AbsolutePath 8 | public var status: LinkingStatus 9 | public var source: SDKSource 10 | 11 | public init( 12 | name: String, 13 | path: AbsolutePath, 14 | status: LinkingStatus, 15 | source: SDKSource 16 | ) { 17 | self.name = name 18 | self.path = path 19 | self.status = status 20 | self.source = source 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/Metadata/TargetMetadata.swift: -------------------------------------------------------------------------------- 1 | /// The metadata associated with a target. 2 | public struct TargetMetadata: Codable, Equatable, Sendable { 3 | /// Tags set by the user to group targets together. 4 | /// Some Tuist features can leverage that information for doing things like filtering. 5 | public var tags: Set 6 | 7 | @available(*, deprecated, renamed: "metadata(tags:)", message: "Use the static 'metadata' initializer instead") 8 | public init( 9 | tags: Set 10 | ) { 11 | self.tags = tags 12 | } 13 | 14 | init(tags: Set, isLocal _: Bool) { 15 | self.tags = tags 16 | } 17 | 18 | public static func metadata(tags: Set = Set(), isLocal: Bool = true) -> TargetMetadata { 19 | self.init(tags: tags, isLocal: isLocal) 20 | } 21 | } 22 | 23 | #if DEBUG 24 | extension TargetMetadata { 25 | public static func test( 26 | tags: Set = [] 27 | ) -> TargetMetadata { 28 | TargetMetadata.metadata( 29 | tags: tags 30 | ) 31 | } 32 | } 33 | #endif 34 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/Metadata/XCFrameworkMetadata.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | /// The metadata associated with a precompiled xcframework 5 | public struct XCFrameworkMetadata: Equatable { 6 | public var path: AbsolutePath 7 | public var infoPlist: XCFrameworkInfoPlist 8 | public var linking: BinaryLinking 9 | public var mergeable: Bool 10 | public var status: LinkingStatus 11 | public var macroPath: AbsolutePath? 12 | /// All `.swiftmodule` files present in the `.xcframework` 13 | public var swiftModules: [AbsolutePath] 14 | /// All `.modulemap` files present in the `.xcframework` 15 | public var moduleMaps: [AbsolutePath] 16 | public var expectedSignature: XCFrameworkSignature? 17 | 18 | public init( 19 | path: AbsolutePath, 20 | infoPlist: XCFrameworkInfoPlist, 21 | linking: BinaryLinking, 22 | mergeable: Bool, 23 | status: LinkingStatus, 24 | macroPath: AbsolutePath?, 25 | swiftModules: [AbsolutePath] = [], 26 | moduleMaps: [AbsolutePath] = [], 27 | expectedSignature: XCFrameworkSignature? = nil 28 | ) { 29 | self.path = path 30 | self.infoPlist = infoPlist 31 | self.linking = linking 32 | self.mergeable = mergeable 33 | self.status = status 34 | self.macroPath = macroPath 35 | self.swiftModules = swiftModules 36 | self.moduleMaps = moduleMaps 37 | self.expectedSignature = expectedSignature 38 | } 39 | } 40 | 41 | #if DEBUG 42 | extension XCFrameworkMetadata { 43 | public static func test( 44 | // swiftlint:disable:next force_try 45 | path: AbsolutePath = try! AbsolutePath(validating: "/XCFrameworks/XCFramework.xcframework"), 46 | infoPlist: XCFrameworkInfoPlist = .test(), 47 | linking: BinaryLinking = .dynamic, 48 | mergeable: Bool = false, 49 | status: LinkingStatus = .required, 50 | macroPath: AbsolutePath? = nil, 51 | swiftModules: [AbsolutePath] = [], 52 | moduleMaps: [AbsolutePath] = [], 53 | expectedSignature: XCFrameworkSignature? = nil 54 | ) -> XCFrameworkMetadata { 55 | XCFrameworkMetadata( 56 | path: path, 57 | infoPlist: infoPlist, 58 | linking: linking, 59 | mergeable: mergeable, 60 | status: status, 61 | macroPath: macroPath, 62 | swiftModules: swiftModules, 63 | moduleMaps: moduleMaps, 64 | expectedSignature: expectedSignature 65 | ) 66 | } 67 | } 68 | #endif 69 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/MetalOptions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct MetalOptions: Equatable, Codable, Sendable { 4 | public var apiValidation: Bool 5 | public var shaderValidation: Bool 6 | public var showGraphicsOverview: Bool 7 | public var logGraphicsOverview: Bool 8 | 9 | public init( 10 | apiValidation: Bool = true, 11 | shaderValidation: Bool = false, 12 | showGraphicsOverview: Bool = false, 13 | logGraphicsOverview: Bool = false 14 | ) { 15 | self.apiValidation = apiValidation 16 | self.shaderValidation = shaderValidation 17 | self.showGraphicsOverview = showGraphicsOverview 18 | self.logGraphicsOverview = logGraphicsOverview 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/OnDemandResourcesTags.swift: -------------------------------------------------------------------------------- 1 | public struct OnDemandResourcesTags: Codable, Equatable, Sendable { 2 | public let initialInstall: [String]? 3 | public let prefetchOrder: [String]? 4 | 5 | public init(initialInstall: [String]?, prefetchOrder: [String]?) { 6 | self.initialInstall = initialInstall 7 | self.prefetchOrder = prefetchOrder 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/Package.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | public enum Package: Equatable, Codable, Sendable { 5 | case remote(url: String, requirement: Requirement) 6 | case local(path: AbsolutePath) 7 | } 8 | 9 | extension XcodeGraph.Package { 10 | public var isRemote: Bool { 11 | switch self { 12 | case .remote: 13 | return true 14 | case .local: 15 | return false 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/PlatformFilter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Convenience typealias to be used to ensure unique filters are applied 4 | public typealias PlatformFilters = Set 5 | 6 | extension PlatformFilters: @retroactive Comparable { 7 | public static func < (lhs: Set, rhs: Set) -> Bool { 8 | lhs.map(\.xcodeprojValue).sorted().joined() < rhs.map(\.xcodeprojValue).sorted().joined() 9 | } 10 | } 11 | 12 | /// Defines a set of platforms that can be used to limit where things 13 | /// like build files, resources, and dependencies are used. 14 | /// Context: https://github.com/tuist/tuist/pull/3152 15 | public enum PlatformFilter: Comparable, Hashable, Codable, CaseIterable, Sendable { 16 | case ios 17 | case macos 18 | case tvos 19 | case catalyst 20 | case driverkit 21 | case watchos 22 | case visionos 23 | 24 | public var xcodeprojValue: String { 25 | switch self { 26 | case .catalyst: 27 | return "maccatalyst" 28 | case .macos: 29 | return "macos" 30 | case .tvos: 31 | return "tvos" 32 | case .ios: 33 | return "ios" 34 | case .driverkit: 35 | return "driverkit" 36 | case .watchos: 37 | return "watchos" 38 | case .visionos: 39 | return "xros" 40 | } 41 | } 42 | 43 | /// It returns the platform that the filter is filtering for 44 | public var platform: Platform? { 45 | switch self { 46 | case .ios, .catalyst: 47 | return .iOS 48 | case .macos: 49 | return .macOS 50 | case .tvos: 51 | return .tvOS 52 | case .watchos: 53 | return .watchOS 54 | case .visionos: 55 | return .visionOS 56 | case .driverkit: 57 | // TODO: Add support for it 58 | return nil 59 | } 60 | } 61 | } 62 | 63 | extension PlatformFilters { 64 | /// Examples - 65 | /// This should not be here but downstream 66 | /// `platformFilter = ios` 67 | /// `platformFilters = (ios, xros, )` 68 | public var xcodeprojValue: [String] { 69 | map(\.xcodeprojValue).sorted() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/PrivacyManifest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct PrivacyManifest: Codable, Equatable, Sendable { 4 | public var tracking: Bool 5 | 6 | public var trackingDomains: [String] 7 | 8 | public var collectedDataTypes: [[String: Plist.Value]] 9 | 10 | public var accessedApiTypes: [[String: Plist.Value]] 11 | 12 | public init( 13 | tracking: Bool, 14 | trackingDomains: [String], 15 | collectedDataTypes: [[String: Plist.Value]], 16 | accessedApiTypes: [[String: Plist.Value]] 17 | ) { 18 | self.tracking = tracking 19 | self.trackingDomains = trackingDomains 20 | self.collectedDataTypes = collectedDataTypes 21 | self.accessedApiTypes = accessedApiTypes 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/ProfileAction.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | public struct ProfileAction: Equatable, Codable, Sendable { 5 | // MARK: - Attributes 6 | 7 | public let configurationName: String 8 | public let preActions: [ExecutionAction] 9 | public let postActions: [ExecutionAction] 10 | public let executable: TargetReference? 11 | public let arguments: Arguments? 12 | 13 | // MARK: - Init 14 | 15 | public init( 16 | configurationName: String, 17 | preActions: [ExecutionAction] = [], 18 | postActions: [ExecutionAction] = [], 19 | executable: TargetReference? = nil, 20 | arguments: Arguments? = nil 21 | ) { 22 | self.configurationName = configurationName 23 | self.preActions = preActions 24 | self.postActions = postActions 25 | self.executable = executable 26 | self.arguments = arguments 27 | } 28 | } 29 | 30 | #if DEBUG 31 | extension ProfileAction { 32 | public static func test( 33 | configurationName: String = "Beta Release", 34 | preActions: [ExecutionAction] = [], 35 | postActions: [ExecutionAction] = [], 36 | // swiftlint:disable:next force_try 37 | executable: TargetReference? = TargetReference(projectPath: try! AbsolutePath(validating: "/Project"), name: "App"), 38 | arguments: Arguments? = Arguments.test() 39 | ) -> ProfileAction { 40 | ProfileAction( 41 | configurationName: configurationName, 42 | preActions: preActions, 43 | postActions: postActions, 44 | executable: executable, 45 | arguments: arguments 46 | ) 47 | } 48 | } 49 | #endif 50 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/ProjectGroup.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum ProjectGroup: Hashable, Codable, Sendable { 4 | case group(name: String) 5 | } 6 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/RawScriptBuildPhase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | /// It represents a raw script build phase. 5 | public struct RawScriptBuildPhase: Equatable, Codable, Sendable { 6 | /// The name of the build phase. 7 | public let name: String 8 | 9 | /// Script. 10 | public let script: String 11 | 12 | /// Whether we want the build phase to show the environment variables in the logs. 13 | public let showEnvVarsInLog: Bool 14 | 15 | /// Whether the script should be hashed for caching purposes. 16 | public let hashable: Bool 17 | 18 | /// The path to the shell which shall execute this script. 19 | public let shellPath: String 20 | 21 | /// Initializes the target script. 22 | /// - Parameter name: The name of the build phase. 23 | /// - Parameter script: Script. 24 | /// - Parameter showEnvVarsInLog: Whether we want the build phase to show the environment variables in the logs. 25 | /// - Parameter hashable: Whether the script should be hashed for caching purposes. 26 | /// - Parameter shellPath: The path to the shell which shall execute this script. Default is `/bin/sh`. 27 | public init( 28 | name: String, 29 | script: String, 30 | showEnvVarsInLog: Bool, 31 | hashable: Bool, 32 | shellPath: String = "/bin/sh" 33 | ) { 34 | self.name = name 35 | self.script = script 36 | self.showEnvVarsInLog = showEnvVarsInLog 37 | self.hashable = hashable 38 | self.shellPath = shellPath 39 | } 40 | } 41 | 42 | #if DEBUG 43 | extension RawScriptBuildPhase { 44 | public static func test( 45 | name: String = "Test", 46 | script: String = "", 47 | showEnvVarsInLog: Bool = false, 48 | hashable: Bool = false 49 | ) -> RawScriptBuildPhase { 50 | RawScriptBuildPhase(name: name, script: script, showEnvVarsInLog: showEnvVarsInLog, hashable: hashable) 51 | } 52 | } 53 | #endif 54 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/Requirement.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum Requirement: Equatable, Codable, Sendable { 4 | case upToNextMajor(String) 5 | case upToNextMinor(String) 6 | case range(from: String, to: String) 7 | case exact(String) 8 | case branch(String) 9 | case revision(String) 10 | } 11 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/ResourceFileElement.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | public enum ResourceFileElement: Equatable, Hashable, Codable, Sendable { 5 | /// A file path (or glob pattern) to include, a list of file paths (or glob patterns) to exclude, ODR tags list and inclusion 6 | /// condition. 7 | /// For convenience, a string literal can be used as an alternate way to specify this option. 8 | case file(path: AbsolutePath, tags: [String] = [], inclusionCondition: PlatformCondition? = nil) 9 | /// A directory path to include as a folder reference, ODR tags list and inclusion condition. 10 | case folderReference(path: AbsolutePath, tags: [String] = [], inclusionCondition: PlatformCondition? = nil) 11 | 12 | public var path: AbsolutePath { 13 | switch self { 14 | case let .file(path, _, _): 15 | return path 16 | case let .folderReference(path, _, _): 17 | return path 18 | } 19 | } 20 | 21 | public var isReference: Bool { 22 | switch self { 23 | case .file: 24 | return false 25 | case .folderReference: 26 | return true 27 | } 28 | } 29 | 30 | public var tags: [String] { 31 | switch self { 32 | case let .file(_, tags, _): 33 | return tags 34 | case let .folderReference(_, tags, _): 35 | return tags 36 | } 37 | } 38 | 39 | public var inclusionCondition: PlatformCondition? { 40 | switch self { 41 | case let .file(_, _, condition): 42 | return condition 43 | case let .folderReference(_, _, condition): 44 | return condition 45 | } 46 | } 47 | 48 | public init(path: AbsolutePath) { 49 | self = .file(path: path) 50 | } 51 | } 52 | 53 | extension [XcodeGraph.ResourceFileElement] { 54 | public mutating func remove(path: AbsolutePath) { 55 | guard let index = firstIndex(of: XcodeGraph.ResourceFileElement(path: path)) else { return } 56 | remove(at: index) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/ResourceFileElements.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct ResourceFileElements: Codable, Equatable, Sendable { 4 | public var resources: [ResourceFileElement] 5 | 6 | public var privacyManifest: PrivacyManifest? 7 | 8 | public init( 9 | _ resources: [ResourceFileElement], 10 | privacyManifest: PrivacyManifest? = nil 11 | ) { 12 | self.resources = resources 13 | self.privacyManifest = privacyManifest 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/RunActionOptions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | /// Options for the `RunAction` action 5 | public struct RunActionOptions: Equatable, Codable, Sendable { 6 | /// App Language. 7 | public let language: String? 8 | 9 | /// App Region. 10 | public let region: String? 11 | 12 | /// The path of the 13 | /// [StoreKit configuration 14 | /// file](https://developer.apple.com/documentation/xcode/setting_up_storekit_testing_in_xcode#3625700) 15 | public let storeKitConfigurationPath: AbsolutePath? 16 | 17 | /// A simulated location used when running the provided run action. 18 | public let simulatedLocation: SimulatedLocation? 19 | 20 | /// Configure your project to work with the Metal frame debugger. 21 | public let enableGPUFrameCaptureMode: GPUFrameCaptureMode 22 | 23 | /// Creates an `RunActionOptions` instance 24 | /// 25 | /// - Parameters: 26 | /// - language: language (e.g. "pl"). 27 | /// 28 | /// - storeKitConfigurationPath: The absolute path of the 29 | /// [StoreKit configuration 30 | /// file](https://developer.apple.com/documentation/xcode/setting_up_storekit_testing_in_xcode#3625700). 31 | /// The default value is `nil`, which results in no 32 | /// configuration defined for the scheme 33 | /// 34 | /// - simulatedLocation: The simulated GPS location to use when running the app. 35 | /// 36 | /// - enableGPUFrameCaptureMode: The Metal Frame Capture mode to use. e.g: .disabled 37 | /// If your target links to the Metal framework, Xcode enables GPU Frame Capture. 38 | /// You can disable it to test your app in best performance. 39 | 40 | public init( 41 | language: String? = nil, 42 | region: String? = nil, 43 | storeKitConfigurationPath: AbsolutePath? = nil, 44 | simulatedLocation: SimulatedLocation? = nil, 45 | enableGPUFrameCaptureMode: GPUFrameCaptureMode = .autoEnabled 46 | ) { 47 | self.language = language 48 | self.region = region 49 | self.storeKitConfigurationPath = storeKitConfigurationPath 50 | self.simulatedLocation = simulatedLocation 51 | self.enableGPUFrameCaptureMode = enableGPUFrameCaptureMode 52 | } 53 | } 54 | 55 | extension RunActionOptions { 56 | public enum GPUFrameCaptureMode: String, Codable, Equatable, Sendable { 57 | case autoEnabled 58 | case metal 59 | case openGL 60 | case disabled 61 | 62 | public static var `default`: GPUFrameCaptureMode { 63 | .autoEnabled 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/SDKSource.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum SDKSource: String, Equatable, Codable, Sendable { 4 | case developer // Platforms/iPhoneOS.platform/Developer/Library 5 | case system // Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library 6 | 7 | /// Returns the framework search path that should be used in Xcode to locate the SDK. 8 | public var frameworkSearchPath: String? { 9 | switch self { 10 | case .developer: 11 | return "$(PLATFORM_DIR)/Developer/Library/Frameworks" 12 | case .system: 13 | return nil 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/SDKType.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum SDKType: CaseIterable, Equatable { 4 | case framework 5 | case library 6 | case swiftLibrary 7 | 8 | public static var supportedTypesDescription: String { 9 | let supportedTypes = allCases 10 | .map { 11 | switch $0 { 12 | case .framework: 13 | return ".framework" 14 | case .library, .swiftLibrary: 15 | return ".tbd" 16 | } 17 | } 18 | .joined(separator: ", ") 19 | return "[\(supportedTypes)]" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/Scheme.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | public struct Scheme: Equatable, Codable, Sendable { 5 | // MARK: - Attributes 6 | 7 | public var name: String 8 | public var shared: Bool 9 | public var hidden: Bool 10 | public var buildAction: BuildAction? 11 | public var testAction: TestAction? 12 | public var runAction: RunAction? 13 | public var archiveAction: ArchiveAction? 14 | public var profileAction: ProfileAction? 15 | public var analyzeAction: AnalyzeAction? 16 | 17 | // MARK: - Init 18 | 19 | public init( 20 | name: String, 21 | shared: Bool = false, 22 | hidden: Bool = false, 23 | buildAction: BuildAction? = nil, 24 | testAction: TestAction? = nil, 25 | runAction: RunAction? = nil, 26 | archiveAction: ArchiveAction? = nil, 27 | profileAction: ProfileAction? = nil, 28 | analyzeAction: AnalyzeAction? = nil 29 | ) { 30 | self.name = name 31 | self.shared = shared 32 | self.hidden = hidden 33 | self.buildAction = buildAction 34 | self.testAction = testAction 35 | self.runAction = runAction 36 | self.archiveAction = archiveAction 37 | self.profileAction = profileAction 38 | self.analyzeAction = analyzeAction 39 | } 40 | } 41 | 42 | #if DEBUG 43 | extension Scheme { 44 | public static func test( 45 | name: String = "Test", 46 | shared: Bool = false, 47 | buildAction: BuildAction? = BuildAction.test(), 48 | testAction: TestAction? = TestAction.test(), 49 | runAction: RunAction? = RunAction.test(), 50 | archiveAction: ArchiveAction? = ArchiveAction.test(), 51 | profileAction: ProfileAction? = ProfileAction.test(), 52 | analyzeAction: AnalyzeAction? = AnalyzeAction.test() 53 | ) -> Scheme { 54 | Scheme( 55 | name: name, 56 | shared: shared, 57 | buildAction: buildAction, 58 | testAction: testAction, 59 | runAction: runAction, 60 | archiveAction: archiveAction, 61 | profileAction: profileAction, 62 | analyzeAction: analyzeAction 63 | ) 64 | } 65 | } 66 | #endif 67 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/SchemeDiagnosticsOptions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct SchemeDiagnosticsOptions: Equatable, Codable, Sendable { 4 | public let addressSanitizerEnabled: Bool 5 | public let detectStackUseAfterReturnEnabled: Bool 6 | public let threadSanitizerEnabled: Bool 7 | public let mainThreadCheckerEnabled: Bool 8 | public let performanceAntipatternCheckerEnabled: Bool 9 | 10 | public init( 11 | addressSanitizerEnabled: Bool = false, 12 | detectStackUseAfterReturnEnabled: Bool = false, 13 | threadSanitizerEnabled: Bool = false, 14 | mainThreadCheckerEnabled: Bool = false, 15 | performanceAntipatternCheckerEnabled: Bool = false 16 | ) { 17 | self.addressSanitizerEnabled = addressSanitizerEnabled 18 | self.detectStackUseAfterReturnEnabled = detectStackUseAfterReturnEnabled 19 | self.threadSanitizerEnabled = threadSanitizerEnabled 20 | self.mainThreadCheckerEnabled = mainThreadCheckerEnabled 21 | self.performanceAntipatternCheckerEnabled = performanceAntipatternCheckerEnabled 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/ScreenCaptureFormat.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum ScreenCaptureFormat: String, Codable, Sendable { 4 | case screenshots 5 | case screenRecording 6 | } 7 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/SimulatedLocation.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | public enum SimulatedLocation: Sendable { 5 | case gpxFile(AbsolutePath) 6 | case reference(String) 7 | 8 | /// A unique identifier string for the selected simulated location. 9 | /// 10 | /// In case of Xcode's simulated locations, this is a string representing the location. 11 | /// In case of a custom GPX file, this is a path to that file. 12 | public var identifier: String { 13 | switch self { 14 | case let .gpxFile(path): 15 | return path.pathString 16 | case let .reference(identifier): 17 | return identifier 18 | } 19 | } 20 | 21 | /// A reference type is 1 if using Xcode's built-in simulated locations. 22 | /// Otherwise, it is 0. 23 | public var referenceType: String { 24 | if case .gpxFile = self { return "0" } 25 | return "1" 26 | } 27 | } 28 | 29 | extension SimulatedLocation: Equatable, Codable, Hashable { 30 | public init(from decoder: Decoder) throws { 31 | let container = try decoder.singleValueContainer() 32 | let value = try container.decode(String.self) 33 | 34 | guard value.hasSuffix(".gpx") else { 35 | self = .reference(value) 36 | return 37 | } 38 | 39 | self = .gpxFile(try AbsolutePath(validating: value)) 40 | } 41 | 42 | public func encode(to encoder: Encoder) throws { 43 | var container = encoder.singleValueContainer() 44 | try container.encode(identifier) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/SourceFile.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | /// A type that represents a source file. 5 | public struct SourceFile: ExpressibleByStringLiteral, Equatable, Codable, Sendable { 6 | /// Source file path. 7 | public var path: AbsolutePath 8 | 9 | /// Compiler flags 10 | /// When source files are added to a target, they can contain compiler flags that Xcode's build system 11 | /// passes to the compiler when compiling those files. By default none is passed. 12 | public var compilerFlags: String? 13 | 14 | /// This is intended to be used by the mappers that generate files through side effects. 15 | /// This attribute is used by the content hasher used by the caching functionality. 16 | public var contentHash: String? 17 | 18 | /// Source file code generation attribute 19 | public let codeGen: FileCodeGen? 20 | 21 | /// Source file condition for platform filters 22 | public let compilationCondition: PlatformCondition? 23 | 24 | public init( 25 | path: AbsolutePath, 26 | compilerFlags: String? = nil, 27 | contentHash: String? = nil, 28 | codeGen: FileCodeGen? = nil, 29 | compilationCondition: PlatformCondition? = nil 30 | ) { 31 | self.path = path 32 | self.compilerFlags = compilerFlags 33 | self.contentHash = contentHash 34 | self.codeGen = codeGen 35 | self.compilationCondition = compilationCondition 36 | } 37 | 38 | // MARK: - ExpressibleByStringLiteral 39 | 40 | public init(stringLiteral value: String) { 41 | path = try! AbsolutePath(validating: value) // swiftlint:disable:this force_try 42 | compilerFlags = nil 43 | contentHash = nil 44 | codeGen = nil 45 | compilationCondition = nil 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/SourceFileGlob.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A type that represents a list of source files defined by a glob. 4 | public struct SourceFileGlob: Equatable { 5 | /// Glob pattern to unfold all the source files. 6 | public let glob: String 7 | 8 | /// Glob pattern used for filtering out files. 9 | public let excluding: [String] 10 | 11 | /// Compiler flags. 12 | public let compilerFlags: String? 13 | 14 | /// Source file code generation attribute. 15 | public let codeGen: FileCodeGen? 16 | 17 | /// Compilation condition the source file. 18 | public let compilationCondition: PlatformCondition? 19 | 20 | /// Initializes the source file glob. 21 | /// - Parameters: 22 | /// - glob: Glob pattern to unfold all the source files. 23 | /// - excluding: Glob pattern used for filtering out files. 24 | /// - compilerFlags: Compiler flags. 25 | /// - codeGen: Source file code generation attribute. 26 | /// - compilationCondition: Condition for file compilation. 27 | public init( 28 | glob: String, 29 | excluding: [String] = [], 30 | compilerFlags: String? = nil, 31 | codeGen: FileCodeGen? = nil, 32 | compilationCondition: PlatformCondition? = nil 33 | ) { 34 | self.glob = glob 35 | self.excluding = excluding 36 | self.compilerFlags = compilerFlags 37 | self.codeGen = codeGen 38 | self.compilationCondition = compilationCondition 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/TargetReference.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | public struct TargetReference: Hashable, Codable, Sendable { 5 | public var projectPath: AbsolutePath 6 | public var name: String 7 | 8 | public init(projectPath: AbsolutePath, name: String) { 9 | self.projectPath = projectPath 10 | self.name = name 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/TargetType.swift: -------------------------------------------------------------------------------- 1 | public enum TargetType: Codable, Hashable, Equatable, Sendable { 2 | /// A target is local when it hasn't been resolved and pulled by a package manager (e.g., SPM). 3 | case local 4 | /// A target is remote, when it has been resolved and pulled by a package manager (e.g., SwiftPM). 5 | case remote 6 | } 7 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/TestPlan.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | public struct TestPlan: Hashable, Codable, Sendable { 5 | public let name: String 6 | public let path: AbsolutePath 7 | public let testTargets: [TestableTarget] 8 | public let isDefault: Bool 9 | 10 | public init(path: AbsolutePath, testTargets: [TestableTarget], isDefault: Bool) { 11 | name = path.basenameWithoutExt 12 | self.path = path 13 | self.testTargets = testTargets 14 | self.isDefault = isDefault 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/XcodeGraph/Models/TestingOptions.swift: -------------------------------------------------------------------------------- 1 | public struct TestingOptions: Sendable, OptionSet, Codable, Hashable { 2 | public let rawValue: Int 3 | 4 | public init(rawValue: Int) { 5 | self.rawValue = rawValue 6 | } 7 | 8 | public static let parallelizable = TestingOptions(rawValue: 1 << 0) 9 | public static let randomExecutionOrdering = TestingOptions(rawValue: 1 << 1) 10 | } 11 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Documentation.docc/XcodeProjMapper.md: -------------------------------------------------------------------------------- 1 | # ``XcodeGraphMapper`` 2 | 3 | @Metadata { 4 | @DisplayName("Xcode Project Mapper") 5 | @TitleHeading("Documentation Portal") 6 | @PageColor(purple) 7 | } 8 | 9 | A tool that maps Xcode projects (`.xcodeproj` and `.xcworkspace`) into a structured, analyzable graph of projects, targets, dependencies, and build settings. This enables downstream tasks such as code generation, dependency analysis, and integration with custom tooling pipelines. 10 | 11 | ## Overview 12 | 13 | ``XcodeGraphMapper`` leverages ``XcodeProj`` to parse and navigate Xcode project files, then translates them into a domain-specific graph model (``XcodeGraph/Graph``). This model captures all essential components—projects, targets, packages, dependencies, build settings, schemes, and more—providing a high-level, language-agnostic structure for further processing. 14 | 15 | By using this graph-based representation, you can easily analyze project configurations, visualize complex dependency graphs, or integrate advanced workflows into your build pipelines. For example, teams can leverage ``XcodeGraphMapper`` to: 16 | 17 | - Generate code based on discovered resources and targets. 18 | - Validate project configurations and detect missing bundle identifiers or invalid references. 19 | - Explore dependencies between multiple projects and packages within a workspace. 20 | - Automate repetitive tasks like scheme generation, resource synthesis, or compliance checks. 21 | 22 | ## Topics 23 | 24 | ### XcodeGraphMapper 25 | 26 | - ``XcodeGraphMapper`` 27 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Extensions/PBXProject+Extensions.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | extension PBXProject { 4 | /// Retrieves the value of a specific project attribute. 5 | /// 6 | /// - Parameter attr: The attribute key to look up. 7 | /// - Returns: The value of the attribute if it exists, or `nil` if not found. 8 | func attribute(for attr: ProjectAttributeKey) -> String? { 9 | attributes[attr.rawValue]?.stringValue 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Extensions/Package+Extensions.swift: -------------------------------------------------------------------------------- 1 | import XcodeGraph 2 | 3 | extension Package { 4 | /// Returns a URL or identifier for the package based on whether it's remote or local. 5 | var url: String { 6 | switch self { 7 | case let .remote(url, _): 8 | return url 9 | case let .local(path): 10 | return path.pathString 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Extensions/Platform+Extensions.swift: -------------------------------------------------------------------------------- 1 | import XcodeGraph 2 | 3 | extension Platform { 4 | /// Initializes a `Platform` instance from an SDK root string (e.g., "iphoneos", "macosx"). 5 | /// Returns `nil` if no matching platform is found. 6 | init?(sdkroot: String) { 7 | guard let platform = Platform.allCases.first(where: { $0.xcodeSdkRoot == sdkroot }) else { 8 | return nil 9 | } 10 | self = platform 11 | } 12 | 13 | /// Returns a set of `Destination` values supported by this platform. 14 | var destinations: Destinations { 15 | switch self { 16 | case .iOS: 17 | return [.iPad, .iPhone, .macCatalyst, .macWithiPadDesign, .appleVisionWithiPadDesign] 18 | case .macOS: 19 | return [.mac] 20 | case .tvOS: 21 | return [.appleTv] 22 | case .watchOS: 23 | return [.appleWatch] 24 | case .visionOS: 25 | return [.appleVision] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Extensions/PlatformFilter+Extensions.swift: -------------------------------------------------------------------------------- 1 | import XcodeGraph 2 | 3 | extension PlatformFilter { 4 | /// Initializes a `PlatformFilter` from a string that matches Xcodeproj values. 5 | init?(rawValue: String) { 6 | switch rawValue { 7 | case PlatformFilter.ios.xcodeprojValue: 8 | self = .ios 9 | case PlatformFilter.macos.xcodeprojValue: 10 | self = .macos 11 | case PlatformFilter.tvos.xcodeprojValue: 12 | self = .tvos 13 | case PlatformFilter.catalyst.xcodeprojValue: 14 | self = .catalyst 15 | case PlatformFilter.driverkit.xcodeprojValue: 16 | self = .driverkit 17 | case PlatformFilter.watchos.xcodeprojValue: 18 | self = .watchos 19 | case PlatformFilter.visionos.xcodeprojValue: 20 | self = .visionos 21 | default: 22 | return nil 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Extensions/TargetDependency+Extensions.swift: -------------------------------------------------------------------------------- 1 | import XcodeGraph 2 | 3 | extension TargetDependency { 4 | /// Extracts the name of the dependency for relevant cases, such as target, project, SDK, package, and libraries. 5 | var name: String { 6 | switch self { 7 | case let .target(name, _, _): 8 | return name 9 | case let .project(target, _, _, _): 10 | return target 11 | case let .sdk(name, _, _): 12 | return name 13 | case let .package(product, _, _): 14 | return product 15 | case let .framework(path, _, _): 16 | return path.basenameWithoutExt 17 | case let .xcframework(path, _, _, _): 18 | return path.basenameWithoutExt 19 | case let .library(path, _, _, _): 20 | return path.basenameWithoutExt 21 | case .xctest: 22 | return "xctest" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Extensions/XCWorkspace+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XcodeProj 4 | 5 | // swiftlint:disable force_try 6 | extension XCWorkspace { 7 | /// A computed property that either returns the workspace’s `path` 8 | public var workspacePath: AbsolutePath { 9 | try! AbsolutePath(validating: path!.string) 10 | } 11 | } 12 | 13 | extension XcodeProj { 14 | /// A computed property that either returns the project’s `path` 15 | public var projectPath: AbsolutePath { 16 | try! AbsolutePath(validating: path!.string) 17 | } 18 | 19 | public var srcPath: AbsolutePath { 20 | projectPath.parentDirectory 21 | } 22 | 23 | public var srcPathString: String { 24 | srcPath.pathString 25 | } 26 | } 27 | 28 | // swiftlint:enable force_try 29 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Extensions/XCWorkspaceDataFileRef+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Path 2 | import XcodeProj 3 | 4 | extension XCWorkspaceDataFileRef { 5 | /// Resolves the absolute path referenced by this `XCWorkspaceDataFileRef`. 6 | /// 7 | /// - Parameter srcPath: The workspace source root path. 8 | /// - Returns: The resolved `AbsolutePath` of this file reference. 9 | func path( 10 | srcPath: AbsolutePath, 11 | developerDirectoryProvider: DeveloperDirectoryProviding = DeveloperDirectoryProvider() 12 | ) async throws -> AbsolutePath { 13 | switch location { 14 | case let .absolute(path): 15 | return try AbsolutePath(validating: path) 16 | case let .container(subPath): 17 | let relativePath = try RelativePath(validating: subPath) 18 | return srcPath.appending(relativePath) 19 | case let .developer(subPath): 20 | return try AbsolutePath( 21 | validating: subPath, 22 | relativeTo: try await developerDirectoryProvider.developerDirectory() 23 | ) 24 | case let .group(subPath): 25 | // Group paths are relative to the workspace file itself 26 | let relativePath = try RelativePath(validating: subPath) 27 | return srcPath.appending(relativePath) 28 | case let .current(subPath): 29 | // Current paths are relative to the current directory 30 | let relativePath = try RelativePath(validating: subPath) 31 | return srcPath.appending(relativePath) 32 | case let .other(type, subPath): 33 | // Other path types: prefix with the type and append subpath 34 | let relativePath = try RelativePath(validating: "\(type)/\(subPath)") 35 | return srcPath.appending(relativePath) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Mappers/Phases/BuildPhaseConstants.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | 4 | /// Constants related to various build phases and their default values. 5 | enum BuildPhaseConstants { 6 | /// The default name for a run script build phase if none is provided. 7 | static let defaultScriptName = "Run Script" 8 | /// The default shell path used by run script build phases. 9 | static let defaultShellPath = "/bin/sh" 10 | /// A placeholder name used when a shell script build phase has no name. 11 | static let unnamedScriptPhase = "Unnamed Shell Script Phase" 12 | /// The default name for a copy files build phase if none is provided. 13 | static let copyFilesDefault = "Copy Files" 14 | } 15 | 16 | /// Attributes indicating header visibility within a build target. 17 | enum HeaderAttribute: String { 18 | /// Indicates that a header is and can be exposed outside the module. 19 | case `public` = "Public" 20 | /// Indicates that a header is private and not exposed outside the module. 21 | case `private` = "Private" 22 | } 23 | 24 | /// Attributes related to code generation behavior for source files. 25 | enum CodeGenAttribute: String { 26 | /// Indicates that code generation is enabled publicly. 27 | case `public` = "codegen" 28 | /// Indicates that code generation is restricted to private scopes. 29 | case `private` = "private_codegen" 30 | /// Indicates that code generation is scoped to the project only. 31 | case project = "project_codegen" 32 | /// Indicates that code generation is disabled for the file. 33 | case disabled = "no_codegen" 34 | } 35 | 36 | /// Commonly referenced directory names within a project. 37 | enum DirectoryName { 38 | /// The directory used to store headers. 39 | static let headers = "Headers" 40 | } 41 | 42 | /// Attributes that can be assigned to build files in certain build phases. 43 | enum BuildFileAttribute: String { 44 | /// Indicates that the file should be code signed on copy during a copy files build phase. 45 | case codeSignOnCopy = "CodeSignOnCopy" 46 | } 47 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Mappers/Phases/PBXCoreDataModelsBuildPhaseMapper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XcodeGraph 4 | import XcodeProj 5 | 6 | /// A protocol for mapping a set of resource files into `CoreDataModel` objects. 7 | protocol PBXCoreDataModelsBuildPhaseMapping { 8 | /// Maps the provided resource files into an array of `CoreDataModel`. 9 | /// - Parameters: 10 | /// - resourceFiles: The build files that might contain Core Data models. 11 | /// - xcodeProj: The `XcodeProj` for resolving file paths. 12 | /// - Returns: An array of `CoreDataModel` objects. 13 | /// - Throws: If any paths are invalid. 14 | func map(_ resourceFiles: [PBXBuildFile], xcodeProj: XcodeProj) throws -> [CoreDataModel] 15 | } 16 | 17 | /// Maps `PBXBuildFile` objects to `CoreDataModel` domain models if they represent `.xcdatamodeld` files. 18 | struct PBXCoreDataModelsBuildPhaseMapper: PBXCoreDataModelsBuildPhaseMapping { 19 | func map(_ resourceFiles: [PBXBuildFile], xcodeProj: XcodeProj) throws -> [CoreDataModel] { 20 | try resourceFiles.compactMap { try mapCoreDataModel($0, xcodeProj: xcodeProj) } 21 | } 22 | 23 | // MARK: - Private Helpers 24 | 25 | /// Converts a single `PBXBuildFile` into a `CoreDataModel` if it references a `.xcdatamodeld` version group. 26 | private func mapCoreDataModel(_ buildFile: PBXBuildFile, xcodeProj: XcodeProj) throws -> CoreDataModel? { 27 | guard let versionGroup = buildFile.file as? XCVersionGroup, 28 | versionGroup.path?.hasSuffix(FileExtension.coreData.rawValue) == true, 29 | let modelPathString = try versionGroup.fullPath(sourceRoot: xcodeProj.srcPathString) 30 | else { 31 | return nil 32 | } 33 | 34 | let modelPath = try AbsolutePath(validating: modelPathString) 35 | 36 | // Gather all child .xcdatamodel versions 37 | let versionPaths = versionGroup.children.compactMap(\.path) 38 | let resolvedVersions = try versionPaths.map { 39 | try AbsolutePath(validating: $0, relativeTo: modelPath) 40 | } 41 | 42 | // Current version defaults to the first if not explicitly set 43 | let currentVersion = versionGroup.currentVersion?.path ?? resolvedVersions.first?.pathString ?? "" 44 | 45 | return CoreDataModel( 46 | path: modelPath, 47 | versions: resolvedVersions, 48 | currentVersion: currentVersion 49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Mappers/Project/ProjectAttribute.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Attributes for project settings that can be retrieved from a `PBXProject`. 4 | enum ProjectAttributeKey: String { 5 | case classPrefix = "CLASSPREFIX" 6 | case organization = "ORGANIZATIONNAME" 7 | case lastUpgradeCheck = "LastUpgradeCheck" 8 | } 9 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Mappers/Project/XcodeProj+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XcodeProj 3 | 4 | /// Errors that may occur while accessing main `PBXProject` information. 5 | enum XcodeProjError: LocalizedError, Equatable { 6 | case noProjectsFound 7 | 8 | var errorDescription: String? { 9 | switch self { 10 | case .noProjectsFound: 11 | return "No `PBXProject` was found in the `.xcodeproj`" 12 | } 13 | } 14 | } 15 | 16 | extension XcodeProj { 17 | func mainPBXProject() throws -> PBXProject { 18 | guard let pbxProject = pbxproj.projects.first else { 19 | throw XcodeProjError.noProjectsFound 20 | } 21 | return pbxProject 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Mappers/Schemes/SchemeDiagnosticsOptions+XCScheme.swift: -------------------------------------------------------------------------------- 1 | import XcodeGraph 2 | import XcodeProj 3 | 4 | extension SchemeDiagnosticsOptions { 5 | /// Creates a SchemeDiagnosticsOptions from a LaunchAction. 6 | init(action: XCScheme.LaunchAction) { 7 | self = SchemeDiagnosticsOptions( 8 | addressSanitizerEnabled: action.enableAddressSanitizer, 9 | detectStackUseAfterReturnEnabled: action.enableASanStackUseAfterReturn, 10 | threadSanitizerEnabled: action.enableThreadSanitizer, 11 | mainThreadCheckerEnabled: !action.disableMainThreadChecker, 12 | performanceAntipatternCheckerEnabled: !action.disablePerformanceAntipatternChecker 13 | ) 14 | } 15 | 16 | /// Creates a SchemeDiagnosticsOptions from a TestAction. 17 | init(action: XCScheme.TestAction) { 18 | self = SchemeDiagnosticsOptions( 19 | addressSanitizerEnabled: action.enableAddressSanitizer, 20 | detectStackUseAfterReturnEnabled: action.enableASanStackUseAfterReturn, 21 | threadSanitizerEnabled: action.enableThreadSanitizer, 22 | mainThreadCheckerEnabled: !action.disableMainThreadChecker 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Mappers/Schemes/XCTestPlan.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct XCTestPlan: Codable { 4 | struct TestTarget: Codable { 5 | let parallelizable: Bool? 6 | let target: TestTargetReference 7 | } 8 | 9 | struct TestTargetReference: Codable { 10 | let containerPath: String 11 | let identifier: String 12 | let name: String 13 | } 14 | 15 | let testTargets: [TestTarget] 16 | } 17 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Mappers/Settings/BuildSettings.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XcodeProj 3 | 4 | /// Keys representing various build settings that may appear in an Xcode project or workspace configuration. 5 | enum BuildSettingKey: String { 6 | case sdkroot = "SDKROOT" 7 | case codeSignOnCopy = "CODE_SIGN_ON_COPY" 8 | case mergedBinaryType = "MERGED_BINARY_TYPE" 9 | case productBundleIdentifier = "PRODUCT_BUNDLE_IDENTIFIER" 10 | case infoPlistFile = "INFOPLIST_FILE" 11 | case codeSignEntitlements = "CODE_SIGN_ENTITLEMENTS" 12 | case iPhoneOSDeploymentTarget = "IPHONEOS_DEPLOYMENT_TARGET" 13 | case macOSDeploymentTarget = "MACOSX_DEPLOYMENT_TARGET" 14 | case watchOSDeploymentTarget = "WATCHOS_DEPLOYMENT_TARGET" 15 | case tvOSDeploymentTarget = "TVOS_DEPLOYMENT_TARGET" 16 | case visionOSDeploymentTarget = "VISIONOS_DEPLOYMENT_TARGET" 17 | } 18 | 19 | extension BuildSettings { 20 | subscript(_ key: BuildSettingKey) -> BuildSetting? { 21 | self[key.rawValue] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Mappers/Settings/XCConfigurationList+Helpers.swift: -------------------------------------------------------------------------------- 1 | import XcodeGraph 2 | import XcodeProj 3 | 4 | extension XCConfigurationList { 5 | /// Retrieves a build setting value from the first configuration in which it is found. 6 | /// 7 | /// - Parameter key: The `BuildSettingKey` to look up. 8 | /// - Returns: The value as a `String` if found, otherwise `nil`. 9 | func stringSettings(for key: BuildSettingKey) -> [BuildConfiguration: String] { 10 | let configurationMatcher = ConfigurationMatcher() 11 | var results = [BuildConfiguration: String]() 12 | for config in buildConfigurations { 13 | if let value = config.buildSettings[key]?.stringValue { 14 | let variant = configurationMatcher.variant(for: config.name) 15 | let buildConfig = BuildConfiguration(name: config.name, variant: variant) 16 | results[buildConfig] = value 17 | } 18 | } 19 | return results 20 | } 21 | 22 | /// Retrieves all deployment target values from all configurations and aggregates them. 23 | /// 24 | /// - Parameters: 25 | /// - keys: A list of keys to search (e.g., `.iPhoneOSDeploymentTarget`, `.macOSDeploymentTarget`) 26 | /// - Returns: A dictionary mapping `BuildSettingKey` to the found value. 27 | func allDeploymentTargets(keys: [BuildSettingKey]) -> [BuildSettingKey: String] { 28 | var results = [BuildSettingKey: String]() 29 | 30 | for key in keys { 31 | for config in buildConfigurations { 32 | if let value = config.buildSettings[key]?.stringValue { 33 | results[key] = value 34 | break // Once found, move to the next key 35 | } 36 | } 37 | } 38 | 39 | return results 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Mappers/Targets/PBXTarget+BuildHeaders.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | extension PBXTarget { 4 | /// Returns the headers build phase, if any. 5 | func headersBuildPhase() throws -> PBXHeadersBuildPhase? { 6 | buildPhases.compactMap { $0 as? PBXHeadersBuildPhase }.first 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Mappers/Targets/PBXTarget+GraphMapping.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XcodeGraph 4 | import XcodeProj 5 | 6 | extension PBXTarget { 7 | /// Attempts to retrieve the bundle identifier from the target's debug build settings, or throws an error if missing. 8 | func bundleIdentifier() throws -> String { 9 | if let bundleId = debugBuildSettings[BuildSettingKey.productBundleIdentifier]?.stringValue { 10 | return bundleId 11 | } else { 12 | return "Unknown" 13 | } 14 | } 15 | 16 | /// Returns an array of all `PBXCopyFilesBuildPhase` instances for this target. 17 | func copyFilesBuildPhases() -> [PBXCopyFilesBuildPhase] { 18 | buildPhases.compactMap { $0 as? PBXCopyFilesBuildPhase } 19 | } 20 | 21 | func mergedBinaryType() throws -> MergedBinaryType { 22 | let mergedBinaryTypeString = debugBuildSettings[BuildSettingKey.mergedBinaryType]?.stringValue 23 | return mergedBinaryTypeString == "automatic" ? .automatic : .disabled 24 | } 25 | 26 | func onDemandResourcesTags() throws -> OnDemandResourcesTags? { 27 | // Currently returns nil, could be extended if needed 28 | return nil 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Mappers/Targets/PBXTargetDependency+PlatformCondition.swift: -------------------------------------------------------------------------------- 1 | import XcodeGraph 2 | import XcodeProj 3 | 4 | /// A mapper for platform-related conditions, extracting platform filters from `PBXTargetDependency`. 5 | extension PBXTargetDependency { 6 | /// Maps the platform filters on a given `PBXTargetDependency` into a `PlatformCondition`. 7 | /// 8 | /// Returns `nil` if no filters apply, meaning the dependency isn't restricted by platform and 9 | /// should be considered available on all platforms. 10 | func platformCondition() -> PlatformCondition? { 11 | var filters = Set(platformFilters ?? []) 12 | if let singleFilter = platformFilter { 13 | filters.insert(singleFilter) 14 | } 15 | 16 | let platformFilters = Set(filters.compactMap { PlatformFilter(rawValue: $0) }) 17 | return PlatformCondition.when(platformFilters) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Utilities/ConfigurationMatcher.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XcodeGraph 3 | 4 | /// A protocol defining methods for determining the variant of a build configuration 5 | /// and validating configuration names. 6 | protocol ConfigurationMatching { 7 | /// Returns the build configuration variant for a given configuration name. 8 | /// 9 | /// This method checks for keywords that identify known variants and defaults to `.debug` if none match. 10 | /// 11 | /// - Parameter name: The name of the build configuration. 12 | /// - Returns: The determined `BuildConfiguration.Variant` for the given name. 13 | func variant(for name: String) -> BuildConfiguration.Variant 14 | 15 | /// Validates that a configuration name is non-empty and contains no whitespace. 16 | /// 17 | /// - Parameter name: The configuration name to validate. 18 | /// - Returns: `true` if the name is valid; `false` otherwise. 19 | func validateConfigurationName(_ name: String) -> Bool 20 | } 21 | 22 | /// A concrete implementation of `ConfigurationMatching` that uses predefined keyword patterns 23 | /// to determine configuration variants. 24 | struct ConfigurationMatcher: ConfigurationMatching { 25 | /// Represents a pattern mapping a set of keywords to a configuration variant. 26 | struct Pattern { 27 | let keywords: Set 28 | let variant: BuildConfiguration.Variant 29 | } 30 | 31 | /// Common patterns for identifying build configuration variants. 32 | let patterns: [Pattern] 33 | 34 | /// Initializes a new `ConfigurationMatcher` with default patterns. 35 | /// 36 | /// - Parameter patterns: An optional array of `Pattern` to override defaults. 37 | init(patterns: [Pattern]? = nil) { 38 | self.patterns = patterns ?? [ 39 | Pattern(keywords: ["debug", "development", "dev"], variant: .debug), 40 | Pattern(keywords: ["release", "prod", "production"], variant: .release), 41 | ] 42 | } 43 | 44 | func variant(for name: String) -> BuildConfiguration.Variant { 45 | let lowercased = name.lowercased() 46 | return patterns.first { pattern in 47 | pattern.keywords.contains(where: { lowercased.contains($0) }) 48 | }?.variant ?? .debug 49 | } 50 | 51 | func validateConfigurationName(_ name: String) -> Bool { 52 | !name.isEmpty && name.rangeOfCharacter(from: .whitespaces) == nil 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Utilities/DeveloperDirectoryProvider.swift: -------------------------------------------------------------------------------- 1 | import Command 2 | import Foundation 3 | import Path 4 | 5 | /// A protocol that obtains the current developer directory (via `xcode-select -p`) asynchronously. 6 | protocol DeveloperDirectoryProviding { 7 | /// Returns the absolute path to the currently selected Xcode’s Developer directory. 8 | /// - Throws: If `xcode-select -p` fails or if the output is invalid. 9 | func developerDirectory() async throws -> AbsolutePath 10 | } 11 | 12 | /// Default implementation of `DeveloperDirectoryProviding` that uses `CommandRunner`. 13 | struct DeveloperDirectoryProvider: DeveloperDirectoryProviding { 14 | private let commandRunner: CommandRunning 15 | 16 | /// Creates a new provider that uses the given `CommandRunning` instance. 17 | /// - Parameter commandRunner: By default, uses `CommandRunner()`. 18 | init(commandRunner: CommandRunning = CommandRunner()) { 19 | self.commandRunner = commandRunner 20 | } 21 | 22 | /// Uses `xcode-select -p` to get the path to the currently selected Xcode’s Developer folder. 23 | /// - Throws: If `xcode-select -p` fails or if the path output is invalid. 24 | /// - Returns: A valid `AbsolutePath` pointing to the developer directory. 25 | func developerDirectory() async throws -> AbsolutePath { 26 | let stream = commandRunner.run(arguments: ["xcode-select", "-p"]) 27 | let rawPath = try await stream.concatenatedString() 28 | .trimmingCharacters(in: .whitespacesAndNewlines) 29 | 30 | return try AbsolutePath(validating: rawPath) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Utilities/Optional+Throwing.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Optional { 4 | /// Unwraps the optional value or throws the provided error if `nil`. 5 | /// 6 | /// - Parameter error: An autoclosure that generates the error to throw if the optional is `nil`. 7 | /// - Returns: The unwrapped value of the optional. 8 | /// - Throws: The provided error if the optional is `nil`. 9 | func throwing(_ error: @autoclosure () -> Error) throws -> Wrapped { 10 | guard let value = self else { throw error() } 11 | return value 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Utilities/PackageInfoLoader.swift: -------------------------------------------------------------------------------- 1 | import Command 2 | import Foundation 3 | import Mockable 4 | import Path 5 | import XcodeGraph 6 | 7 | @Mockable 8 | protocol PackageInfoLoading { 9 | func loadPackageInfo(at path: AbsolutePath) async throws -> PackageInfo 10 | } 11 | 12 | struct PackageInfoLoader: PackageInfoLoading { 13 | private let commandRunner: CommandRunning 14 | private let decoder = JSONDecoder() 15 | 16 | init( 17 | commandRunner: CommandRunning = CommandRunner() 18 | ) { 19 | self.commandRunner = commandRunner 20 | } 21 | 22 | func loadPackageInfo(at path: AbsolutePath) async throws -> PackageInfo { 23 | let output = try await commandRunner.run( 24 | arguments: [ 25 | "swift", 26 | "package", 27 | "--package-path", 28 | path.pathString, 29 | "dump-package", 30 | ] 31 | ) 32 | .concatenatedString() 33 | 34 | let data = Data(output.utf8) 35 | 36 | return try decoder.decode(PackageInfo.self, from: data) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/XcodeGraphMapper/Utilities/ProjectNativeTarget.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | /// Model representing a `PBXNativeTarget` in a give `XcodeProj` 4 | struct ProjectNativeTarget: Equatable { 5 | let nativeTarget: PBXNativeTarget 6 | let project: XcodeProj 7 | } 8 | -------------------------------------------------------------------------------- /Sources/XcodeMetadata/Extensions/FileHandle+ReadHelpers.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension FileHandle { 4 | /// Returns the current offset in the file. 5 | var currentOffset: UInt64 { 6 | offsetInFile 7 | } 8 | 9 | /// Seeks to a specific file offset. 10 | func seek(to offset: UInt64) { 11 | seek(toFileOffset: offset) 12 | } 13 | 14 | /// Reads a value of type `T` from the file handle. 15 | /// - Returns: The value `T` loaded from the next `MemoryLayout.size` bytes. 16 | func read() -> T { 17 | let data = readData(ofLength: MemoryLayout.size) 18 | return data.withUnsafeBytes { $0.load(as: T.self) } 19 | } 20 | 21 | /// Reads a string of a specified length using ASCII encoding. 22 | /// - Parameter length: The number of bytes to read. 23 | /// - Returns: A `String` if decoding succeeds, otherwise `nil`. 24 | func readString(ofLength length: Int) -> String? { 25 | let data = readData(ofLength: length) 26 | return String(data: data, encoding: .ascii) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/XcodeMetadata/Extensions/MachOByteSwapExtensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if canImport(MachO) 3 | import MachO 4 | #else 5 | import MachOKitC 6 | #endif 7 | 8 | extension fat_header { 9 | mutating func swapIfNeeded(_ shouldSwap: Bool) { 10 | guard shouldSwap else { return } 11 | magic = magic.byteSwapped 12 | nfat_arch = nfat_arch.byteSwapped 13 | } 14 | } 15 | 16 | extension fat_arch { 17 | mutating func swapIfNeeded(_ shouldSwap: Bool) { 18 | guard shouldSwap else { return } 19 | cputype = cputype.byteSwapped 20 | cpusubtype = cpusubtype.byteSwapped 21 | offset = offset.byteSwapped 22 | size = size.byteSwapped 23 | align = align.byteSwapped 24 | } 25 | } 26 | 27 | extension mach_header { 28 | mutating func swapIfNeeded(_ shouldSwap: Bool) { 29 | guard shouldSwap else { return } 30 | magic = magic.byteSwapped 31 | cputype = cputype.byteSwapped 32 | cpusubtype = cpusubtype.byteSwapped 33 | filetype = filetype.byteSwapped 34 | ncmds = ncmds.byteSwapped 35 | sizeofcmds = sizeofcmds.byteSwapped 36 | flags = flags.byteSwapped 37 | } 38 | } 39 | 40 | extension mach_header_64 { 41 | mutating func swapIfNeeded(_ shouldSwap: Bool) { 42 | guard shouldSwap else { return } 43 | magic = magic.byteSwapped 44 | cputype = cputype.byteSwapped 45 | cpusubtype = cpusubtype.byteSwapped 46 | filetype = filetype.byteSwapped 47 | ncmds = ncmds.byteSwapped 48 | sizeofcmds = sizeofcmds.byteSwapped 49 | flags = flags.byteSwapped 50 | reserved = reserved.byteSwapped 51 | } 52 | } 53 | 54 | extension load_command { 55 | mutating func swapIfNeeded(_ shouldSwap: Bool) { 56 | guard shouldSwap else { return } 57 | cmd = cmd.byteSwapped 58 | cmdsize = cmdsize.byteSwapped 59 | } 60 | } 61 | 62 | extension uuid_command { 63 | mutating func swapIfNeeded(_ shouldSwap: Bool) { 64 | guard shouldSwap else { return } 65 | cmd = cmd.byteSwapped 66 | cmdsize = cmdsize.byteSwapped 67 | // The uuid field is 16 raw bytes; no integer fields to swap for endianness. 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/XcodeMetadata/Extensions/Sequence+ExecutionContext.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Sequence where Element: Sendable { 4 | /// Taken from: https://github.com/JohnSundell/CollectionConcurrencyKit/blob/b4f23e24b5a1bff301efc5e70871083ca029ff95/Sources/CollectionConcurrencyKit.swift 5 | /// Transform the sequence into an array of new values using 6 | /// an async closure that returns optional values. Only the 7 | /// non-`nil` return values will be included in the new array. 8 | /// 9 | /// The closure calls will be performed in order, by waiting for 10 | /// each call to complete before proceeding with the next one. If 11 | /// any of the closure calls throw an error, then the iteration 12 | /// will be terminated and the error rethrown. 13 | /// 14 | /// - parameter transform: The transform to run on each element. 15 | /// - returns: The transformed values as an array. The order of 16 | /// the transformed values will match the original sequence, 17 | /// except for the values that were transformed into `nil`. 18 | /// - throws: Rethrows any error thrown by the passed closure. 19 | public func serialCompactMap( 20 | _ transform: (Element) async throws -> T? 21 | ) async rethrows -> [T] { 22 | var values = [T]() 23 | 24 | for element in self { 25 | guard let value = try await transform(element) else { 26 | continue 27 | } 28 | 29 | values.append(value) 30 | } 31 | 32 | return values 33 | } 34 | 35 | /// Filter (with execution context) 36 | /// 37 | /// - Parameters: 38 | /// - context: The execution context to perform the `perform` operation with 39 | /// - perform: The perform closure to call on each element in the array 40 | func concurrentFilter(_ filter: @Sendable @escaping (Element) async throws -> Bool) async rethrows -> [Element] { 41 | return try await concurrentCompactMap { 42 | try await filter($0) ? $0 : nil 43 | } 44 | } 45 | 46 | /// Async concurrent compact map 47 | /// 48 | /// - Parameters: 49 | /// - transform: The transformation closure to apply to the array 50 | func concurrentCompactMap(_ transform: @Sendable @escaping (Element) async throws -> B?) async rethrows 51 | -> [B] 52 | { 53 | let tasks = map { element in 54 | Task { 55 | try await transform(element) 56 | } 57 | } 58 | 59 | return try await tasks.serialCompactMap { task in 60 | try await task.value 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyFramework.xcframework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AvailableLibraries 6 | 7 | 8 | LibraryIdentifier 9 | ios-x86_64-simulator 10 | LibraryPath 11 | MyFramework.framework 12 | SupportedArchitectures 13 | 14 | x86_64 15 | 16 | SupportedPlatform 17 | ios 18 | SupportedPlatformVariant 19 | simulator 20 | 21 | 22 | LibraryIdentifier 23 | ios-arm64 24 | LibraryPath 25 | MyFramework.framework 26 | SupportedArchitectures 27 | 28 | arm64 29 | 30 | SupportedPlatform 31 | ios 32 | 33 | 34 | CFBundlePackageType 35 | XFWK 36 | XCFrameworkFormatVersion 37 | 1.0 38 | 39 | 40 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyFramework.xcframework/ios-arm64/MyFramework.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyFramework.xcframework/ios-arm64/MyFramework.framework/Info.plist -------------------------------------------------------------------------------- /Tests/Fixtures/MyFramework.xcframework/ios-arm64/MyFramework.framework/Modules/MyFramework.swiftmodule/arm64-apple-ios.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyFramework.xcframework/ios-arm64/MyFramework.framework/Modules/MyFramework.swiftmodule/arm64-apple-ios.swiftdoc -------------------------------------------------------------------------------- /Tests/Fixtures/MyFramework.xcframework/ios-arm64/MyFramework.framework/Modules/MyFramework.swiftmodule/arm64-apple-ios.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 5.1 (swiftlang-1100.0.270.13 clang-1100.0.33.7) 3 | // swift-module-flags: -target arm64-apple-ios13.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name MyFramework 4 | import Foundation 5 | import Swift 6 | public class MyFramework { 7 | public var name: Swift.String 8 | public init() 9 | @objc deinit 10 | } 11 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyFramework.xcframework/ios-arm64/MyFramework.framework/Modules/MyFramework.swiftmodule/arm64.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyFramework.xcframework/ios-arm64/MyFramework.framework/Modules/MyFramework.swiftmodule/arm64.swiftdoc -------------------------------------------------------------------------------- /Tests/Fixtures/MyFramework.xcframework/ios-arm64/MyFramework.framework/Modules/MyFramework.swiftmodule/arm64.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 5.1 (swiftlang-1100.0.270.13 clang-1100.0.33.7) 3 | // swift-module-flags: -target arm64-apple-ios13.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name MyFramework 4 | import Foundation 5 | import Swift 6 | public class MyFramework { 7 | public var name: Swift.String 8 | public init() 9 | @objc deinit 10 | } 11 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyFramework.xcframework/ios-arm64/MyFramework.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module MyFramework { 2 | header "MyFramework-Swift.h" 3 | requires objc 4 | } 5 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyFramework.xcframework/ios-arm64/MyFramework.framework/MyFramework: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyFramework.xcframework/ios-arm64/MyFramework.framework/MyFramework -------------------------------------------------------------------------------- /Tests/Fixtures/MyFramework.xcframework/ios-x86_64-simulator/MyFramework.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyFramework.xcframework/ios-x86_64-simulator/MyFramework.framework/Info.plist -------------------------------------------------------------------------------- /Tests/Fixtures/MyFramework.xcframework/ios-x86_64-simulator/MyFramework.framework/Modules/MyFramework.swiftmodule/x86_64-apple-ios-simulator.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyFramework.xcframework/ios-x86_64-simulator/MyFramework.framework/Modules/MyFramework.swiftmodule/x86_64-apple-ios-simulator.swiftdoc -------------------------------------------------------------------------------- /Tests/Fixtures/MyFramework.xcframework/ios-x86_64-simulator/MyFramework.framework/Modules/MyFramework.swiftmodule/x86_64-apple-ios-simulator.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 5.1 (swiftlang-1100.0.270.13 clang-1100.0.33.7) 3 | // swift-module-flags: -target x86_64-apple-ios13.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name MyFramework 4 | import Foundation 5 | import Swift 6 | public class MyFramework { 7 | public var name: Swift.String 8 | public init() 9 | @objc deinit 10 | } 11 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyFramework.xcframework/ios-x86_64-simulator/MyFramework.framework/Modules/MyFramework.swiftmodule/x86_64.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyFramework.xcframework/ios-x86_64-simulator/MyFramework.framework/Modules/MyFramework.swiftmodule/x86_64.swiftdoc -------------------------------------------------------------------------------- /Tests/Fixtures/MyFramework.xcframework/ios-x86_64-simulator/MyFramework.framework/Modules/MyFramework.swiftmodule/x86_64.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 5.1 (swiftlang-1100.0.270.13 clang-1100.0.33.7) 3 | // swift-module-flags: -target x86_64-apple-ios13.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name MyFramework 4 | import Foundation 5 | import Swift 6 | public class MyFramework { 7 | public var name: Swift.String 8 | public init() 9 | @objc deinit 10 | } 11 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyFramework.xcframework/ios-x86_64-simulator/MyFramework.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module MyFramework { 2 | header "MyFramework-Swift.h" 3 | requires objc 4 | } 5 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyFramework.xcframework/ios-x86_64-simulator/MyFramework.framework/MyFramework: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyFramework.xcframework/ios-x86_64-simulator/MyFramework.framework/MyFramework -------------------------------------------------------------------------------- /Tests/Fixtures/MyFrameworkMissingArch.xcframework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AvailableLibraries 6 | 7 | 8 | LibraryIdentifier 9 | ios-x86_64-simulator 10 | LibraryPath 11 | MyFrameworkMissingArch.framework 12 | SupportedArchitectures 13 | 14 | x86_64 15 | 16 | SupportedPlatform 17 | ios 18 | SupportedPlatformVariant 19 | simulator 20 | 21 | 22 | LibraryIdentifier 23 | ios-arm64 24 | LibraryPath 25 | MyFrameworkMissingArch.framework 26 | SupportedArchitectures 27 | 28 | arm64 29 | 30 | SupportedPlatform 31 | ios 32 | 33 | 34 | CFBundlePackageType 35 | XFWK 36 | XCFrameworkFormatVersion 37 | 1.0 38 | 39 | 40 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyFrameworkMissingArch.xcframework/ios-arm64/MyFrameworkMissingArch.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyFrameworkMissingArch.xcframework/ios-arm64/MyFrameworkMissingArch.framework/Info.plist -------------------------------------------------------------------------------- /Tests/Fixtures/MyFrameworkMissingArch.xcframework/ios-arm64/MyFrameworkMissingArch.framework/Modules/MyFramework.swiftmodule/arm64-apple-ios.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyFrameworkMissingArch.xcframework/ios-arm64/MyFrameworkMissingArch.framework/Modules/MyFramework.swiftmodule/arm64-apple-ios.swiftdoc -------------------------------------------------------------------------------- /Tests/Fixtures/MyFrameworkMissingArch.xcframework/ios-arm64/MyFrameworkMissingArch.framework/Modules/MyFramework.swiftmodule/arm64-apple-ios.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 5.1 (swiftlang-1100.0.270.13 clang-1100.0.33.7) 3 | // swift-module-flags: -target arm64-apple-ios13.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name MyFramework 4 | import Foundation 5 | import Swift 6 | public class MyFramework { 7 | public var name: Swift.String 8 | public init() 9 | @objc deinit 10 | } 11 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyFrameworkMissingArch.xcframework/ios-arm64/MyFrameworkMissingArch.framework/Modules/MyFramework.swiftmodule/arm64.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyFrameworkMissingArch.xcframework/ios-arm64/MyFrameworkMissingArch.framework/Modules/MyFramework.swiftmodule/arm64.swiftdoc -------------------------------------------------------------------------------- /Tests/Fixtures/MyFrameworkMissingArch.xcframework/ios-arm64/MyFrameworkMissingArch.framework/Modules/MyFramework.swiftmodule/arm64.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 5.1 (swiftlang-1100.0.270.13 clang-1100.0.33.7) 3 | // swift-module-flags: -target arm64-apple-ios13.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name MyFramework 4 | import Foundation 5 | import Swift 6 | public class MyFramework { 7 | public var name: Swift.String 8 | public init() 9 | @objc deinit 10 | } 11 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyFrameworkMissingArch.xcframework/ios-arm64/MyFrameworkMissingArch.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module MyFramework { 2 | header "MyFramework-Swift.h" 3 | requires objc 4 | } 5 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyFrameworkMissingArch.xcframework/ios-arm64/MyFrameworkMissingArch.framework/MyFrameworkMissingArch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyFrameworkMissingArch.xcframework/ios-arm64/MyFrameworkMissingArch.framework/MyFrameworkMissingArch -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AvailableLibraries 6 | 7 | 8 | BinaryPath 9 | MyMergeableFramework.framework/MyMergeableFramework 10 | LibraryIdentifier 11 | ios-x86_64-simulator 12 | LibraryPath 13 | MyMergeableFramework.framework 14 | MergeableMetadata 15 | 16 | SupportedArchitectures 17 | 18 | x86_64 19 | 20 | SupportedPlatform 21 | ios 22 | SupportedPlatformVariant 23 | simulator 24 | 25 | 26 | BinaryPath 27 | MyMergeableFramework.framework/MyMergeableFramework 28 | LibraryIdentifier 29 | ios-arm64 30 | LibraryPath 31 | MyMergeableFramework.framework 32 | MergeableMetadata 33 | 34 | SupportedArchitectures 35 | 36 | arm64 37 | 38 | SupportedPlatform 39 | ios 40 | 41 | 42 | CFBundlePackageType 43 | XFWK 44 | XCFrameworkFormatVersion 45 | 1.1 46 | 47 | 48 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/ios-arm64/MyMergeableFramework.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyMergeableFramework.xcframework/ios-arm64/MyMergeableFramework.framework/Info.plist -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/ios-arm64/MyMergeableFramework.framework/Modules/MyMergeableFramework.swiftmodule/arm64-apple-ios.abi.json: -------------------------------------------------------------------------------- 1 | { 2 | "ABIRoot": { 3 | "kind": "Root", 4 | "name": "TopLevel", 5 | "printedName": "TopLevel", 6 | "json_format_version": 8 7 | }, 8 | "ConstValues": [] 9 | } -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/ios-arm64/MyMergeableFramework.framework/Modules/MyMergeableFramework.swiftmodule/arm64-apple-ios.private.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.2 clang-1500.0.40.1) 3 | // swift-module-flags: -target arm64-apple-ios14.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -Osize -module-name MyMergeableFramework 4 | // swift-module-flags-ignorable: -enable-bare-slash-regex 5 | import Swift 6 | import _Concurrency 7 | import _StringProcessing 8 | import _SwiftConcurrencyShims 9 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/ios-arm64/MyMergeableFramework.framework/Modules/MyMergeableFramework.swiftmodule/arm64-apple-ios.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyMergeableFramework.xcframework/ios-arm64/MyMergeableFramework.framework/Modules/MyMergeableFramework.swiftmodule/arm64-apple-ios.swiftdoc -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/ios-arm64/MyMergeableFramework.framework/Modules/MyMergeableFramework.swiftmodule/arm64-apple-ios.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.2 clang-1500.0.40.1) 3 | // swift-module-flags: -target arm64-apple-ios14.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -Osize -module-name MyMergeableFramework 4 | // swift-module-flags-ignorable: -enable-bare-slash-regex 5 | import Swift 6 | import _Concurrency 7 | import _StringProcessing 8 | import _SwiftConcurrencyShims 9 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/ios-arm64/MyMergeableFramework.framework/Modules/MyMergeableFramework.swiftmodule/arm64-apple-ios.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyMergeableFramework.xcframework/ios-arm64/MyMergeableFramework.framework/Modules/MyMergeableFramework.swiftmodule/arm64-apple-ios.swiftmodule -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/ios-arm64/MyMergeableFramework.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module MyMergeableFramework { 2 | header "MyMergeableFramework-Swift.h" 3 | requires objc 4 | } 5 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/ios-arm64/MyMergeableFramework.framework/MyMergeableFramework: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyMergeableFramework.xcframework/ios-arm64/MyMergeableFramework.framework/MyMergeableFramework -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/ios-x86_64-simulator/MyMergeableFramework.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyMergeableFramework.xcframework/ios-x86_64-simulator/MyMergeableFramework.framework/Info.plist -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/ios-x86_64-simulator/MyMergeableFramework.framework/Modules/MyMergeableFramework.swiftmodule/x86_64-apple-ios-simulator.abi.json: -------------------------------------------------------------------------------- 1 | { 2 | "ABIRoot": { 3 | "kind": "Root", 4 | "name": "TopLevel", 5 | "printedName": "TopLevel", 6 | "json_format_version": 8 7 | }, 8 | "ConstValues": [] 9 | } -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/ios-x86_64-simulator/MyMergeableFramework.framework/Modules/MyMergeableFramework.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.2 clang-1500.0.40.1) 3 | // swift-module-flags: -target x86_64-apple-ios14.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -Osize -module-name MyMergeableFramework 4 | // swift-module-flags-ignorable: -enable-bare-slash-regex 5 | import Swift 6 | import _Concurrency 7 | import _StringProcessing 8 | import _SwiftConcurrencyShims 9 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/ios-x86_64-simulator/MyMergeableFramework.framework/Modules/MyMergeableFramework.swiftmodule/x86_64-apple-ios-simulator.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyMergeableFramework.xcframework/ios-x86_64-simulator/MyMergeableFramework.framework/Modules/MyMergeableFramework.swiftmodule/x86_64-apple-ios-simulator.swiftdoc -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/ios-x86_64-simulator/MyMergeableFramework.framework/Modules/MyMergeableFramework.swiftmodule/x86_64-apple-ios-simulator.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.2 clang-1500.0.40.1) 3 | // swift-module-flags: -target x86_64-apple-ios14.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -Osize -module-name MyMergeableFramework 4 | // swift-module-flags-ignorable: -enable-bare-slash-regex 5 | import Swift 6 | import _Concurrency 7 | import _StringProcessing 8 | import _SwiftConcurrencyShims 9 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/ios-x86_64-simulator/MyMergeableFramework.framework/Modules/MyMergeableFramework.swiftmodule/x86_64-apple-ios-simulator.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyMergeableFramework.xcframework/ios-x86_64-simulator/MyMergeableFramework.framework/Modules/MyMergeableFramework.swiftmodule/x86_64-apple-ios-simulator.swiftmodule -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/ios-x86_64-simulator/MyMergeableFramework.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module MyMergeableFramework { 2 | header "MyMergeableFramework-Swift.h" 3 | requires objc 4 | } 5 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyMergeableFramework.xcframework/ios-x86_64-simulator/MyMergeableFramework.framework/MyMergeableFramework: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyMergeableFramework.xcframework/ios-x86_64-simulator/MyMergeableFramework.framework/MyMergeableFramework -------------------------------------------------------------------------------- /Tests/Fixtures/MyStaticLibrary.xcframework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AvailableLibraries 6 | 7 | 8 | LibraryIdentifier 9 | ios-x86_64-simulator 10 | LibraryPath 11 | libMyStaticLibrary.a 12 | SupportedArchitectures 13 | 14 | x86_64 15 | 16 | SupportedPlatform 17 | ios 18 | SupportedPlatformVariant 19 | simulator 20 | 21 | 22 | LibraryIdentifier 23 | ios-arm64 24 | LibraryPath 25 | libMyStaticLibrary.a 26 | SupportedArchitectures 27 | 28 | arm64 29 | 30 | SupportedPlatform 31 | ios 32 | 33 | 34 | CFBundlePackageType 35 | XFWK 36 | XCFrameworkFormatVersion 37 | 1.0 38 | 39 | 40 | -------------------------------------------------------------------------------- /Tests/Fixtures/MyStaticLibrary.xcframework/ios-arm64/libMyStaticLibrary.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyStaticLibrary.xcframework/ios-arm64/libMyStaticLibrary.a -------------------------------------------------------------------------------- /Tests/Fixtures/MyStaticLibrary.xcframework/ios-x86_64-simulator/libMyStaticLibrary.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/MyStaticLibrary.xcframework/ios-x86_64-simulator/libMyStaticLibrary.a -------------------------------------------------------------------------------- /Tests/Fixtures/libStaticLibrary.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/libStaticLibrary.a -------------------------------------------------------------------------------- /Tests/Fixtures/xpm.framework.dSYM/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleIdentifier 8 | com.apple.xcode.dsym.com.xcodepm.tuist 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundlePackageType 12 | dSYM 13 | CFBundleSignature 14 | ???? 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleVersion 18 | 1 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/Fixtures/xpm.framework.dSYM/Contents/Resources/DWARF/xpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/xpm.framework.dSYM/Contents/Resources/DWARF/xpm -------------------------------------------------------------------------------- /Tests/Fixtures/xpm.framework/Headers/xpm.h: -------------------------------------------------------------------------------- 1 | // 2 | // tuist.h 3 | // tuist 4 | // 5 | // Created by Pedro Piñera Buendía on 03.06.18. 6 | // Copyright © 2018 com.xcodepm. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for tuist. 12 | FOUNDATION_EXPORT double tuistVersionNumber; 13 | 14 | //! Project version string for tuist. 15 | FOUNDATION_EXPORT const unsigned char tuistVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Tests/Fixtures/xpm.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/xpm.framework/Info.plist -------------------------------------------------------------------------------- /Tests/Fixtures/xpm.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module tuist { 2 | umbrella header "tuist.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Tests/Fixtures/xpm.framework/PrivateHeaders/private.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/xpm.framework/PrivateHeaders/private.h -------------------------------------------------------------------------------- /Tests/Fixtures/xpm.framework/xpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuist/XcodeGraph/6daad18195ec32fc31fbb9ad0a5971b966523f4e/Tests/Fixtures/xpm.framework/xpm -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/MapperTests/Phases/PBXCoreDataModelsBuildPhaseMapperTests.swift: -------------------------------------------------------------------------------- 1 | import Testing 2 | import XcodeGraph 3 | import XcodeProj 4 | @testable import XcodeGraphMapper 5 | 6 | @Suite 7 | struct PBXCoreDataModelsBuildPhaseMapperTests { 8 | @Test("Maps CoreData models from version groups within resources phase") 9 | func testMapCoreDataModels() async throws { 10 | // Given 11 | let xcodeProj = try await XcodeProj.test() 12 | let pbxProj = xcodeProj.pbxproj 13 | 14 | let versionChildRef = try PBXFileReference.test( 15 | name: "Model.xcdatamodel", 16 | path: "Model.xcdatamodel" 17 | ) 18 | .add(to: pbxProj) 19 | .addToMainGroup(in: pbxProj) 20 | 21 | let versionGroup = try XCVersionGroup.test( 22 | currentVersion: versionChildRef, 23 | children: [versionChildRef], 24 | path: "Model.xcdatamodeld", 25 | sourceTree: .group, 26 | versionGroupType: "wrapper.xcdatamodel", 27 | name: "Model.xcdatamodeld", 28 | pbxProj: pbxProj 29 | ) 30 | .add(to: pbxProj) 31 | .addToMainGroup(in: pbxProj) 32 | versionGroup.currentVersion?.add(to: pbxProj) 33 | 34 | let buildFile = PBXBuildFile(file: versionGroup).add(to: pbxProj) 35 | let resourcesPhase = PBXResourcesBuildPhase(files: [buildFile]).add(to: pbxProj) 36 | 37 | try PBXNativeTarget.test( 38 | name: "App", 39 | buildPhases: [resourcesPhase], 40 | productType: .application 41 | ) 42 | .add(to: pbxProj) 43 | .add(to: pbxProj.rootObject) 44 | 45 | let mapper = PBXCoreDataModelsBuildPhaseMapper() 46 | 47 | // When 48 | let models = try mapper.map([buildFile], xcodeProj: xcodeProj) 49 | 50 | // Then 51 | #expect(models.count == 1) 52 | let model = try #require(models.first) 53 | #expect(model.path.basename == "Model.xcdatamodeld") 54 | #expect(model.versions.count == 1) 55 | #expect(model.currentVersion.contains("Model.xcdatamodel") == true) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/MapperTests/Phases/PBXHeadersBuildPhaseMapperTests.swift: -------------------------------------------------------------------------------- 1 | import Testing 2 | import XcodeGraph 3 | import XcodeProj 4 | @testable import XcodeGraphMapper 5 | 6 | @Suite 7 | struct PBXHeadersBuildPhaseMapperTests { 8 | @Test("Maps public, private, and project headers from headers phase") 9 | func testMapHeaders() async throws { 10 | // Given 11 | let xcodeProj = try await XcodeProj.test() 12 | let pbxProj = xcodeProj.pbxproj 13 | 14 | let publicHeaderRef = try PBXFileReference.test( 15 | name: "PublicHeader.h", 16 | path: "Include/PublicHeader.h" 17 | ) 18 | .add(to: pbxProj) 19 | .addToMainGroup(in: pbxProj) 20 | 21 | let publicBuildFile = PBXBuildFile( 22 | file: publicHeaderRef, 23 | settings: ["ATTRIBUTES": ["Public"]] 24 | ).add(to: pbxProj) 25 | 26 | let privateHeaderRef = try PBXFileReference.test( 27 | name: "PrivateHeader.h", 28 | path: "Include/PrivateHeader.h" 29 | ) 30 | .add(to: pbxProj) 31 | .addToMainGroup(in: pbxProj) 32 | 33 | let privateBuildFile = PBXBuildFile( 34 | file: privateHeaderRef, 35 | settings: ["ATTRIBUTES": ["Private"]] 36 | ).add(to: pbxProj) 37 | 38 | let projectHeaderRef = try PBXFileReference.test( 39 | name: "ProjectHeader.h", 40 | path: "Include/ProjectHeader.h" 41 | ) 42 | .add(to: pbxProj) 43 | .addToMainGroup(in: pbxProj) 44 | 45 | let projectBuildFile = PBXBuildFile(file: projectHeaderRef).add(to: pbxProj) 46 | 47 | let headersPhase = PBXHeadersBuildPhase( 48 | files: [publicBuildFile, privateBuildFile, projectBuildFile] 49 | ) 50 | .add(to: pbxProj) 51 | 52 | try PBXNativeTarget( 53 | name: "App", 54 | buildPhases: [headersPhase], 55 | productType: .application 56 | ) 57 | .add(to: pbxProj) 58 | .add(to: pbxProj.rootObject) 59 | 60 | let mapper = PBXHeadersBuildPhaseMapper() 61 | 62 | // When 63 | let headers = try mapper.map(headersPhase, xcodeProj: xcodeProj) 64 | 65 | // Then 66 | try #require(headers != nil) 67 | #expect(headers?.public.map(\.basename).contains("PublicHeader.h") == true) 68 | #expect(headers?.private.map(\.basename).contains("PrivateHeader.h") == true) 69 | #expect(headers?.project.map(\.basename).contains("ProjectHeader.h") == true) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/Mocks/AssertionsTesting.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import Testing 4 | 5 | enum AssertionsTesting { 6 | static func fixturePath() -> AbsolutePath { 7 | try! AbsolutePath( 8 | validating: #filePath 9 | ) 10 | .parentDirectory 11 | .parentDirectory 12 | .parentDirectory 13 | .appending(components: "Fixtures") 14 | } 15 | 16 | /// Resolves a fixture path relative to the project's root. 17 | static func fixturePath(path: RelativePath) -> AbsolutePath { 18 | fixturePath().appending(path) 19 | } 20 | } 21 | 22 | extension AbsolutePath: Swift.ExpressibleByStringLiteral { 23 | public init(stringLiteral value: String) { 24 | do { 25 | self = try AbsolutePath(validating: value) 26 | } catch { 27 | Issue.record("Invalid path at: \(value) - Error: \(error)") 28 | self = AbsolutePath("/") 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/Mocks/MockDefaults.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XcodeGraph 4 | @testable import XcodeProj 5 | 6 | enum MockDefaults { 7 | static let defaultDebugSettings: [String: BuildSetting] = [ 8 | "PRODUCT_NAME": "$(TARGET_NAME)", 9 | "ENABLE_STRICT_OBJC_MSGSEND": "YES", 10 | "PRODUCT_BUNDLE_IDENTIFIER": "com.example.debug", 11 | ] 12 | 13 | static let defaultReleaseSettings: [String: BuildSetting] = [ 14 | "PRODUCT_NAME": "$(TARGET_NAME)", 15 | "VALIDATE_PRODUCT": "YES", 16 | "PRODUCT_BUNDLE_IDENTIFIER": "com.example.release", 17 | ] 18 | 19 | nonisolated(unsafe) static let defaultProjectAttributes: [String: ProjectAttribute] = [ 20 | "BuildIndependentTargetsInParallel": "YES", 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/.swift: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/PBXBuildRule+TestData.swift: -------------------------------------------------------------------------------- 1 | import XcodeGraph 2 | import XcodeProj 3 | 4 | extension PBXBuildRule { 5 | static func test( 6 | compilerSpec: String = BuildRule.CompilerSpec.appleClang.rawValue, 7 | fileType: String = BuildRule.FileType.cSource.rawValue, 8 | isEditable: Bool = true, 9 | filePatterns: String? = "*.cpp;*.cxx;*.cc", 10 | name: String = "Default Build Rule", 11 | dependencyFile: String? = nil, 12 | outputFiles: [String] = ["$(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE).o"], 13 | inputFiles: [String] = [], 14 | outputFilesCompilerFlags: [String]? = nil, 15 | script: String? = nil, 16 | runOncePerArchitecture: Bool? = nil 17 | ) -> PBXBuildRule { 18 | PBXBuildRule( 19 | compilerSpec: compilerSpec, 20 | fileType: fileType, 21 | isEditable: isEditable, 22 | filePatterns: filePatterns, 23 | name: name, 24 | dependencyFile: dependencyFile, 25 | outputFiles: outputFiles, 26 | inputFiles: inputFiles, 27 | outputFilesCompilerFlags: outputFilesCompilerFlags, 28 | script: script, 29 | runOncePerArchitecture: runOncePerArchitecture 30 | ) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/PBXFileReference+TestData.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | extension PBXFileReference { 4 | static func test( 5 | sourceTree: PBXSourceTree = .group, 6 | name: String? = nil, 7 | explicitFileType: String? = nil, 8 | path: String = "AppDelegate.swift", 9 | lastKnownFileType: String? = "sourcecode.swift", 10 | includeInIndex: Bool? = nil 11 | ) -> PBXFileReference { 12 | PBXFileReference( 13 | sourceTree: sourceTree, 14 | name: name, 15 | explicitFileType: explicitFileType, 16 | lastKnownFileType: lastKnownFileType, 17 | path: path, 18 | includeInIndex: includeInIndex 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/PBXGroup+TestData.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | extension PBXGroup { 4 | static func test( 5 | children: [PBXFileElement] = [], 6 | sourceTree: PBXSourceTree = .group, 7 | name: String? = "MainGroup", 8 | path: String? = "/tmp/TestProject" 9 | ) -> PBXGroup { 10 | PBXGroup( 11 | children: children, 12 | sourceTree: sourceTree, 13 | name: name, 14 | path: path 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/PBXNativeTarget+TestData.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | extension PBXNativeTarget { 4 | static func test( 5 | name: String = "App", 6 | buildConfigurationList: XCConfigurationList? = nil, 7 | buildRules: [PBXBuildRule] = [PBXBuildRule.test()], 8 | buildPhases: [PBXBuildPhase] = [ 9 | PBXSourcesBuildPhase(files: []), 10 | PBXResourcesBuildPhase(files: []), 11 | PBXFrameworksBuildPhase(files: []), 12 | ], 13 | dependencies: [PBXTargetDependency] = [], 14 | productInstallPath: String? = nil, 15 | productName: String? = nil, 16 | productType: PBXProductType = .application, 17 | product: PBXFileReference? = PBXFileReference.test( 18 | sourceTree: .buildProductsDir, 19 | explicitFileType: "wrapper.application", 20 | path: "App.app", 21 | lastKnownFileType: nil, 22 | includeInIndex: false 23 | ) 24 | ) -> PBXNativeTarget { 25 | PBXNativeTarget( 26 | name: name, 27 | buildConfigurationList: buildConfigurationList, 28 | buildPhases: buildPhases, 29 | buildRules: buildRules, 30 | dependencies: dependencies, 31 | productInstallPath: productInstallPath, 32 | productName: productName ?? name, 33 | product: product, 34 | productType: productType 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/PBXProj+TestData.swift: -------------------------------------------------------------------------------- 1 | import Testing 2 | @testable import XcodeProj 3 | 4 | extension PBXProj { 5 | func add(objects: [PBXObject]) { 6 | objects.forEach { add(object: $0) } 7 | } 8 | } 9 | 10 | extension PBXObject { 11 | @discardableResult 12 | func add(to pbxProj: PBXProj) -> Self { 13 | pbxProj.add(object: self) 14 | 15 | return self 16 | } 17 | } 18 | 19 | extension PBXFileElement { 20 | @discardableResult 21 | func addToMainGroup(in pbxProj: PBXProj) throws -> Self { 22 | let project = try #require(pbxProj.projects.first) 23 | project.mainGroup.children.append(self) 24 | return self 25 | } 26 | } 27 | 28 | extension PBXTarget { 29 | @discardableResult 30 | func add(to pbxProject: PBXProject?) throws -> Self { 31 | let project = try #require(pbxProject) 32 | project.targets.append(self) 33 | return self 34 | } 35 | } 36 | 37 | extension PBXProj { 38 | /// Adds a PBXObject to the project and returns it. 39 | @discardableResult 40 | func addObject(_ object: T) -> T { 41 | add(object: object) 42 | return object 43 | } 44 | 45 | /// Adds a PBXFileReference and optionally attaches it to the main group. 46 | @discardableResult 47 | func addFileReference( 48 | _ file: PBXFileReference, 49 | addToMainGroup: Bool = true 50 | ) -> PBXFileReference { 51 | addObject(file) 52 | 53 | if addToMainGroup, let project = projects.first, 54 | let mainGroup = project.mainGroup 55 | { 56 | mainGroup.children.append(file) 57 | } 58 | 59 | return file 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/PBXShellScriptBuildPhase+TestData.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | extension PBXShellScriptBuildPhase { 4 | static func test( 5 | files: [PBXBuildFile] = [], 6 | name: String? = "Embed Precompiled Frameworks", 7 | shellScript: String = "#!/bin/sh\necho 'Mock Shell Script'", 8 | inputPaths: [String] = [], 9 | outputPaths: [String] = [], 10 | inputFileListPaths: [String]? = nil, 11 | outputFileListPaths: [String]? = nil, 12 | shellPath: String = "/bin/sh", 13 | buildActionMask: UInt = PBXBuildPhase.defaultBuildActionMask, 14 | runOnlyForDeploymentPostprocessing: Bool = false, 15 | showEnvVarsInLog: Bool = true, 16 | alwaysOutOfDate: Bool = false, 17 | dependencyFile: String? = nil 18 | ) -> PBXShellScriptBuildPhase { 19 | PBXShellScriptBuildPhase( 20 | files: files, 21 | name: name, 22 | inputPaths: inputPaths, 23 | outputPaths: outputPaths, 24 | inputFileListPaths: inputFileListPaths, 25 | outputFileListPaths: outputFileListPaths, 26 | shellPath: shellPath, 27 | shellScript: shellScript, 28 | buildActionMask: buildActionMask, 29 | runOnlyForDeploymentPostprocessing: runOnlyForDeploymentPostprocessing, 30 | showEnvVarsInLog: showEnvVarsInLog, 31 | alwaysOutOfDate: alwaysOutOfDate, 32 | dependencyFile: dependencyFile 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/PBXTargetDependency+TestData.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | extension PBXTargetDependency { 4 | static func test( 5 | name: String? = "App", 6 | target: PBXTarget? = nil, 7 | targetProxy: PBXContainerItemProxy? = nil, 8 | platformFilter: String? = nil, 9 | platformFilters: [String]? = nil 10 | ) -> PBXTargetDependency { 11 | PBXTargetDependency( 12 | name: name, 13 | platformFilter: platformFilter, 14 | platformFilters: platformFilters, 15 | target: target, 16 | targetProxy: targetProxy 17 | ) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/PBXVariantGroup+TestData.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | extension PBXVariantGroup { 4 | static func mockVariant( 5 | children: [PBXFileElement] = [], 6 | sourceTree: PBXSourceTree = .group, 7 | name: String? = "MainGroup", 8 | path: String? = "/tmp/TestProject" 9 | ) -> PBXVariantGroup { 10 | PBXVariantGroup( 11 | children: children, 12 | sourceTree: sourceTree, 13 | name: name, 14 | path: path 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/XCBuildConfiguration+TestData.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | extension XCBuildConfiguration { 4 | static func testDebug( 5 | baseConfiguration: PBXFileReference? = nil, 6 | buildSettings: BuildSettings = MockDefaults.defaultDebugSettings 7 | ) -> XCBuildConfiguration { 8 | XCBuildConfiguration( 9 | name: "Debug", 10 | baseConfiguration: baseConfiguration, 11 | buildSettings: buildSettings 12 | ) 13 | } 14 | 15 | static func testRelease( 16 | baseConfiguration: PBXFileReference? = nil, 17 | buildSettings: BuildSettings = MockDefaults.defaultReleaseSettings 18 | ) -> XCBuildConfiguration { 19 | XCBuildConfiguration( 20 | name: "Release", 21 | baseConfiguration: baseConfiguration, 22 | buildSettings: buildSettings 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/XCConfigurationList+TestData.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | extension XCConfigurationList { 4 | static func test( 5 | buildConfigurations: [XCBuildConfiguration] = [], 6 | defaultConfigurationName: String = "Release", 7 | defaultConfigurationIsVisible: Bool = false 8 | ) -> XCConfigurationList { 9 | XCConfigurationList( 10 | buildConfigurations: buildConfigurations, 11 | defaultConfigurationName: defaultConfigurationName, 12 | defaultConfigurationIsVisible: defaultConfigurationIsVisible 13 | ) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/XCScheme+TestData.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | extension XCScheme { 4 | static func test( 5 | name: String = "DefaultScheme", 6 | lastUpgradeVersion: String = "1.3", 7 | version: String = "1.3", 8 | buildAction: BuildAction? = nil, 9 | testAction: TestAction? = nil, 10 | launchAction: LaunchAction? = nil, 11 | archiveAction: ArchiveAction? = nil, 12 | profileAction: ProfileAction? = nil, 13 | analyzeAction: AnalyzeAction? = nil, 14 | wasCreatedForAppExtension: Bool? = nil 15 | ) -> XCScheme { 16 | XCScheme( 17 | name: name, 18 | lastUpgradeVersion: lastUpgradeVersion, 19 | version: version, 20 | buildAction: buildAction, 21 | testAction: testAction, 22 | launchAction: launchAction, 23 | profileAction: profileAction, 24 | analyzeAction: analyzeAction, 25 | archiveAction: archiveAction, 26 | wasCreatedForAppExtension: wasCreatedForAppExtension 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/XCSchemeTestableReference+TestData.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XcodeGraph 4 | @testable import XcodeProj 5 | 6 | extension XCScheme.TestableReference { 7 | static func test( 8 | skipped: Bool, 9 | parallelization: XCScheme.TestParallelization = .none, 10 | randomExecutionOrdering: Bool = false, 11 | buildableReference: XCScheme.BuildableReference, 12 | locationScenarioReference: XCScheme.LocationScenarioReference? = nil, 13 | skippedTests: [XCScheme.TestItem] = [], 14 | selectedTests: [XCScheme.TestItem] = [], 15 | useTestSelectionWhitelist: Bool? = nil 16 | ) -> XCScheme.TestableReference { 17 | XCScheme.TestableReference( 18 | skipped: skipped, 19 | parallelization: parallelization, 20 | randomExecutionOrdering: randomExecutionOrdering, 21 | buildableReference: buildableReference, 22 | locationScenarioReference: locationScenarioReference, 23 | skippedTests: skippedTests, 24 | selectedTests: selectedTests, 25 | useTestSelectionWhitelist: useTestSelectionWhitelist 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/XCUserData+TestData.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | // Tests? 4 | extension XCUserData { 5 | static func test( 6 | userName: String = "user", 7 | schemes: [XCScheme] = [], 8 | schemeManagement: XCSchemeManagement? = XCSchemeManagement( 9 | schemeUserState: [ 10 | XCSchemeManagement.UserStateScheme( 11 | name: "App.xcscheme", 12 | shared: true, 13 | orderHint: 0, 14 | isShown: true 15 | ), 16 | ], 17 | suppressBuildableAutocreation: nil 18 | ) 19 | ) -> XCUserData { 20 | XCUserData( 21 | userName: userName, 22 | schemes: schemes, 23 | breakpoints: nil, 24 | schemeManagement: schemeManagement 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/XCVersionGroup+TestData.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | extension XCVersionGroup { 4 | static func test( 5 | currentVersion: PBXFileReference? = nil, 6 | children: [PBXFileElement] = [], 7 | path: String = "DefaultGroup", 8 | sourceTree: PBXSourceTree = .group, 9 | versionGroupType: String? = nil, 10 | name: String? = nil, 11 | includeInIndex: Bool? = nil, 12 | wrapsLines: Bool? = nil, 13 | usesTabs: Bool? = nil, 14 | indentWidth: UInt? = nil, 15 | tabWidth: UInt? = nil, 16 | pbxProj _: PBXProj 17 | ) -> XCVersionGroup { 18 | let group = XCVersionGroup( 19 | currentVersion: currentVersion, 20 | path: path, 21 | name: name, 22 | sourceTree: sourceTree, 23 | versionGroupType: versionGroupType, 24 | includeInIndex: includeInIndex, 25 | wrapsLines: wrapsLines, 26 | usesTabs: usesTabs, 27 | indentWidth: indentWidth, 28 | tabWidth: tabWidth 29 | ) 30 | 31 | group.children = children 32 | return group 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/XCWorkspace+TestData.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | extension XCWorkspace { 4 | static func test( 5 | files: [String] = [ 6 | "App/MainApp.xcodeproj", 7 | "Framework1/Framework1.xcodeproj", 8 | "StaticFramework1/StaticFramework1.xcodeproj", 9 | ], 10 | path: String 11 | ) -> XCWorkspace { 12 | let children = files.map { path in 13 | XCWorkspaceDataElement.file(XCWorkspaceDataFileRef(location: .group(path))) 14 | } 15 | return XCWorkspace(data: XCWorkspaceData(children: children), path: .init(path)) 16 | } 17 | 18 | static func test(withElements elements: [XCWorkspaceDataElement], path: String) -> XCWorkspace { 19 | let data = XCWorkspaceData(children: elements) 20 | return XCWorkspace(data: data, path: .init(path)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/XCWorkspaceDataElement+TestData.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | extension XCWorkspaceDataElement { 4 | static func test(relativePath: String) -> XCWorkspaceDataElement { 5 | .file(XCWorkspaceDataFileRef(location: .group(relativePath))) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/XCWorkspaceDataGroup+TestData.swift: -------------------------------------------------------------------------------- 1 | import XcodeProj 2 | 3 | import XcodeProj 4 | 5 | extension XCWorkspaceDataElement { 6 | static func test(name: String, children: [XCWorkspaceDataElement]) -> XCWorkspaceDataElement { 7 | .group(XCWorkspaceDataGroup(location: .group(name), name: name, children: children)) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Tests/XcodeGraphMapperTests/TestData/XcodeProj+TestData.swift: -------------------------------------------------------------------------------- 1 | import FileSystem 2 | import Foundation 3 | import Path 4 | import PathKit 5 | import XcodeGraph 6 | import XcodeProj 7 | 8 | extension XcodeProj { 9 | static func test( 10 | projectName: String = "TestProject", 11 | configurationList: XCConfigurationList = XCConfigurationList.test( 12 | buildConfigurations: [.testDebug(), .testRelease()] 13 | ), 14 | targets: [PBXTarget] = [], 15 | pbxProj: PBXProj = PBXProj(), 16 | path: AbsolutePath? = nil 17 | ) async throws -> XcodeProj { 18 | pbxProj.add(object: configurationList) 19 | for config in configurationList.buildConfigurations { 20 | pbxProj.add(object: config) 21 | } 22 | 23 | let sourceDirectory = try await FileSystem().makeTemporaryDirectory(prefix: "test") 24 | 25 | // Minimal project setup: 26 | let mainGroup = PBXGroup.test( 27 | children: [], 28 | sourceTree: .group, 29 | name: "MainGroup", 30 | path: "/tmp/TestProject" 31 | ).add(to: pbxProj) 32 | 33 | let projectRef = PBXFileReference 34 | .test(name: "App", path: "App.xcodeproj") 35 | .add(to: pbxProj) 36 | mainGroup.children.append(projectRef) 37 | 38 | let projects = [ 39 | ["B900DB68213936CC004AEC3E": projectRef], 40 | ] 41 | 42 | let pbxProject = PBXProject.test( 43 | name: projectName, 44 | buildConfigurationList: configurationList, 45 | mainGroup: mainGroup, 46 | projects: projects, 47 | targets: targets 48 | ).add(to: pbxProj) 49 | 50 | pbxProject.mainGroup = mainGroup 51 | pbxProj.add(object: pbxProject) 52 | pbxProj.rootObject = pbxProject 53 | 54 | return XcodeProj( 55 | workspace: XCWorkspace(), 56 | pbxproj: pbxProj, 57 | path: path.map(\.pathString).map { Path($0) } ?? Path("\(sourceDirectory)/\(projectName).xcodeproj") 58 | ) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/DependenciesGraph/DependenciesGraphTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class DependenciesGraphTests: XCTestCase { 7 | func test_codable_xcframework() { 8 | // Given 9 | let subject = DependenciesGraph.test() 10 | 11 | // Then 12 | XCTAssertCodable(subject) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Extensions/SettingsDictionary+ExtrasTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class SettingsDictionaryExtrasTest: XCTestCase { 7 | func testOverlay_addsPlatformSpecifierWhenSettingsDiffer() { 8 | // Given 9 | var settings: [String: SettingValue] = [ 10 | "A": "a value", 11 | "B": "b value", 12 | ] 13 | 14 | // When 15 | settings.overlay(with: [ 16 | "A": "overlayed value", 17 | "B": "b value", 18 | "C": "c value", 19 | ], for: .macOS) 20 | 21 | // Then 22 | XCTAssertEqual(settings, [ 23 | "A[sdk=macosx*]": "overlayed value", 24 | "A": "a value", 25 | "B": "b value", 26 | "C[sdk=macosx*]": "c value", 27 | ]) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Extensions/XCTestCase+Extras.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | extension XCTestCase { 4 | func XCTAssertCodable(_ subject: C, file _: StaticString = #file, line _: UInt = #line) { 5 | let encoder = JSONEncoder() 6 | let decoder = JSONDecoder() 7 | 8 | encoder.outputFormatting = .prettyPrinted 9 | 10 | let data = XCTTry(try encoder.encode(subject)) 11 | let decoded = XCTTry(try decoder.decode(C.self, from: data)) 12 | 13 | XCTAssertEqual(subject, decoded, "The subject is not equal to it's encoded & decoded version") 14 | } 15 | 16 | func XCTTry(_ closure: @autoclosure @escaping () throws -> T, file: StaticString = #filePath, line: UInt = #line) -> T { 17 | var value: T! 18 | do { 19 | value = try closure() 20 | } catch { 21 | XCTFail("The code threw the following error: \(error)", file: file, line: line) 22 | } 23 | return value 24 | } 25 | 26 | func XCTAssertEqualDictionaries( 27 | _ first: [T: Any], 28 | _ second: [T: Any], 29 | file: StaticString = #filePath, 30 | line: UInt = #line 31 | ) { 32 | let firstDictionary = NSDictionary(dictionary: first) 33 | let secondDictioanry = NSDictionary(dictionary: second) 34 | XCTAssertEqual(firstDictionary, secondDictioanry, file: file, line: line) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Graph/GraphDependencyTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class GraphDependencyTests: XCTestCase { 7 | func test_codable_target() { 8 | // Given 9 | let subject = GraphDependency.testTarget() 10 | 11 | // Then 12 | XCTAssertCodable(subject) 13 | } 14 | 15 | func test_codable_framework() { 16 | // Given 17 | let subject = GraphDependency.testFramework() 18 | 19 | // Then 20 | XCTAssertCodable(subject) 21 | } 22 | 23 | func test_isLinkable() { 24 | XCTAssertFalse(GraphDependency.testMacro().isLinkable) 25 | XCTAssertTrue(GraphDependency.testXCFramework().isLinkable) 26 | XCTAssertTrue(GraphDependency.testFramework().isLinkable) 27 | XCTAssertTrue(GraphDependency.testLibrary().isLinkable) 28 | XCTAssertFalse(GraphDependency.testBundle().isLinkable) 29 | XCTAssertTrue(GraphDependency.testPackageProduct().isLinkable) 30 | XCTAssertTrue(GraphDependency.testTarget().isLinkable) 31 | XCTAssertTrue(GraphDependency.testSDK().isLinkable) 32 | } 33 | 34 | func test_isPrecompiledMacro() { 35 | XCTAssertTrue(GraphDependency.testMacro().isPrecompiledMacro) 36 | XCTAssertFalse(GraphDependency.testXCFramework().isPrecompiledMacro) 37 | XCTAssertFalse(GraphDependency.testFramework().isPrecompiledMacro) 38 | XCTAssertFalse(GraphDependency.testLibrary().isPrecompiledMacro) 39 | XCTAssertFalse(GraphDependency.testBundle().isPrecompiledMacro) 40 | XCTAssertFalse(GraphDependency.testPackageProduct().isPrecompiledMacro) 41 | XCTAssertFalse(GraphDependency.testTarget().isPrecompiledMacro) 42 | XCTAssertFalse(GraphDependency.testSDK().isPrecompiledMacro) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Graph/GraphTargetTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class GraphTargetTests: XCTestCase { 7 | func test_codable() { 8 | // Given 9 | let subject = GraphTarget.test() 10 | 11 | // Then 12 | XCTAssertCodable(subject) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Graph/GraphTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class GraphTests: XCTestCase { 8 | func test_codable() { 9 | // Given 10 | let subject = Graph.test(name: "name", path: try! AbsolutePath(validating: "/path/to")) 11 | 12 | // Then 13 | XCTAssertCodable(subject) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/AnalyzeActionTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class AnalyzeActionTests: XCTestCase { 7 | func test_codable() { 8 | // Given 9 | let subject = AnalyzeAction(configurationName: "name") 10 | 11 | // Then 12 | XCTAssertCodable(subject) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/ArchiveActionTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class ArchiveActionTests: XCTestCase { 7 | func test_codable() { 8 | // Given 9 | let subject = ArchiveAction( 10 | configurationName: "name", 11 | revealArchiveInOrganizer: true, 12 | customArchiveName: "archiveName", 13 | preActions: [ 14 | .init( 15 | title: "preActionTitle", 16 | scriptText: "text", 17 | target: nil, 18 | shellPath: nil, 19 | showEnvVarsInLog: false 20 | ), 21 | ], 22 | postActions: [ 23 | .init( 24 | title: "postActionTitle", 25 | scriptText: "text", 26 | target: nil, 27 | shellPath: nil, 28 | showEnvVarsInLog: true 29 | ), 30 | ] 31 | ) 32 | 33 | // Then 34 | XCTAssertCodable(subject) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/ArgumentsTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class ArgumentsTests: XCTestCase { 7 | func test_codable() { 8 | // Given 9 | let subject = Arguments( 10 | environmentVariables: [ 11 | "key": EnvironmentVariable(value: "value", isEnabled: true), 12 | ], 13 | launchArguments: [ 14 | .init( 15 | name: "name", 16 | isEnabled: true 17 | ), 18 | ] 19 | ) 20 | 21 | // Then 22 | XCTAssertCodable(subject) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/BinaryArchitectureTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XcodeGraph 4 | import XCTest 5 | 6 | final class BinaryArchitectureTests: XCTestCase { 7 | func test_rawValue() { 8 | XCTAssertEqual(BinaryArchitecture.x8664.rawValue, "x86_64") 9 | XCTAssertEqual(BinaryArchitecture.i386.rawValue, "i386") 10 | XCTAssertEqual(BinaryArchitecture.armv7.rawValue, "armv7") 11 | XCTAssertEqual(BinaryArchitecture.armv7s.rawValue, "armv7s") 12 | XCTAssertEqual(BinaryArchitecture.arm64.rawValue, "arm64") 13 | XCTAssertEqual(BinaryArchitecture.armv7k.rawValue, "armv7k") 14 | XCTAssertEqual(BinaryArchitecture.arm6432.rawValue, "arm64_32") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/BuildActionTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class BuildActionTests: XCTestCase { 8 | func test_codable() { 9 | // Given 10 | let subject = BuildAction( 11 | targets: [ 12 | .init( 13 | projectPath: try! AbsolutePath(validating: "/path/to/project"), 14 | name: "name" 15 | ), 16 | ], 17 | preActions: [ 18 | .init( 19 | title: "preActionTitle", 20 | scriptText: "text", 21 | target: nil, 22 | shellPath: nil, 23 | showEnvVarsInLog: true 24 | ), 25 | ], 26 | postActions: [ 27 | .init( 28 | title: "postActionTitle", 29 | scriptText: "text", 30 | target: nil, 31 | shellPath: nil, 32 | showEnvVarsInLog: false 33 | ), 34 | ] 35 | ) 36 | 37 | // Then 38 | XCTAssertCodable(subject) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/BuildConfigurationTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class BuildConfigurationTests: XCTestCase { 7 | func test_codable() { 8 | // Given 9 | let subject = BuildConfiguration( 10 | name: "Debug", 11 | variant: .debug 12 | ) 13 | 14 | // Then 15 | XCTAssertCodable(subject) 16 | } 17 | 18 | func test_name_returnsTheRightValue_whenDebug() { 19 | XCTAssertEqual(BuildConfiguration.debug.name, "Debug") 20 | } 21 | 22 | func test_name_returnsTheRightValue_whenRelease() { 23 | XCTAssertEqual(BuildConfiguration.release.name, "Release") 24 | } 25 | 26 | func test_hashValue() { 27 | XCTAssertEqual( 28 | BuildConfiguration(name: "Debug", variant: .debug).hashValue, 29 | BuildConfiguration(name: "Debug", variant: .debug).hashValue 30 | ) 31 | XCTAssertEqual( 32 | BuildConfiguration(name: "Debug", variant: .debug).hashValue, 33 | BuildConfiguration.debug.hashValue 34 | ) 35 | XCTAssertEqual( 36 | BuildConfiguration(name: "debug", variant: .debug).hashValue, 37 | BuildConfiguration.debug.hashValue 38 | ) 39 | XCTAssertNotEqual( 40 | BuildConfiguration(name: "Debug", variant: .debug).hashValue, 41 | BuildConfiguration.release.hashValue 42 | ) 43 | XCTAssertNotEqual( 44 | BuildConfiguration(name: "debug", variant: .debug).hashValue, 45 | BuildConfiguration.release.hashValue 46 | ) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/BuildRule.CompilerSpecTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class BuildRuleCompilerSpecTests: XCTestCase { 7 | func test_codable() { 8 | // Given 9 | let subject = BuildRule.CompilerSpec.customScript 10 | 11 | // Then 12 | XCTAssertCodable(subject) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/BuildRule.FileTypeTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class BuildRuleFileTypeTests: XCTestCase { 7 | func test_codable() { 8 | // Given 9 | let subject = BuildRule.FileType.sourceFilesWithNamesMatching 10 | 11 | // Then 12 | XCTAssertCodable(subject) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/BuildRuleTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class BuildRuleTests: XCTestCase { 7 | func test_codable() { 8 | // Given 9 | let subject = BuildRule( 10 | compilerSpec: .unifdef, 11 | fileType: .sourceFilesWithNamesMatching 12 | ) 13 | 14 | // Then 15 | XCTAssertCodable(subject) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/CopyFileElementTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class CopyFileElementTests: XCTestCase { 8 | func test_codable_file() { 9 | // Given 10 | let subject = CopyFileElement.file(path: try! AbsolutePath(validating: "/path/to/file"), condition: .when([.macos])) 11 | 12 | // Then 13 | XCTAssertCodable(subject) 14 | } 15 | 16 | func test_codable_folderReference() { 17 | // Given 18 | let subject = CopyFileElement.folderReference( 19 | path: try! AbsolutePath(validating: "/folder/reference"), 20 | condition: .when([.macos]) 21 | ) 22 | 23 | // Then 24 | XCTAssertCodable(subject) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/CopyFilesActionTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class CopyFilesActionTests: XCTestCase { 8 | func test_codable() { 9 | // Given 10 | let subject = CopyFilesAction( 11 | name: "name", 12 | destination: .frameworks, 13 | subpath: "subpath", 14 | files: [ 15 | .file(path: try! AbsolutePath(validating: "/path/to/file")), 16 | ] 17 | ) 18 | 19 | // Then 20 | XCTAssertCodable(subject) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/CoreDataModelTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class CoreDataModelTests: XCTestCase { 8 | func test_codable() { 9 | // Given 10 | let subject = CoreDataModel( 11 | path: try! AbsolutePath(validating: "/path/to/model"), 12 | versions: [ 13 | try! AbsolutePath(validating: "/path/to/version"), 14 | ], 15 | currentVersion: "1.1.1" 16 | ) 17 | 18 | // Then 19 | XCTAssertCodable(subject) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/ExecutionActionTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class ExecutionActionTests: XCTestCase { 8 | func test_codable() { 9 | // Given 10 | let subject = ExecutionAction( 11 | title: "title", 12 | scriptText: "text", 13 | target: .init( 14 | projectPath: try! AbsolutePath(validating: "/path/to/project"), 15 | name: "name" 16 | ), 17 | shellPath: nil, 18 | showEnvVarsInLog: false 19 | ) 20 | 21 | // Then 22 | XCTAssertCodable(subject) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/FileElementTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class FileElementTests: XCTestCase { 8 | func test_codable_file() { 9 | // Given 10 | let subject = FileElement.file(path: try! AbsolutePath(validating: "/path/to/file")) 11 | 12 | // Then 13 | XCTAssertCodable(subject) 14 | } 15 | 16 | func test_codable_folderReference() { 17 | // Given 18 | let subject = FileElement.folderReference(path: try! AbsolutePath(validating: "/folder/reference")) 19 | 20 | // Then 21 | XCTAssertCodable(subject) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/HeadersTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class HeadersTests: XCTestCase { 8 | func test_codable() { 9 | // Given 10 | let subject = Headers( 11 | public: [ 12 | try! AbsolutePath(validating: "/path/to/public/header"), 13 | ], 14 | private: [ 15 | try! AbsolutePath(validating: "/path/to/private/header"), 16 | ], 17 | project: [ 18 | try! AbsolutePath(validating: "/path/to/project/header"), 19 | ] 20 | ) 21 | 22 | // Then 23 | XCTAssertCodable(subject) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/IDETemplateMacrosTests.swift: -------------------------------------------------------------------------------- 1 | import XcodeGraph 2 | import XCTest 3 | 4 | final class IDETemplateMacrosTests: XCTestCase { 5 | func test_removing_leading_comment_slashes() { 6 | // Given 7 | let fileHeader = "// Some template" 8 | let templateMacros = IDETemplateMacros(fileHeader: fileHeader) 9 | 10 | // Then 11 | XCTAssertEqual(" Some template", templateMacros.fileHeader) 12 | } 13 | 14 | func test_space_preservation_if_leading_comment_slashes_are_present() { 15 | // Given 16 | let fileHeader = "//Some template" 17 | let templateMacros = IDETemplateMacros(fileHeader: fileHeader) 18 | 19 | // Then 20 | XCTAssertEqual("Some template", templateMacros.fileHeader) 21 | } 22 | 23 | func test_removing_trailing_newline() { 24 | // Given 25 | let fileHeader = "Some template\n" 26 | let templateMacros = IDETemplateMacros(fileHeader: fileHeader) 27 | 28 | // Then 29 | XCTAssertEqual(" Some template", templateMacros.fileHeader) 30 | } 31 | 32 | func test_inserting_leading_space() { 33 | // Given 34 | let fileHeader = "Some template" 35 | let templateMacros = IDETemplateMacros(fileHeader: fileHeader) 36 | 37 | // Then 38 | XCTAssertEqual(" Some template", templateMacros.fileHeader) 39 | } 40 | 41 | func test_not_inserting_leading_space_if_already_present() { 42 | // Given 43 | let fileHeader = " Some template" 44 | let templateMacros = IDETemplateMacros(fileHeader: fileHeader) 45 | 46 | // Then 47 | XCTAssertEqual(" Some template", templateMacros.fileHeader) 48 | } 49 | 50 | func test_not_inserting_leading_space_if_starting_with_newline() { 51 | // Given 52 | let fileHeader = "\nSome template" 53 | let templateMacros = IDETemplateMacros(fileHeader: fileHeader) 54 | 55 | // Then 56 | XCTAssertEqual("\nSome template", templateMacros.fileHeader) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/InfoPlistTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class InfoPlistTests: XCTestCase { 8 | func test_codable_file() { 9 | // Given 10 | let subject = InfoPlist.file(path: try! AbsolutePath(validating: "/path/to/file")) 11 | 12 | // Then 13 | XCTAssertCodable(subject) 14 | } 15 | 16 | func test_codable_dictionary() { 17 | // Given 18 | let subject = InfoPlist.dictionary([ 19 | "key1": "value1", 20 | "key2": "value2", 21 | "key3": "value3", 22 | ]) 23 | 24 | // Then 25 | XCTAssertCodable(subject) 26 | } 27 | 28 | func test_path_when_file() { 29 | // Given 30 | let path = try! AbsolutePath(validating: "/path/Info.list") 31 | let subject: InfoPlist = .file(path: path) 32 | 33 | // Then 34 | XCTAssertEqual(subject.path, path) 35 | } 36 | 37 | func test_expressive_by_string_literal() { 38 | // Given 39 | let subject: InfoPlist = "/path/Info.list" 40 | 41 | // Then 42 | XCTAssertEqual(subject.path, try AbsolutePath(validating: "/path/Info.list")) 43 | } 44 | 45 | func test_expressive_by_string_literal_using_build_variable() { 46 | // Given 47 | let subject1: InfoPlist = "$(CONFIGURATION)/Info.list" 48 | let subject2: InfoPlist = "${CONFIGURATION}/Info.list" 49 | 50 | // Then 51 | XCTAssertEqual(subject1, .variable("$(CONFIGURATION)/Info.list", configuration: nil)) 52 | XCTAssertEqual(subject2, .variable("${CONFIGURATION}/Info.list", configuration: nil)) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/PackageTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class PackageTests: XCTestCase { 8 | func test_codable_local() { 9 | // Given 10 | let subject = Package.local(path: try! AbsolutePath(validating: "/path/to/workspace")) 11 | 12 | // Then 13 | XCTAssertCodable(subject) 14 | } 15 | 16 | func test_codable_remote() { 17 | // Given 18 | let subject = Package.remote( 19 | url: "/url/to/package", 20 | requirement: .branch("branch") 21 | ) 22 | 23 | // Then 24 | XCTAssertCodable(subject) 25 | } 26 | 27 | func test_is_remote_local() { 28 | // Given 29 | let subject = Package.local(path: try! AbsolutePath(validating: "/path/to/package")) 30 | 31 | // Then 32 | XCTAssertFalse(subject.isRemote) 33 | } 34 | 35 | func test_is_remote_remote() { 36 | // Given 37 | let subject = Package.remote( 38 | url: "/url/to/package", 39 | requirement: .branch("branch") 40 | ) 41 | 42 | // Then 43 | XCTAssertTrue(subject.isRemote) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/PlatformFilterTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class PlatformFilterTests: XCTestCase { 7 | func test_xcodeprojValue() { 8 | XCTAssertEqual(PlatformFilter.catalyst.xcodeprojValue, "maccatalyst") 9 | XCTAssertEqual(PlatformFilter.ios.xcodeprojValue, "ios") 10 | XCTAssertEqual(PlatformFilter.driverkit.xcodeprojValue, "driverkit") 11 | XCTAssertEqual(PlatformFilter.macos.xcodeprojValue, "macos") 12 | XCTAssertEqual(PlatformFilter.tvos.xcodeprojValue, "tvos") 13 | XCTAssertEqual(PlatformFilter.watchos.xcodeprojValue, "watchos") 14 | } 15 | 16 | func test_platformfilters_xcodeprojValue() { 17 | func xcodeProjValueFor(_ filters: PlatformFilters) -> [String] { 18 | filters.xcodeprojValue 19 | } 20 | 21 | XCTAssertEqual(xcodeProjValueFor([.ios, .macos]), ["ios", "macos"]) 22 | XCTAssertEqual(xcodeProjValueFor([.macos, .ios]), ["ios", "macos"]) 23 | XCTAssertEqual(xcodeProjValueFor([.tvos, .macos, .ios]), ["ios", "macos", "tvos"]) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/ProfileActionTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class ProfileActionTests: XCTestCase { 8 | func test_codable() { 9 | // Given 10 | let subject = ProfileAction( 11 | configurationName: "name", 12 | executable: .init( 13 | projectPath: try! AbsolutePath(validating: "/path/to/project"), 14 | name: "name" 15 | ), 16 | arguments: .init( 17 | environmentVariables: [ 18 | "key": EnvironmentVariable(value: "value", isEnabled: true), 19 | ], 20 | launchArguments: [ 21 | .init( 22 | name: "name", 23 | isEnabled: false 24 | ), 25 | ] 26 | ) 27 | ) 28 | 29 | // Then 30 | XCTAssertCodable(subject) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/ProjectGroupTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class ProjectGroupTests: XCTestCase { 7 | func test_codable() { 8 | // Given 9 | let subject = ProjectGroup.group(name: "name") 10 | 11 | // Then 12 | XCTAssertCodable(subject) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/ProjectTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | @testable import XcodeGraph 5 | 6 | final class ProjectTests: XCTestCase { 7 | func test_codable() { 8 | // Given 9 | let framework = Target.test(name: "Framework", product: .framework) 10 | let app = Target.test(name: "App", product: .app) 11 | let appTests = Target.test(name: "AppTets", product: .unitTests) 12 | let frameworkTests = Target.test(name: "FrameworkTests", product: .unitTests) 13 | let subject = Project.test(targets: [ 14 | framework, app, appTests, frameworkTests, 15 | ]) 16 | 17 | // Then 18 | XCTAssertCodable(subject) 19 | } 20 | 21 | func test_defaultDebugBuildConfigurationName_when_defaultDebugConfigExists() { 22 | // Given 23 | let project = Project.test(settings: Settings.test()) 24 | 25 | // When 26 | let got = project.defaultDebugBuildConfigurationName 27 | 28 | // Then 29 | XCTAssertEqual(got, "Debug") 30 | } 31 | 32 | func test_defaultDebugBuildConfigurationName_when_defaultDebugConfigDoesntExist() { 33 | // Given 34 | let settings = Settings.test(base: [:], configurations: [.debug("Test"): Configuration.test()]) 35 | let project = Project.test(settings: settings) 36 | 37 | // When 38 | let got = project.defaultDebugBuildConfigurationName 39 | 40 | // Then 41 | XCTAssertEqual(got, "Test") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/RawScriptBuildPhaseTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class RawScriptBuildPhaseTests: XCTestCase { 7 | func test_codable() { 8 | // Given 9 | let subject = RawScriptBuildPhase( 10 | name: "name", 11 | script: "script", 12 | showEnvVarsInLog: true, 13 | hashable: true 14 | ) 15 | 16 | // Then 17 | XCTAssertCodable(subject) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/RequirementTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class RequirementTests: XCTestCase { 7 | func test_codable_range() { 8 | // Given 9 | let subject = Requirement.range(from: "1.0.0", to: "2.0.0") 10 | 11 | // Then 12 | XCTAssertCodable(subject) 13 | } 14 | 15 | func test_codable_upToNextMajor() { 16 | // Given 17 | let subject = Requirement.upToNextMajor("1.2.3") 18 | 19 | // Then 20 | XCTAssertCodable(subject) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/ResourceFileElementTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class ResourceFileElementTests: XCTestCase { 8 | func test_codable_file() { 9 | // Given 10 | let subject = ResourceFileElement.file( 11 | path: try! AbsolutePath(validating: "/path/to/element"), 12 | tags: [ 13 | "tag", 14 | ] 15 | ) 16 | 17 | // Then 18 | XCTAssertCodable(subject) 19 | } 20 | 21 | func test_codable_folderReference() { 22 | // Given 23 | let subject = ResourceFileElement.folderReference( 24 | path: try! AbsolutePath(validating: "/path/to/folder"), 25 | tags: [ 26 | "tag", 27 | ] 28 | ) 29 | 30 | // Then 31 | XCTAssertCodable(subject) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/ResourceSynthesizerTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class ResourceSynthesizerTests: XCTestCase { 7 | func test_codable() { 8 | // Given 9 | let subject = ResourceSynthesizer( 10 | parser: .coreData, 11 | parserOptions: ["key": "value"], 12 | extensions: [ 13 | "extension1", 14 | "extension2", 15 | ], 16 | template: .defaultTemplate("template") 17 | ) 18 | 19 | // Then 20 | XCTAssertCodable(subject) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/RunActionTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class RunActionTests: XCTestCase { 8 | func test_codable() { 9 | // Given 10 | let subject = RunAction( 11 | configurationName: "name", 12 | attachDebugger: true, 13 | customLLDBInitFile: try! AbsolutePath(validating: "/path/to/project"), 14 | executable: .init( 15 | projectPath: try! AbsolutePath(validating: "/path/to/project"), 16 | name: "name" 17 | ), 18 | filePath: try! AbsolutePath(validating: "/path/to/file"), 19 | arguments: .init( 20 | environmentVariables: [ 21 | "key": EnvironmentVariable(value: "value", isEnabled: true), 22 | ], 23 | launchArguments: [ 24 | .init( 25 | name: "name", 26 | isEnabled: true 27 | ), 28 | ] 29 | ), 30 | options: .init(), 31 | diagnosticsOptions: SchemeDiagnosticsOptions( 32 | mainThreadCheckerEnabled: true, 33 | performanceAntipatternCheckerEnabled: true 34 | ) 35 | ) 36 | 37 | // Then 38 | XCTAssertCodable(subject) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/SDKSourceTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class SDKSourceTests: XCTestCase { 7 | func test_codable_developer() { 8 | // Given 9 | let subject = SDKSource.developer 10 | 11 | // Then 12 | XCTAssertCodable(subject) 13 | } 14 | 15 | func test_codable_system() { 16 | // Given 17 | let subject = SDKSource.system 18 | 19 | // Then 20 | XCTAssertCodable(subject) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/SchemeTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class SchemeTests: XCTestCase { 7 | func test_codable() { 8 | // Given 9 | let subject = Scheme.test(name: "name", shared: true) 10 | 11 | // Then 12 | XCTAssertCodable(subject) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/SourceFileTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class SourceFileTests: XCTestCase { 8 | func test_codable() { 9 | // Given 10 | let subject = SourceFile( 11 | path: try! AbsolutePath(validating: "/path/to/file"), 12 | compilerFlags: "flag", 13 | contentHash: "hash" 14 | ) 15 | 16 | // Then 17 | XCTAssertCodable(subject) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/TargetReferenceTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class TargetReferenceTests: XCTestCase { 8 | func test_codable() { 9 | // Given 10 | let subject = TargetReference( 11 | projectPath: try! AbsolutePath(validating: "/path/to/project"), 12 | name: "name" 13 | ) 14 | 15 | // Then 16 | XCTAssertCodable(subject) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/TargetScriptTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | @testable import XcodeGraph 5 | 6 | private let script = """ 7 | echo 'Hello World' 8 | wd=$(pwd) 9 | echo "$wd" 10 | """ 11 | 12 | final class TargetScriptTests: XCTestCase { 13 | func test_codable() { 14 | // Given 15 | let subject = TargetScript(name: "name", order: .pre, script: .embedded(script)) 16 | 17 | // Then 18 | XCTAssertCodable(subject) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/TestActionTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class TestActionTests: XCTestCase { 7 | func test_codable() { 8 | // Given 9 | let subject = TestAction.test() 10 | 11 | // Then 12 | XCTAssertCodable(subject) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/TestPlanTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class TestPlanTests: XCTestCase { 8 | func test_codable() { 9 | // Given 10 | let subject = TestPlan( 11 | path: try! AbsolutePath(validating: "/path/to"), 12 | testTargets: [], 13 | isDefault: true 14 | ) 15 | 16 | // Then 17 | XCTAssertCodable(subject) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/TestableTargetTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class TestableTargetTests: XCTestCase { 8 | func test_codable_with_deprecated_parallelizable() { 9 | // Given 10 | let subject = TestableTarget.test( 11 | target: .init( 12 | projectPath: try! AbsolutePath(validating: "/path/to/project"), 13 | name: "name" 14 | ), 15 | skipped: true, 16 | parallelizable: true, 17 | randomExecutionOrdering: true 18 | ) 19 | 20 | // Then 21 | XCTAssertCodable(subject) 22 | } 23 | 24 | func test_codable() { 25 | // Given 26 | let subject = TestableTarget( 27 | target: .init( 28 | projectPath: try! AbsolutePath(validating: "/path/to/project"), 29 | name: "name" 30 | ), 31 | skipped: true, 32 | parallelization: .all, 33 | randomExecutionOrdering: true 34 | ) 35 | 36 | // Then 37 | XCTAssertCodable(subject) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/VersionTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XcodeGraph 3 | import XCTest 4 | 5 | final class VersionTests: XCTestCase { 6 | func test_xcodeStringValue() { 7 | // Given 8 | let version = Version(stringLiteral: "1.2.3") 9 | 10 | // When 11 | let got = version.xcodeStringValue 12 | 13 | // Then 14 | XCTAssertEqual(got, "123") 15 | } 16 | 17 | func test_codable() { 18 | // Given 19 | let version = Version(stringLiteral: "1.2.3") 20 | 21 | // Then 22 | XCTAssertCodable(version) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/WorkspaceGenerationOptionsTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import XcodeGraph 5 | 6 | final class WorkspaceGenerationOptionsTests: XCTestCase { 7 | func test_codable_whenDefault() { 8 | // Given 9 | let subject = Workspace.GenerationOptions.test() 10 | 11 | // Then 12 | XCTAssertCodable(subject) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/WorkspaceTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | 5 | @testable import XcodeGraph 6 | 7 | final class WorkspaceTests: XCTestCase { 8 | func test_codable() { 9 | // Given 10 | let subject = Workspace.test( 11 | path: try! AbsolutePath(validating: "/path/to/workspace"), 12 | name: "name" 13 | ) 14 | 15 | // Then 16 | XCTAssertCodable(subject) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/XcodeGraphTests/Models/XCFrameworkInfoPlistTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import XCTest 4 | @testable import XcodeGraph 5 | 6 | final class XCFrameworkInfoPlistTests: XCTestCase { 7 | func test_codable() throws { 8 | // Given 9 | let subject: XCFrameworkInfoPlist = .test() 10 | 11 | // Then 12 | XCTAssertCodable(subject) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/XcodeMetadataTests/AssertionsTesting.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Path 3 | import Testing 4 | 5 | enum AssertionsTesting { 6 | // MARK: - Fixtures 7 | 8 | /// Resolves a fixture path relative to the project's root. 9 | static func fixturePath(path: RelativePath) -> AbsolutePath { 10 | try! AbsolutePath( 11 | validating: #filePath 12 | ) 13 | .parentDirectory 14 | .parentDirectory 15 | .appending(components: "Fixtures") 16 | .appending(path) 17 | } 18 | } 19 | 20 | extension AbsolutePath: Swift.ExpressibleByStringLiteral { 21 | public init(stringLiteral value: String) { 22 | do { 23 | self = try AbsolutePath(validating: value) 24 | } catch { 25 | Issue.record("Invalid path at: \(value) - Error: \(error)") 26 | self = AbsolutePath("/") 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/XcodeMetadataTests/FrameworkMetadataProviderTests.swift: -------------------------------------------------------------------------------- 1 | import Path 2 | import Testing 3 | import XcodeGraph 4 | import XcodeMetadata 5 | 6 | @Suite 7 | struct FrameworkMetadataProviderTests { 8 | var subject: FrameworkMetadataProvider 9 | 10 | init() { 11 | subject = FrameworkMetadataProvider() 12 | } 13 | 14 | @Test 15 | func loadMetadata() async throws { 16 | // Given 17 | let frameworkPath = AssertionsTesting.fixturePath(path: try RelativePath(validating: "xpm.framework")) 18 | 19 | // When 20 | let metadata = try await subject.loadMetadata(at: frameworkPath, status: .required) 21 | 22 | // Then 23 | let expectedBinaryPath = frameworkPath.appending(component: frameworkPath.basenameWithoutExt) 24 | let expectedDsymPath = frameworkPath.parentDirectory.appending(component: "xpm.framework.dSYM") 25 | 26 | #expect( 27 | metadata == FrameworkMetadata( 28 | path: frameworkPath, 29 | binaryPath: expectedBinaryPath, 30 | dsymPath: expectedDsymPath, 31 | bcsymbolmapPaths: [], 32 | linking: .dynamic, 33 | architectures: [.x8664, .arm64], 34 | status: .required 35 | ), 36 | "Loaded metadata does not match expected metadata" 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/XcodeMetadataTests/LibraryMetadataProviderTests.swift: -------------------------------------------------------------------------------- 1 | import Path 2 | import Testing 3 | import XcodeGraph 4 | @testable import XcodeMetadata 5 | 6 | @Suite 7 | struct LibraryMetadataProviderTests { 8 | var subject: LibraryMetadataProvider 9 | 10 | /// Initializes the test suite, setting up the required `LibraryMetadataProvider` instance. 11 | init() { 12 | subject = LibraryMetadataProvider() 13 | } 14 | 15 | @Test 16 | func loadMetadata() async throws { 17 | // Given 18 | let libraryPath = AssertionsTesting.fixturePath(path: try RelativePath(validating: "libStaticLibrary.a")) 19 | 20 | // When 21 | let metadata = try await subject.loadMetadata( 22 | at: libraryPath, 23 | publicHeaders: libraryPath.parentDirectory, 24 | swiftModuleMap: nil 25 | ) 26 | 27 | // Then 28 | #expect( 29 | metadata == LibraryMetadata( 30 | path: libraryPath, 31 | publicHeaders: libraryPath.parentDirectory, 32 | swiftModuleMap: nil, 33 | architectures: [.x8664], 34 | linking: .static 35 | ), 36 | "Loaded metadata does not match expected metadata" 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tuist.swift: -------------------------------------------------------------------------------- 1 | import ProjectDescription 2 | 3 | let config = Config( 4 | cloud: .cloud( 5 | projectId: "tuist/xcodegraph", 6 | url: "https://cloud.tuist.io", 7 | options: [.optional] 8 | ), 9 | swiftVersion: .init("5.10") 10 | ) 11 | -------------------------------------------------------------------------------- /Workspace.swift: -------------------------------------------------------------------------------- 1 | import ProjectDescription 2 | 3 | let workspace = Workspace( 4 | name: "XcodeGraph", 5 | projects: ["."], 6 | generationOptions: .options(autogeneratedWorkspaceSchemes: .disabled) 7 | ) 8 | -------------------------------------------------------------------------------- /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 | } 15 | ], 16 | "lockFileMaintenance": { 17 | "enabled": true, 18 | "automerge": true 19 | } 20 | } 21 | --------------------------------------------------------------------------------