Repository: yonaskolb/XcodeGen Branch: master Commit: 3e6e5e387859 Files: 412 Total size: 1.8 MB Directory structure: gitextract_4pjcqkl7/ ├── .gitattributes ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .swiftformat ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Docs/ │ ├── Examples.md │ ├── FAQ.md │ ├── ProjectSpec.md │ └── Usage.md ├── LICENSE ├── Makefile ├── Package.resolved ├── Package.resources ├── Package.swift ├── README.md ├── RELEASE.md ├── SettingPresets/ │ ├── Configs/ │ │ ├── debug.yml │ │ └── release.yml │ ├── Platforms/ │ │ ├── iOS.yml │ │ ├── macOS.yml │ │ ├── tvOS.yml │ │ ├── visionOS.yml │ │ └── watchOS.yml │ ├── Product_Platform/ │ │ ├── app-extension_macOS.yml │ │ ├── application_iOS.yml │ │ ├── application_macOS.yml │ │ ├── application_tvOS.yml │ │ ├── application_visionOS.yml │ │ ├── application_watchOS.yml │ │ └── bundle.unit-test_macOS.yml │ ├── Products/ │ │ ├── app-extension.intents-service.yml │ │ ├── app-extension.messages.yml │ │ ├── app-extension.yml │ │ ├── bundle.ui-testing.yml │ │ ├── bundle.unit-test.yml │ │ ├── framework.static.yml │ │ ├── framework.yml │ │ ├── library.static.yml │ │ ├── tv-app-extension.yml │ │ └── watchkit2-extension.yml │ ├── SupportedDestinations/ │ │ ├── iOS.yml │ │ ├── macCatalyst.yml │ │ ├── macOS.yml │ │ ├── tvOS.yml │ │ ├── visionOS.yml │ │ └── watchOS.yml │ └── base.yml ├── Sources/ │ ├── ProjectSpec/ │ │ ├── AggregateTarget.swift │ │ ├── Array+Extension.swift │ │ ├── Breakpoint.swift │ │ ├── BuildPhaseSpec.swift │ │ ├── BuildRule.swift │ │ ├── BuildScript.swift │ │ ├── BuildSettingsContainer.swift │ │ ├── BuildSettingsExtractor.swift │ │ ├── BuildToolPlugin.swift │ │ ├── CacheFile.swift │ │ ├── Config.swift │ │ ├── Decoding.swift │ │ ├── Dependency.swift │ │ ├── DeploymentTarget.swift │ │ ├── Dictionary+Extension.swift │ │ ├── Encoding.swift │ │ ├── FileType.swift │ │ ├── GroupOrdering.swift │ │ ├── Linkage.swift │ │ ├── NSRegularExpressionExtensions.swift │ │ ├── PathContainer.swift │ │ ├── Platform.swift │ │ ├── Plist.swift │ │ ├── Project.swift │ │ ├── ProjectReference.swift │ │ ├── ProjectTarget.swift │ │ ├── Scheme.swift │ │ ├── Settings.swift │ │ ├── SourceType.swift │ │ ├── SpecFile.swift │ │ ├── SpecLoader.swift │ │ ├── SpecOptions.swift │ │ ├── SpecParsingError.swift │ │ ├── SpecValidation.swift │ │ ├── SpecValidationError.swift │ │ ├── SupportedDestination.swift │ │ ├── SwiftPackage.swift │ │ ├── Target.swift │ │ ├── TargetReference.swift │ │ ├── TargetScheme.swift │ │ ├── TargetSource.swift │ │ ├── Template.swift │ │ ├── TestPlan.swift │ │ ├── TestTargeReference.swift │ │ ├── VersionExtensions.swift │ │ ├── XCProjExtensions.swift │ │ └── Yaml.swift │ ├── TestSupport/ │ │ └── TestHelpers.swift │ ├── XcodeGen/ │ │ └── main.swift │ ├── XcodeGenCLI/ │ │ ├── Arguments.swift │ │ ├── Commands/ │ │ │ ├── CacheCommand.swift │ │ │ ├── DumpCommand.swift │ │ │ ├── GenerateCommand.swift │ │ │ └── ProjectCommand.swift │ │ ├── GenerationError.swift │ │ └── XcodeGenCLI.swift │ ├── XcodeGenCore/ │ │ ├── ArrayExtensions.swift │ │ ├── Atomic.swift │ │ ├── Glob.swift │ │ ├── MD5.swift │ │ ├── PathExtensions.swift │ │ └── StringDiff.swift │ └── XcodeGenKit/ │ ├── BreakpointGenerator.swift │ ├── CarthageDependencyResolver.swift │ ├── CarthageVersionLoader.swift │ ├── FileWriter.swift │ ├── InfoPlistGenerator.swift │ ├── PBXProjGenerator.swift │ ├── ProjectFormat.swift │ ├── ProjectGenerator.swift │ ├── SchemeGenerator.swift │ ├── SettingsBuilder.swift │ ├── SettingsPresetFile.swift │ ├── SourceGenerator.swift │ ├── StringCatalogDecoding.swift │ ├── Version.swift │ └── XCProjExtensions.swift ├── Tests/ │ ├── FixtureTests/ │ │ └── FixtureTests.swift │ ├── Fixtures/ │ │ ├── CarthageProject/ │ │ │ ├── .gitignore │ │ │ ├── Cartfile │ │ │ ├── Cartfile.resolved │ │ │ ├── Carthage/ │ │ │ │ └── Build/ │ │ │ │ ├── .Alamofire.version │ │ │ │ ├── .CarthageTestFixture.version │ │ │ │ ├── .ReactiveCocoa.version │ │ │ │ ├── .ReactiveSwift.version │ │ │ │ └── .Result.version │ │ │ ├── Project.xcodeproj/ │ │ │ │ ├── project.pbxproj │ │ │ │ └── project.xcworkspace/ │ │ │ │ └── contents.xcworkspacedata │ │ │ └── project.yml │ │ ├── SPM/ │ │ │ ├── FooFeature/ │ │ │ │ ├── Package.swift │ │ │ │ └── Sources/ │ │ │ │ ├── FooDomain/ │ │ │ │ │ └── FooDomain.swift │ │ │ │ └── FooUI/ │ │ │ │ └── FooUI.swift │ │ │ ├── SPM/ │ │ │ │ ├── App.xctestplan │ │ │ │ ├── AppDelegate.swift │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ └── Info.plist │ │ │ ├── SPM.xcodeproj/ │ │ │ │ ├── project.pbxproj │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ └── swiftpm/ │ │ │ │ │ └── Package.resolved │ │ │ │ └── xcshareddata/ │ │ │ │ └── xcschemes/ │ │ │ │ └── App.xcscheme │ │ │ ├── SPMTests/ │ │ │ │ └── SPMTests.swift │ │ │ ├── StaticLibrary/ │ │ │ │ └── StaticLibrary.swift │ │ │ └── project.yml │ │ ├── TestProject/ │ │ │ ├── .gitignore │ │ │ ├── .lldbinit │ │ │ ├── AnotherProject/ │ │ │ │ ├── AnotherProject.xcodeproj/ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ └── project.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ └── project.yml │ │ │ ├── App_Clip/ │ │ │ │ ├── AppDelegate.swift │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Base.lproj/ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ └── Main.storyboard │ │ │ │ ├── Clip.entitlements │ │ │ │ ├── Info.plist │ │ │ │ └── ViewController.swift │ │ │ ├── App_Clip_Tests/ │ │ │ │ ├── Info.plist │ │ │ │ └── TestProjectTests.swift │ │ │ ├── App_Clip_UITests/ │ │ │ │ ├── Info.plist │ │ │ │ └── TestProjectUITests.swift │ │ │ ├── App_iOS/ │ │ │ │ ├── App.entitlements │ │ │ │ ├── AppDelegate.swift │ │ │ │ ├── App_iOS.xctestplan │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Base.lproj/ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ ├── Localizable.strings │ │ │ │ │ ├── Localizable.stringsdict │ │ │ │ │ ├── LocalizedStoryboard.storyboard │ │ │ │ │ ├── Main.storyboard │ │ │ │ │ └── excluded-file │ │ │ │ ├── Configuration.storekit │ │ │ │ ├── Documentation.docc/ │ │ │ │ │ └── Documentation.md │ │ │ │ ├── FolderWithDot2.0/ │ │ │ │ │ └── SwiftFileInDotPath.swift │ │ │ │ ├── Info.plist │ │ │ │ ├── Model.xcdatamodeld/ │ │ │ │ │ ├── .xccurrentversion │ │ │ │ │ ├── Model 2.xcdatamodel/ │ │ │ │ │ │ └── contents │ │ │ │ │ ├── Model 3.xcdatamodel/ │ │ │ │ │ │ └── contents │ │ │ │ │ └── Model.xcdatamodel/ │ │ │ │ │ └── contents │ │ │ │ ├── Model.xcmappingmodel/ │ │ │ │ │ └── xcmapping.xml │ │ │ │ ├── Resource.abc │ │ │ │ ├── Resource.abcd/ │ │ │ │ │ └── File.json │ │ │ │ ├── Settings.bundle/ │ │ │ │ │ ├── Root.plist │ │ │ │ │ └── en.lproj/ │ │ │ │ │ └── Root.strings │ │ │ │ ├── TestIcon.icon/ │ │ │ │ │ └── icon.json │ │ │ │ ├── ViewController.swift │ │ │ │ ├── en.lproj/ │ │ │ │ │ ├── Localizable.strings │ │ │ │ │ ├── Localizable.stringsdict │ │ │ │ │ └── LocalizedStoryboard.strings │ │ │ │ ├── excluded-file │ │ │ │ ├── inputList.xcfilelist │ │ │ │ ├── module.modulemap │ │ │ │ └── outputList.xcfilelist │ │ │ ├── App_iOS_Tests/ │ │ │ │ ├── Info.plist │ │ │ │ └── TestProjectTests.swift │ │ │ ├── App_iOS_UITests/ │ │ │ │ ├── Info.plist │ │ │ │ └── TestProjectUITests.swift │ │ │ ├── App_macOS/ │ │ │ │ ├── App-Info.plist │ │ │ │ ├── AppDelegate.swift │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Base.lproj/ │ │ │ │ │ └── Main.storyboard │ │ │ │ └── ViewController.swift │ │ │ ├── App_macOS_Tests/ │ │ │ │ ├── Info.plist │ │ │ │ └── TestProjectTests.swift │ │ │ ├── App_supportedDestinations/ │ │ │ │ ├── Info.generated.plist │ │ │ │ ├── Sources/ │ │ │ │ │ ├── MyAppApp.swift │ │ │ │ │ ├── iOS/ │ │ │ │ │ │ └── ContentView.swift │ │ │ │ │ └── tvOS/ │ │ │ │ │ └── ContentView.swift │ │ │ │ ├── Storyboards/ │ │ │ │ │ └── LaunchScreen.storyboard │ │ │ │ └── TestResources/ │ │ │ │ ├── File_MACCATALYST.swift │ │ │ │ ├── File_ios.swift │ │ │ │ ├── File_macOS.swift │ │ │ │ ├── File_tvOs.swift │ │ │ │ ├── TVOS/ │ │ │ │ │ └── File_B.swift │ │ │ │ ├── iOs/ │ │ │ │ │ └── File_A.swift │ │ │ │ ├── macCatalyst/ │ │ │ │ │ └── File_D.swift │ │ │ │ └── macos/ │ │ │ │ └── File_C.swift │ │ │ ├── App_watchOS/ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Base.lproj/ │ │ │ │ │ └── Interface.storyboard │ │ │ │ └── Info.plist │ │ │ ├── App_watchOS Extension/ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ └── Complication.complicationset/ │ │ │ │ │ ├── Circular.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── Extra Large.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Modular.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Utilitarian.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── ExtensionDelegate.swift │ │ │ │ ├── Info.plist │ │ │ │ ├── InterfaceController.swift │ │ │ │ ├── NotificationController.swift │ │ │ │ └── PushNotificationPayload.apns │ │ │ ├── Cartfile │ │ │ ├── Cartfile.resolved │ │ │ ├── Configs/ │ │ │ │ ├── base.xcconfig │ │ │ │ └── config.xcconfig │ │ │ ├── CopyFiles/ │ │ │ │ └── Empty.h │ │ │ ├── CrossOverlayFramework/ │ │ │ │ ├── CrossOverlayFramework.swiftcrossimport/ │ │ │ │ │ └── Framework.swiftoverlay │ │ │ │ ├── FrameworkFile.swift │ │ │ │ ├── Info.plist │ │ │ │ ├── MyFramework.h │ │ │ │ └── Project.xcodeproj/ │ │ │ │ └── project.pbxproj │ │ │ ├── DriverKit Driver/ │ │ │ │ ├── Driver.cpp │ │ │ │ ├── Driver.entitlements │ │ │ │ ├── Driver.iig │ │ │ │ └── Info.plist │ │ │ ├── EndpointSecurity Extension/ │ │ │ │ ├── EndpointSecurity.entitlements │ │ │ │ ├── Info.plist │ │ │ │ └── main.swift │ │ │ ├── ExtensionKit Extension/ │ │ │ │ ├── EntryPoint.swift │ │ │ │ ├── Info.plist │ │ │ │ └── Intent.swift │ │ │ ├── FileGroup/ │ │ │ │ └── UnderFileGroup/ │ │ │ │ └── MoreUnder.swift │ │ │ ├── Folder/ │ │ │ │ ├── Folder1/ │ │ │ │ │ └── file.file │ │ │ │ └── Folder2/ │ │ │ │ └── file.file │ │ │ ├── Framework/ │ │ │ │ ├── FrameworkFile.swift │ │ │ │ ├── Info.plist │ │ │ │ ├── MyFramework.h │ │ │ │ └── Project.xcodeproj/ │ │ │ │ └── project.pbxproj │ │ │ ├── Group/ │ │ │ │ └── File1.swift │ │ │ ├── Group2/ │ │ │ │ └── File2.swift │ │ │ ├── Headers/ │ │ │ │ ├── Folder1/ │ │ │ │ │ └── Header2.h │ │ │ │ └── Header1.h │ │ │ ├── Mintfile │ │ │ ├── NestedFiles/ │ │ │ │ └── Foo/ │ │ │ │ └── Nested.swift │ │ │ ├── Network Extension/ │ │ │ │ ├── FilterDataProvider.swift │ │ │ │ ├── Info.plist │ │ │ │ ├── NetworkExtension.entitlements │ │ │ │ └── main.swift │ │ │ ├── Project.xcodeproj/ │ │ │ │ ├── project.pbxproj │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ └── xcshareddata/ │ │ │ │ ├── xcdebugger/ │ │ │ │ │ └── Breakpoints_v2.xcbkptlist │ │ │ │ └── xcschemes/ │ │ │ │ ├── App_Clip.xcscheme │ │ │ │ ├── App_Scheme.xcscheme │ │ │ │ ├── App_iOS Production.xcscheme │ │ │ │ ├── App_iOS Staging.xcscheme │ │ │ │ ├── App_iOS Test.xcscheme │ │ │ │ ├── App_macOS.xcscheme │ │ │ │ ├── App_watchOS.xcscheme │ │ │ │ ├── DriverKitDriver.xcscheme │ │ │ │ ├── EndpointSecuritySystemExtension.xcscheme │ │ │ │ ├── Framework.xcscheme │ │ │ │ ├── NetworkSystemExtension.xcscheme │ │ │ │ ├── Tool.xcscheme │ │ │ │ ├── iMessageApp.xcscheme │ │ │ │ └── iMessageExtension.xcscheme │ │ │ ├── Resources/ │ │ │ │ ├── GoogleService-Info.plist │ │ │ │ └── MyBundle.bundle/ │ │ │ │ └── file.text │ │ │ ├── SomeFile │ │ │ ├── StandaloneFiles/ │ │ │ │ ├── Standalone.swift │ │ │ │ └── StandaloneAssets.xcassets/ │ │ │ │ └── Contents.json │ │ │ ├── StaticLibrary_ObjC/ │ │ │ │ ├── Module/ │ │ │ │ │ └── module.modulemap │ │ │ │ ├── StaticLibrary_ObjC.h │ │ │ │ └── StaticLibrary_ObjC.m │ │ │ ├── StaticLibrary_Swift/ │ │ │ │ └── StaticLibrary.swift │ │ │ ├── String Catalogs/ │ │ │ │ └── LocalizableStrings.xcstrings │ │ │ ├── SyncedFolder/ │ │ │ │ ├── ExcludedFile.swift │ │ │ │ ├── FeatureATests/ │ │ │ │ │ └── __Snapshots__/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── FeatureBTests/ │ │ │ │ │ └── __Snapshots__/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── Info.plist │ │ │ │ ├── Resources/ │ │ │ │ │ └── .gitkeep │ │ │ │ └── SyncedFile.swift │ │ │ ├── SyncedParent/ │ │ │ │ └── SyncedChild/ │ │ │ │ └── SyncedChildFile.swift │ │ │ ├── Tool/ │ │ │ │ └── main.swift │ │ │ ├── Utilities/ │ │ │ │ └── MyPlayground.playground/ │ │ │ │ ├── Contents.swift │ │ │ │ ├── contents.xcplayground │ │ │ │ └── playground.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata/ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ ├── Vendor/ │ │ │ │ └── SomeXPCService.xpc/ │ │ │ │ └── Contents/ │ │ │ │ ├── Info.plist │ │ │ │ ├── MacOS/ │ │ │ │ │ └── XPC Service │ │ │ │ └── _CodeSignature/ │ │ │ │ └── CodeResources │ │ │ ├── Workspace.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata/ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── swiftpm/ │ │ │ │ └── Package.resolved │ │ │ ├── XPC Service/ │ │ │ │ ├── Info.plist │ │ │ │ ├── XPC_Service.h │ │ │ │ ├── XPC_Service.m │ │ │ │ ├── XPC_ServiceProtocol.h │ │ │ │ └── main.m │ │ │ ├── build.sh │ │ │ ├── carthage_dynamic.xcconfig │ │ │ ├── carthage_static.xcconfig │ │ │ ├── environments.yml │ │ │ ├── excluded-file │ │ │ ├── fixtures.xcconfig │ │ │ ├── iMessageApp/ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ └── Info.plist │ │ │ ├── iMessageExtension/ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── iMessage App Icon.stickersiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Base.lproj/ │ │ │ │ │ └── MainInterface.storyboard │ │ │ │ ├── Info.plist │ │ │ │ └── MessagesViewController.swift │ │ │ ├── iMessageStickers/ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ └── Info.plist │ │ │ ├── project.yml │ │ │ ├── scripts/ │ │ │ │ ├── script.sh │ │ │ │ └── strip-frameworks.sh │ │ │ └── xcode12_13_and_14_workaround.xcconfig │ │ ├── duplicated_include/ │ │ │ ├── different_path/ │ │ │ │ └── duplicated_import_root.yml │ │ │ ├── duplicated_import_root.yml │ │ │ ├── duplicated_import_sut.yml │ │ │ └── duplicated_import_transitive.yml │ │ ├── include_test.json │ │ ├── include_test.yml │ │ ├── included.yml │ │ ├── included_additional.yml │ │ ├── invalid_configs/ │ │ │ ├── invalid_configs_value_non_mapping_aggregate_targets.yml │ │ │ ├── invalid_configs_value_non_mapping_setting_groups.yml │ │ │ ├── invalid_configs_value_non_mapping_settings.yml │ │ │ └── invalid_configs_value_non_mapping_targets.yml │ │ ├── legacy_paths_test/ │ │ │ ├── legacy_included_paths_test.yml │ │ │ └── recursive_include.yml │ │ ├── legacy_paths_test.yml │ │ ├── paths_test/ │ │ │ ├── included_paths_test.yml │ │ │ ├── recursive_test/ │ │ │ │ └── recursive_test.yml │ │ │ ├── relative_file_groups/ │ │ │ │ ├── TestFile.md │ │ │ │ └── inc.yml │ │ │ ├── relative_local_package/ │ │ │ │ ├── LocalPackage/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── Package.swift │ │ │ │ │ └── Sources/ │ │ │ │ │ └── LocalPackage/ │ │ │ │ │ └── LocalPackage.swift │ │ │ │ └── inc.yml │ │ │ └── same_relative_path_test/ │ │ │ ├── parent1/ │ │ │ │ ├── parent1.yml │ │ │ │ └── same/ │ │ │ │ ├── same.yml │ │ │ │ └── target1/ │ │ │ │ └── target1.yml │ │ │ ├── parent2/ │ │ │ │ ├── parent2.yml │ │ │ │ └── same/ │ │ │ │ ├── same.yml │ │ │ │ └── target2/ │ │ │ │ └── target2.yml │ │ │ └── same_relative_path_test.yml │ │ ├── paths_test.yml │ │ ├── scheme_test/ │ │ │ ├── TestProject.xcodeproj/ │ │ │ │ ├── project.pbxproj │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ └── xcshareddata/ │ │ │ │ └── xcschemes/ │ │ │ │ ├── ExternalTarget.xcscheme │ │ │ │ └── Shared_TargetScheme.xcscheme │ │ │ └── test_project.yml │ │ ├── settings_test.yml │ │ ├── test.xcconfig │ │ ├── variables_test.yml │ │ └── yaml.yml │ ├── LinuxMain.swift │ ├── PerformanceTests/ │ │ ├── PerformanceTests.swift │ │ └── TestProject.swift │ ├── ProjectSpecTests/ │ │ ├── Dictionary+Extension_Tests.swift │ │ ├── InvalidConfigsFormatTests.swift │ │ ├── ProjectSpecTests.swift │ │ └── SpecLoadingTests.swift │ ├── XcodeGenCoreTests/ │ │ ├── ArrayExtensionsTests.swift │ │ ├── AtomicTests.swift │ │ ├── GlobTests.swift │ │ └── PathExtensionsTests.swift │ └── XcodeGenKitTests/ │ ├── BreakpointGeneratorTests.swift │ ├── CarthageDependencyResolverTests.swift │ ├── PBXProjGeneratorTests.swift │ ├── ProjectGeneratorTests.swift │ ├── SchemeGeneratorTests.swift │ └── SourceGeneratorTests.swift ├── _config.yml └── scripts/ ├── archive.sh ├── build-fixtures.sh ├── diff-fixtures.sh ├── gen-fixtures.sh └── install.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ CHANGELOG merge=union ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: {} pull_request: {} jobs: run: runs-on: macos-15 name: Xcode ${{ matrix.xcode }} strategy: matrix: xcode: ["16.4", "26.2"] env: DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer steps: - uses: actions/checkout@master - name: Resolve run: swift package resolve - name: Build run: swift build - name: Test run: set -o pipefail && swift test 2>&1 | xcpretty - name: Gen fixtures run: scripts/gen-fixtures.sh - name: Check fixtures run: scripts/diff-fixtures.sh - name: Build fixtures env: GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: scripts/build-fixtures.sh run-linux: runs-on: ubuntu-latest name: Linux steps: - uses: actions/checkout@master - name: Build and run tests run: swift test --enable-test-discovery ================================================ FILE: .gitignore ================================================ .DS_Store .swiftpm /.build /Packages xcuserdata *.xccheckout *.xcuserstate XcodeGen.xcodeproj xcodegen.zip xcodegen.artifactbundle.zip .vscode/launch.json DerivedData .context ================================================ FILE: .swiftformat ================================================ --exclude .build --exclude .swiftpm --swiftversion 5.1 --disable redundantSelf --disable sortedImports --disable blankLinesAtStartOfScope --disable blankLinesAtEndOfScope --disable unusedArguments --disable hoistPatternLet --disable numberFormatting --disable redundantRawValues --disable andOperator --ifdef noindent --ranges nospace ================================================ FILE: CHANGELOG.md ================================================ # Change Log ## Next Version ## 2.45.3 ### Fixed - Fix folder source PBXFileReference path regression with createIntermediateGroups #1605 @yonaskolb - Fix synced folders: includes silently ignored + no deduplication across targets #1604 @4brunu ## 2.45.2 ### Fixed - Validate empty source paths to prevent project root inclusion #1601 @yonaskolb - Fix missing productRefGroup in generated projects #1591 @ruslic19 ## 2.45.1 ### Added - Added built in `.icon` folder support for IconComposer #1600 @yonaskolb ## 2.45.0 ### Added - Added ability to specify project format version via `projectFormat` option #1566 @anivaros - Added `explicitFolders` property to `TargetSource` that is passed through to `PBXFileSystemSynchronizedRootGroup`, to turn entire subfolders into Resources #1596 @macguru - Allow synced folders to be sorted using `groupOrdering` #1596 @macguru - Added `excludes` support for `syncedFolder` sources with glob pattern matching #1587 @mirkokg ### Fixed - Fixed synced folders ignoring `createIntermediateGroups=YES` and always being created at the root level #1596 @macguru - Fix membership exceptions not working for nested synced folders with intermediate groups enabled #1596 @macguru - Fix `supportedDestinations` presets being injected when `settingPresets` is `none` #1599 @macguru - Automatically exclude `Info.plist` from synced folder membership when it's within the synced folder #1587 @mirkokg - Add empty copy resources build phase for synced folders so resources are copied correctly #1587 @mirkokg ### Internal - Update to XcodeProj 9.10.1 #1597 @yonaskolb - Fix CI: add explicit xcodebuild destinations and update Xcode matrix #1594 @yonaskolb - Update ArtifactBundleGen to 0.0.8 #1570 @georgenavarro ## 2.44.1 ### Fixed - Set the correct object version of 77 for Xcode 16 projects @jakobfelsatdm #1563 - Support major.minor SPM package versions which would otherwise fail to decode to a string in yaml specs #1546 @RomanPodymov - Fix regression for `parallelizable` in scheme. It now resolves to "Enabled" and not "Swift Testing Only" #1565 @CraigSiemens ## 2.44.0 ### Added - Basic support for Xcode 16's synchronized folders #1541 @yonaskolb - `TargetSource.type` can now be `syncedFolder` - `Options.defaultSourceDirectoryType` can be set to `syncedFolder` for the default type in all sources in the project (defaults to `group`) - Benefits include faster generation and no cache invalidation or need to regenerate when files are added or removed from these folders - Note that not all TargetSource options like excludes are supported, just a simple path. Please test and see what is missing in your projects - Added sanitizer options to run and test actions in Scheme #1550 @hi-kumar ### Fixed - Added validation to ensure that all values in `settings.configs` are mappings. Previously, passing non-mapping values did not raise an error, making it difficult to detect misconfigurations. Now, `SpecParsingError.invalidConfigsMappingFormat` is thrown if misused. #1547 @Ryu0118 - Use `USER` instead of `LOGNAME` for XCUserData #1559 @KostyaSha ## 2.43.0 ### Added - Added `excludeFromProject` from local packages #1512 @maximkrouk - Added support for `preferredScreenCaptureFormat` in schemes #1450 @vakhidbetrakhmadov ### Changes - `.appex` files are now copied to plugins directory by default #1531 @iljaiwas - The `preGenCommand` is now run before validation and caching #1500 #1519 @simonbs @dalemyers - Improve performance of spec validation #1522 @zewuchen - The `enableGPUValidationMode` enum is deprecated and is now a boolean #1515 @marcosgriselli @yonaskolb ### Fixed - **Breaking**: `fileGroups` are now relative paths when in included files, like other paths #1534 @shnhrrsn - **Breaking**: Local package paths are now relative paths when in included files, like other paths #1498 @juri - Optional groups are no longer skipped when missing and generating projects from a different directory #1529 @SSheldon ### Internal - Fix Swift 6.0 warnings #1513 @marcosgriselli - Update package swift tools to 5.9 #1489 @0111b - Add Xcode 16 to CI #1439 @giginet - Fix test project building on CI #1537 @yonaskolb - Skip failing tests on Linux #1517 @marcosgriselli - XcodeProj updated to 8.24.3 #1515 @marcosgriselli @yonaskolb ## 2.42.0 ### Added - Better support for local Swift packages in Xcode 15 #1465 @kinnarr - Added `macroExpansion` to test actions in schemes #1468 @erneestoc ### Changed - Better default macroExpansion target in schemes #1471 @erneestoc ### Removed - Removed `xcodegen dump --type graphviz` as graphviz no longer builds in Swift 6 and is no longer maintained. If anyone uses this feature and wishes to keep it, please submit a PR providing a suitable alternative. #1485 @giginet ## 2.41.0 ### Added - Added `xcodegen cache` command that writes the cache. Useful for `post-commit` git hook integration #1476 @yonaskolb ### Changed - Include folders in file sorting #1466 @jflan-dd ### Fixed - Fixed `supportedDestinations` validation when it contains watchOS for multiplatform apps. #1470 @tatsuky ## 2.40.1 ### Fixed - Reverted `.xcprivacy` handling. They will now again be treated as resources by default @yonaskolb ## 2.40.0 ### Added - Added support for local Swift packages at the project root by specifying a "" group #1413 @hiltonc - Added a custom `shell` to a scheme's pre and post actions #1430 @balazs-vimn ### Changed - `.xcprivacy` files are now not added to any build phases by default #1464 @yonaskolb ## 2.39.1 ### Added - Proper defaults for `.cp` and `.cxx` files #1447 @eschwieb ### Fixed - Fixed bundle access crash #1448 @freddi-kit - Pinned XcodeProj version to fix breaking changes when XcodeGen is used as a dependency #1449 @yonaskolb ## 2.39.0 ### Added - Support Artifact Bundle #1388 @freddi-kit - Added support for `.xcstrings` String Catalogs #1421 @nicolasbosi95 - Added default `LD_RUNPATH_SEARCH_PATHS` for visionOS #1444 @Dahlgren - Added `watchOS` as a supported cross platform destination #1438 @tatsuky ### Fixed - Fixed custom local package groups not being created #1416 @JaapManenschijn - Fixed spec validation error type #1439 @Lutzifer - Create parent group for local package groups if it does not exist already #1417 @JaapManenschijn ### Internal - Updated Rainbow version #1424 @nysander ## 2.38.0 ### Added - [Multi-destination targets](https://github.com/yonaskolb/XcodeGen/blob/master/Docs/ProjectSpec.md#supported-destinations) #1336 @amatig - Added `supportedDestinations` to target - Added optional new `platform` value of `auto` when using `supportedDestinations` - Added `destinationFilters` for sources and dependencies - Added `inferDestinationFiltersByPath`, a convenience filter for sources - `.mlpackage` files now default to being a source type #1398 @aaron-foreflight - Added support for `Build Tool Plug-ins` in `AggregateTarget` #1390 @BarredEwe ### Fixed - Fixed source file `includes` not working when no paths were found #1337 @shnhrrsn - Supports specifying multiple package products #1395 @simonbs ## 2.37.0 ### Added - Added support for adding `Build Tool Plug-ins` to targets #1374 @BarredEwe ## 2.36.1 ### Fixed - Revert addition of `ENABLE_MODULE_VERIFIER` build setting for causing issues in tests and some setups #1387 @yonaskolb ## 2.36.0 ### Added - Added `scheme.enableGPUValidationMode` #1294 @LouisLWang - Added visionOS support #1379 @shiba1014 - Added ability to disable Thread performance checker in Schemes #1380 @piellarda - Added support for `RuntimeIssue` breakpoints #1384 @yonaskolb ### Changed - The project object version has been updated for Xcode 14.3 #1368 @leonardorock - Updated recommended settings for Xcode 14.3 #1385 @yonaskolb - Dropped support for Xcode 12 and 13, due to XcodeProj update #1384 @yonaskolb ### Fixed - Fix external dependencies from being removed by Xcode #1354 @OdNairy - Stop creating orphaned object references when reusing references to external dependencies #1377 @liamnichols ## 2.35.0 ### Added - Added support for shared breakpoints #177 @alexruperez @myihsan - Added support for `putResourcesBeforeSourcesBuildPhase` in a target #1351 @mat1th ### Fixed - Fix case where source paths may not be deduplicated correctly resulting in duplicate groups and/or a crash in running Xcodegen #1341 @dalemyers ## 2.34.0 ### Changed - Added support for `swiftcrossimport` folders. #1317 @Iron-Ham - Added support for [Scheme Management](Docs/ProjectSpec.md##scheme-management) #1142 @wendyliga, @teameh ### Fixed - Fix includes when the projectRoot is a relative path #1262 @CraigSiemens - Renamed build phase `Embed App Extensions` to `Embed Foundation Extensions` to fix Xcode 14 warning #1310 @casperriboe ## 2.33.0 ### Added - Added support for `enableGPUFrameCaptureMode` #1251 @bsudekum - Config setting presets can now also be loaded from the main bundle when bundling XcodeGenKit #1135 @SofteqDG - Added ability to generate multiple projects in one XcodeGen launch #1270 @skofgar - Use memoization during recursive SpecFiles creation. This provides a drastic performance boost with lots of recursive includes #1275 @ma-oli ### Fixed - Fix scheme not being generated for aggregate targets #1250 @CraigSiemens - Fix recursive include path when relativePath is not set #1275 @ma-oli - Include projectRoot in include paths #1275 @ma-oli ### Internal - Updated to Yams 5.0.1 #1297 @s2mr - Delete ignored `try` keyword #1298 @s2mr ## 2.32.0 ### Added - Add support for `mlmodelc` files #1236 @antonsergeev88 - Add `enable` option for `include` #1242 @freddi-kit ### Fixed - Fix checking environment variable in `include` #1242 @freddi-kit - Fix profile action for frameworks in Xcode 14 #1245 @SSheldon ## 2.31.0 ### Added - Added a new CopyFilesBuildPhase, "Embed ExtensionKit Extensions" #1230 @mtj0928 - Added duplicate dependencies validation #1234 @aleksproger ## 2.30.0 ### Added - Added support for new target type `extensionkit-extension` in Xcode 14 #1228 @aleksproger ### Changed - Speed up generating build settings for large projects #1221 @jpsim ### Fixed - Fix XcodeGen building as library after breaking XcodeProj update 8.8.0 #1228 @aleksproger ## 2.29.0 Some support for Xcode Test Plans has been added. For now test plans are not generated by XcodeGen and must be created in Xcode and checked in, and then referenced by path. If the test targets are added, removed or renamed, the test plans may need to be updated in Xcode #### Added - Schemes and Target Schemes can now reference existing Test Plans under `{scheme}.test.testPlans` and `{target}.scheme.testPlans`, respectively. #716 @yonaskolb @omares #### Fixed - Fixed an issue where DocC was not added to source file list #1202 @hiragram #### Changed - Updated XcodeProj to 8.7.1 #1213 @yonaskolb ## 2.28.0 #### Added - Support for specifying custom group locations for SPM packages. #1173 @John-Connolly ### Fixed - Fix Monterey macOS shell version, shell login flag for environments #1167 @bimawa - Fixed crash caused by a simultaneous write during a glob processing #1177 @tr1ckyf0x ### Changed - Run target source pattern matching in parallel #1197 @alvarhansen ## 2.27.0 #### Added - Support test target for local Swift Package #1074 @freddi-kit - Added `coverageTargets` for target test schemes. This enables to gather code coverage for specific targets. #1189 @gabriellanata - Fixed issue where .gyb files could not be added to source file list #1191 @hakkurishian ### Fixed - Fixed crash caused by a simultaneous write during a glob processing #1177 @tr1ckyf0x - Skip generating empty compile sources build phases for watch apps #1185 @evandcoleman ## 2.26.0 ### Added - Added the option to specify a `location` in a test target #1150 @KrisRJack ### Changed - Speed up source inclusion checking for big projects #1122 @PaulTaykalo ## 2.25.0 ### Added - Allow specifying a `copy` setting for each dependency. #1038 @JakubBednar ### Fixed - Fix broken codesign option for bundle dependency #1104 @kateinoigakukun - Ensure fileTypes are mapped to JSON value #1112 @namolnad - Fix platform filter for package dependecies #1123 @raptorxcz - Fix Xcode 13 build #1130 @raptorxcz @mthole ### Changed - Update XcodeProj to 8.2.0 #1125 @nnsnodnb ## 2.24.0 ### Added - Added support for DocC Catalogs #1091 @brevansio - Added support for "driver-extension" and "system-extension" product types #1092 @vgorloff - Add support for conditionally linking dependencies for specific platforms #1087 @daltonclaybrook - Add ability to specify UI testing screenshot behavior in test schemes #942 @daltonclaybrook ### Changed - **Breaking**: Rename the `platform` field on `Dependency` to `platformFilter` #1087 @daltonclaybrook ## 2.23.1 ### Changed - Reverted "Change FRAMEWORK_SEARCH_PATH for xcframeworks (#1015)", introduced in 2.20.0. XCFrameworks need to be referenced directly in the project for Xcode's build system to extract the appropriate frameworks #1081 @elliottwilliams ## 2.23.0 #### Added - Added ability to set custom platform for dependency #934 @raptorxcz #### Fixed - Added `()` to config variant trimming charater set to fix scheme config variant lookups for some configs like `Debug (Development)` that broke in 2.22.0 #1078 @DavidWoohyunLee - Fixed Linux builds on Swift 5.4 #1083 @yonaskolb ## 2.22.0 #### Added - Support `runPostActionsOnFailure` for running build post scripts on failing build #1075 @freddi-kit #### Changed - Xcode no longer alerts to project changes after regeneration, due to internal workspace not regenerating if identical #1072 @yonaskolb #### Fixed - Fixed no such module `DOT` error when package is used as a dependency #1067 @yanamura - Fixed scheme config variant lookups for some configs like `ProdDebug` and `Prod-Debug` that broke in 2.21.0 #1070 @yonaskolb ## 2.21.0 #### Added - Support weak link for Swift Package Dependency #1064 @freddi-kit #### Changed - Carthage frameworks are no longer embedded for "order-only" target dependencies. This avoid redundant embeds in situations where a target's sources _import_ a Carthage framework but do not have a binary dependency on it (like a test target which runs in a host app). #1041 @elliottwilliams #### Fixed - The `Core` target is renamed to avoid collisions with other packages. #1057 @elliottwilliams - Lookup scheme config variants by whole words, fixing incorrect assignment in names that contain subtrings of each other (eg PreProd and Prod) #976 @stefanomondino ## 2.20.0 #### Added - Allow specifying a `github` name like `JohnSundell/Ink` instead of a full `url` for Swift Packages #1029 @yonaskolb - Added explicit `LastUpgradeCheck` and `LastUpgradeVersion` override support so it's possible to override these properties without using the `project.xcodeVersion`. [1013](https://github.com/yonaskolb/XcodeGen/pull/1013) @Andre113 - Added `macroExpansion` for `run` in `schemes` #1036 @freddi-kit - Added `askForAppToLaunch` for `profile` in `schemes` #1035 @freddi-kit - Added support for selectedTests in schemes `Test` configuration. #913 @ooodin #### Fixed - Fixed regression on `.storekit` configuration files' default build phase. #1026 @jcolicchio - Fixed framework search paths when using `.xcframework`s. #1015 @FranzBusch - Fixed bug where schemes without a build target would crash instead of displaying an error #1040 @dalemyers - Fixed files with names ending in **Info.plist** (such as **GoogleServices-Info.plist**) from being omitted from the Copy Resources build phase. Now, only the resolved info plist file for each specific target is omitted. #1027 @liamnichols #### Internal - Build universal binaries for release. XcodeGen now runs natively on Apple Silicon. #1024 @thii ## 2.19.0 #### Added - Added support for building and running on Linux platforms. Tested for compatibility with Swift 5.3+ and Ubuntu 18.04. #988 @elliottwilliams - Added `useBaseInternationalization` to Project Spec Options to opt out of Base Internationalization. #961 @liamnichols - Added `storeKitConfiguration` to allow specifying StoreKit Configuration in Scheme and TargetScheme, supporting either xcodeproj or xcworkspace via `schemePathPrefix` option. #964 @jcolicchio - Added more detailed error message with method arguments. #990 @bannzai - Added `basedOnDependencyAnalysis` to Project Spec Build Script to be able to choose not to skip the script. #992 @myihsan - Added `BuildRule.runOncePerArchitecture` to allow running build rules once per architecture. #950 @sascha - Added discovered dependency file for a build script #1012 @polac24 @fggeraissate #### Changed - **Breaking**: Info.plists with custom prefixes are no longer added to the Copy Bundle Resources build phase #945 @anivaros - **Breaking**: `workingDirectory` of included legacy targets is now made relative to including project #981 @jcolicchio - **Breaking**: Make `simulateLocation` respect `schemePathPrefix` option. #973 @jcolicchio #### Fixed - Fixed error message output for `minimumXcodeGenVersion`. #967 @joshwalker - Remove force-unwrapping causing crash for `LegacyTarget`s #982 @jcolicchio - Fixed a race condition in an internal JSON decoder, which would occasionally fail with an error like `Parsing project spec failed: Error Domain=Unspecified error Code=0`. #995 @elliottwilliams - Fixed issue where frameworks with `MACH_O_TYPE: staticlib` were being incorrectly embedded. #1003 @mrabiciu #### Internal - Updated to Yams 4.0.0 #984 @swiftty ## 2.18.0 #### Added - Add `Scheme.Test.TestTarget.skipped` to allow skipping of an entire test target. #916 @codeman9 - Added ability to set custom LLDBInit scripts for launch and test schemes #929 @polac24 - Adds App Clip support. #909 @brentleyjones @dflems - Application extension schemes now default to `launchAutomaticallySubstyle = 2` and the correct debugger and launcher identifiers #932 @brentleyjones - Updated SettingsPresets to use new defaults from Xcode 12. #953 @liamnichols - Enable Base Internationalization by default as per Xcode 12 behavior. #954 @liamnichols #### Changed - Change default project version to Xcode 12 #960 @yonaskolb #### Internal - Updates CI to run on Xcode 12. #936 @dflems @yonaskolb #### Fixed - Select the first runnable build target, if present. #957 @codeman9 - Allow SDK dependencies to be embedded. #922 @k-thorat - Allow creating intermediary groups outside of the project directory. #892 @segiddins - Fix appex's Runpath Search Paths under macOS target. #952 @rinsuki - `onlyCopyFilesOnInstall` is extended for the Embed App Extensions build phase. #948 @RomanPodymov ## 2.17.0 #### Added - Added `options.fileTypes` which lets you set cross project defaults for certain file extensions #914 @yonaskolb - Added `onlyCopyFilesOnInstall` option to targets for the Embed Files build phase. #912 @jsorge #### Fixed - Treat all directories with known UTI as file wrapper. #896 @KhaosT - Generated schemes for application extensions now contain `wasCreatedForAppExtension = YES`. #898 @muizidn - Allow package dependencies to use `link: false` #920 @k-thorat - Fixed issue computing relative paths. #915 @andrewreach #### Internal - Updated to XcodeProj 7.13.0 #908 @brentleyjones ## 2.16.0 #### Added - Improve speed of metadata parsing and dependency resolution. #803 @michaeleisel - Improve support for iOS sticker packs and add support for `launchAutomaticallySubstyle` to run schemes. #824 @scelis - Add --project-root option to generate command. #828 @ileitch - Add an ability to set an order of groups with `options.groupOrdering` #613 @Beniamiiin - Add the ability to output a dependency graph in graphviz format #852 @jeffctown - Adds uncluttering the project manifest dumped to YAML from empty values #858 @paciej00 - Added ability to name the executable target when declaring schemes. #869 @elland - Added ability to set executable to Ask to Launch. #871 @pinda #### Fixed - Fixed issue when linking and embedding static frameworks: they should be linked and NOT embed. #820 @acecilia - Fixed issue when generating projects for paths with a dot in the folder for swift sources. #826 @asifmohd - Prefix static library target filenames with 'lib' to match Xcode. #831 @ileitch - Fixed duplicate addition of carthage static frameworks. #829 @funzin - Fix handling of SWIFT_INSTALL_OBJC_HEADER when its value is YES/NO. #827 @ileitch - Set `preActions` and `postActions` on the `build` action of a TargetScheme instead of the other actions. #823 @brentleyjones - Prevent test targets from being set as a scheme's launch action #835 @brentleyjones - Implicitly include bundles in the Copy Bundle Resources build phase. #838 @skirchmeier - Fixed dumping a project manifest which contains an array of project references #840 @paciej00 - Generate correct PBXTargetDependency for external targets. #843 @ileitch - Fix linking of multiple products from the same Swift Package #830 @toshi0383 - Don't deduplicate files in `include` with different path but same name. #849 @akkyie - Don't link transitive static carthage libraries. #853 @akkyie - Optimize simplifying paths for faster project generation. #857 @akkyie - Fixed issue where wrapper folders may not include correctly in the generated project. #862 @KhaosT - Compile `xcmappingmodel` files instead of copying bundle resources. #834 @jcolicchio - Fixed issue where `Complie Sources` build phase is generated for resource bundles even when they have no files to compile #878 @nkukushkin ## 2.15.1 #### Fixed - Fixed issue which caused watch app schemes to be generated incorrectly, preventing these apps from launching. #798 @daltonclaybrook - Added build presets for the target type `framework.static`. #819 @acecilia - Fixed XcodeProj resolution and updated to 7.10.0 #822 @soffes ## 2.15.0 #### Added - Add support for local Swift Packages in `packages` using `path`. #808 @freddi-kit - Add `buildImplicitDependencies` as an option on `TargetScheme`. #810 @evandcoleman #### Fixed - Fixed resolving path to local Swift Packages #796 @freddi-kit - Added ability to stop on every main thread checker issue on Run schemes and TargetSchemes #799 @ionutivan - Avoid copying ObjC interface header when SWIFT_INSTALL_OBJC_HEADER=false. #805 @kateinoigakukun ## 2.14.0 #### Added - Add ability to embed and code sign Swift package dependencies with dynamic products. #788 @alexruperez #### Fixed - Revert "Add Base to known regions even if one doesn't exist" #791 @bryansum - Set `defaultConfigurationName` for every target which is defined in a project. #787 @ken0nek - Set `TEST_TARGET_NAME` only when a project has UITest bundle. #792 @ken0nek - Set xcodeproj path in project.xcworkspace/contents.xcworkspacedata #793 @ken0nek ## 2.13.1 #### Fixed - Validate scheme test action and test coverage target references before generating. #775 @liamnichols - Fixed parsing prerelease identifiers in Swift package versions #779 @yonaskolb - Fixed using legacy targets as dependencies #778 @yonaskolb #### Internal - Updated to XcodeProj 7.8.0 #777 @yonaskolb - Use #779 @yonaskolb ## 2.13.0 #### Added - Support External Target References via subprojects. #701 @evandcoleman #### Fixed - Fixed compilation as library by locking down XcodeProj version #767 @yonaskolb - Stabilized sorting of groups with duplicate names/paths. #671 @ChristopherRogers - Moved `Copy Bundle Resources` to after `Link with Libraries` build phase #768 @yonaskolb #### Internal - Updated to XcodeProj 7.7.0 #767 @yonaskolb ## 2.12.0 #### Added - Added pre and post command options. Useful for running `pod install` in combination with `--use-cache` #759 @yonaskolb - Support for language and region settings on a target basis #728 @FranzBusch - Added option to generate only Info.plist files with `--only-plists` #739 @namolnad - Added the option to specify a `simulateLocation` in a scheme #722 @basvankuijck - Support for On Demand Resources tags #753 @sipao #### Fixed - Fixed resolving a relative path for `projectReference.path` #740 @kateinoigakukun - Don't add framework dependency's directory to `FRAMEWORK_SEARCH_PATHS` if it is implicit #744 @ikesyo @yutailang0119 - Fixed resolving relative path passed to `XcodeProj` #751 @PycKamil - Prefer configurations named "Debug" or "Release" for default scheme build configurations #752 @john-flanagan - Added an extra check for package versions. #755 @basvankuijck #### Internal - Update to SwiftCLI 6.0 and use the new property wrappers #749 @yonaskolb ## 2.11.0 #### Added - Add Carthage static framework dependencies support. #688 @giginet - Added `xcodegen dump` command #710 @yonaskolb - Added `--no-env` option to disable environment variables expansion #704 @rcari - Added custom group support for target sources #621 @sroebert @rcari - Added new dependency type, `bundle`. This allows targets to copy bundles from other projects #616 @bsmith11 #### Fixed - Improved variable expansion runtime #704 @rcari - Fixed missing headers for static framework targets #705 @wag-miles - Using more file types from XcodeProj for PBXFileReferences resulting in less project diffs #715 @yonaskolb - Fixed localized `*.intentdefinition` not being added to build source phases #720 @giginet - Fixed `selectedLauncherIdentifier` not being set `Xcode.IDEFoundation.Launcher.PosixSpawn` when `debugEnabled: false` is defined in test action #725 @ken0nek - Fixed unnecessary dependencies related to SwiftPM #726 @tid-kijyun #### Changed - Deprecated `$old_form` variables in favor of `${new_form}` variables #704 @rcari - Updated XcodeProj to 7.4.0 #709 @yonaskolb - Updated to Swift 5.1 #714 @yonaskolb ## 2.10.1 #### Fixed - Add Base to knownRegions even if one doesn't exist #694 @bryansum - Fixed missing `onlyGenerateCoverageForSpecifiedTargets` issue #700 @kateinoigakukun - Fixed regression on dependencies `link` flag #703 @rcari ## 2.10.0 #### Added - Support Target Reference to another project. #655 @kateinoigakukun - Added `coverageTargets` for test target. This enables to gather code coverage for specific targets. #656 @kateinoigakukun #### Fixed - Add base localisation by default even if no base localised files were found. Fixes warning in Xcode 11 #685 @yonaskolb - Don't generate CFBundleExecutable in default generated Info.plist for `bundle` target types #689 @FranzBusch - Fixed resolving relative paths with custom project destination #681 @giginet - Fixed resolving relative paths for Info.plist #683 @giginet - Fixed macOS unit test target TEST_HOST #696 @mjarvis #### Internal - Restructure targets #698 @yonaskolb ## 2.9.0 #### Added - Added Scheme Templates #672 @bclymer #### Fixed - Fixed macOS unit test setting preset #665 @yonaskolb - Add `rcproject` files to sources build phase instead of resources #669 @Qusic - Prefer default configuration names for generated schemes #673 @giginet - Fixed some resource files being placed to "Recovered References" group #679 @nivanchikov #### Internal - Updated to SwiftCLI 5.3.2 #667 @giginet - Fixed tests in case-sensitive file system #670 @Qusic ## 2.8.0 #### Added - Added support for Swift Package dependencies #624 @yonaskolb - Added `includes` to `sources` for a Target. This follows the same glob-style as `excludes` but functions as a way to only include files that match a specified pattern. Useful if you only want a certain file type, for example specifying `**/*.swift`. #637 @bclymer - Support `dylib` SDK. #650 @kateinoigakukun - Added `language` and `region` options for `run` and `test` scheme #654 @kateinoigakukun - Added `debugEnabled` option for `run` and `test` scheme #657 @kateinoigakukun #### Fixed - Expand template variable in Array of Any #651 @kateinoigakukun - Significantly improve performance when running with a large number files. #658 @kateinoigakukun - Removed some more diffs between the generated .pbxproj and when Xcode resaves it #663 @yonaskolb #### Internal - Removed needless `Array` initialization. #661 @RomanPodymov - Updated to XcodeProj 7.1.0 #624 @yonaskolb ## 2.7.0 #### Added - Added Bash 4 style recursive globbing (`**/*`) in target sources `excludes` #636 @bclymer - Added ability to disable main thread checker in Schemes #601 @wag-miles #### Fixed - Fixed included specs that were referenced multiple times from duplicating content #599 @haritowa - Fixed `.orig` files being added to the project #627 @keith #### Changed - Allow linking of dependencies into static libraries when `link` is set to true #635 @kateinoigakukun ## 2.6.0 #### Added - Added ability to skip tests #582 @kadarandras - Added ability to set `attributes` on build files #583 @min - Allow using environment variables in the form of `${SOME_VARIABLE}`. This might be a **breaking** change when a target template attribute is also defined as an environment variable #594 @tomquist - Added support for `watchapp2-container` and `framework.static` product types #604 @yonaskolb #### Fixed - Fixed `.pch` files being bundled as resources #597 @thii - Fixed an issue that prevents watchOS Intents Extension from running correctly. #571 @KhaosT #### Changed - Updated the default `compatibilityVersion` project setting from `Xcode 9.3` to `Xcode 10.0` #581 @acecilia - Updated to XcodeProj 7.0.0. Note that the length of generated UUIDs has changed #604 @yonaskolb #### Internal - Added ability to encode ProjectSpec #545 @ryohey ## 2.5.0 #### Added - Added support for `app-extension.intents-service` target type #536 @yonaskolb - Added support for custom `root` in `sdk` dependency #562 @raptorxcz #### Changed - Updated to xcodeproj 6.7.0 including its performance improvements #536 @yonaskolb - Updated default generated settings for Xcode 10.2 #555 @yonaskolb - Changed order of file generation so that plists are now generated before the project, so they will be included in the projects files #544 @tomquist - Updated Yams to 2.0.0 @yonaskolb #### Fixed - Fixed groups from sources outside a project spec's directory from being flattened. #550 @sroebert - Fixed `optional` file sources not being added to the project #557 @yonaskolb - Fixed Carthage dependencies being incorrectly embedded in WatchKit app bundles instead of a WatchKit app extension #558 @KhaosT ## 2.4.0 #### Fixed - Fixed installation when building in Swift 5 #549 @yonaskolb #### Changed - Updated to Swift 5 and dropped Swift 4.2 #549 @yonaskolb ## 2.3.0 #### Added - Added ability to automatically find all the frameworks for Carthage dependencies via the global `options.findCarthageFrameworks` or dependency specific `dependency.findFrameworks`. See the [Carthage](Docs/Usage.md#carthage) usage docs for more info #506 @rpassis @yonaskolb - Added support for nested target templates #534 @tomquist - Added ability to define `templateAttributes` within a target to be able to parameterize templates. #533 @tomquist - Added ability to set `link` to false in framework dependencies #532 @dimatosaurus - Added `missingConfigFiles` to `options.disabledValidations` to optionally skip checking for the existence of config files. - Added ability to define a per-platform `deploymentTarget` for Multi-Platform targets. #510 @ainopara #### Changed - **DEPRECATION**: Placeholders `$target_name` and `$platform` have been deprecated in favour of `${target_name}` and `${platform}`. Support for the old placeholders will be removed in a future version #533 @tomquist #### Fixed - Sources outside a project spec's directory will be correctly referenced as relative paths in the project file. #524 - Fixed error when `optional` directory source is missing #527 @yonaskolb - Fixed excludes within included spec #535 @yonaskolb - Fixed paths in target templates within included files not being relative #537 @yonaskolb - Fix multi-platform target templates #541 @yonaskolb - Fixed sources in an included target not being relative when the sources are mix of string and dictionaries #542 @yonaskolb ## 2.2.0 #### Added - Added ability to generate empty directories via `options.generateEmptyDirectories` #480 @Beniamiiin - Added support for the `instrumentsPackage` product type #482 @ksulliva - Added support for `inputFileLists` and `outputFileLists` within project build scripts #500 @lukewakeford - Added support for a `$target_name` replacement string within target templates #504 @yonaskolb - Added `createIntermediateGroups` to individual Target Sources which overrides the top level option #505 @yonaskolb #### Changed - **BREAKING**: All the paths within `include` files are now relative to that file and not the root spec. This can be disabled with a `relativePaths: false` on the include. See the [documentation](https://github.com/yonaskolb/XcodeGen/blob/master/Docs/ProjectSpec.md#include) for more details #489 @ellneal - Updated the Xcode compatibility version from 3.2 to 9.3 #497 @yonaskolb - Exact matches to config names in build settings won't partial apply to other configs #503 @yonaskolb - UUIDs in the project are standard and don't contain any type prefixes anymore #### Fixed - Fixed `--project` argument not taking effect #487 @monowerker - Fixed Sticker Packs from generating an empty Source file phase which caused in error in the new build system #492 @rpassis - Fixed generated schemes for tool targets not setting the executable #496 @yonaskolb - Fixed resolving Carthage dependencies for iOS app with watchOS target. [465](https://github.com/yonaskolb/XcodeGen/pull/465) @raptorxcz ## 2.1.0 #### Added - Added an experiment new caching feature. Pass `--use-cache` to opt in. This will read and write from a cache file to prevent unnecessarily generating the project. Give it a try as it may become the default in a future release #412 @yonaskolb #### Changed - Changed spelling of build phases to **preBuildPhase** and **postBuildPhase**. The older names are deprecated but still work [402](https://github.com/yonaskolb/XcodeGen/pull/402) @brentleyjones - Moved generation to a specific subcommand `xcodegen generate`. Simple `xcodegen` will continue to work for now #437 @yonaskolb - If `INFOPLIST_FILE` has been set on a target, then an `info` path won't ovewrite it #443 @feischl97 #### Fixed - Fixed XPC Service package type in generated `Info.plist` #435 @alvarhansen - Fixed phase ordering for modulemap and static library header Copy File phases. [402](https://github.com/yonaskolb/XcodeGen/pull/402) @brentleyjones - Fixed intermittent errors when running multiple `xcodegen`s concurrently #450 @bryansum - Fixed `--project` argument not working #437 @yonaskolb - Fixed unit tests not hooking up to host applications properly by default. They now generate a `TEST_HOST` and a `TestTargetID` #452 @yonaskolb - Fixed static libraries not including external frameworks in their search paths #454 @brentleyjones - Add `.intentdefinition` files to sources build phase instead of resources #442 @yonaskolb - Add `mlmodel` files to sources build phase instead of resources #457 @dwb357 ## 2.0.0 #### Added - Added `weak` linking setting for dependencies #411 @alvarhansen - Added `info` to targets for generating an `Info.plist` #415 @yonaskolb - Added `entitlements` to targets for generating an `.entitlement` file #415 @yonaskolb - Added `sdk` dependency type for linking system frameworks and libs #430 @yonaskolb - Added `parallelizable` and `randomExecutionOrder` to `Scheme` test targets in an expanded form #434 @yonaskolb - Validate incorrect config setting definitions #431 @yonaskolb - Automatically set project `SDKROOT` if there is only a single platform within the project #433 @yonaskolb #### Changed - Performance improvements for large projects #388 @yonaskolb @kastiglione - Upgraded to xcodeproj 6 #388 @yonaskolb - Upgraded to Swift 4.2 #388 @yonaskolb - Remove iOS codesigning sdk restriction in setting preset #414 @yonaskolb - Changed default project version to Xcode 10.0 and default Swift version to 4.2 #423 @yonaskolb - Added ability to not link Carthage frameworks #432 @yonaskolb #### Fixed - Fixed code signing issues #414 @yonaskolb - Fixed `TargetSource.headerVisibility` not being set in initializer #419 @jerrymarino - Fixed crash when using Xcode Legacy targets as dependencies #427 @dflems ## 1.11.2 If XcodeGen is compiled with Swift 4.2, then UUID's in the generated project will not be deterministic. This will be fixed in an upcoming release with an update to xcodeproj 6.0 #### Fixed - Fixed release builds in Swift 4.2 #404 @pepibumur - Fixed default settings for macOS unit-tests #387 @frankdilo - Fixed Copy Headers phase ordering for Xcode 10 #401 @brentleyjones - Fixed generated schemes on aggregate targets #394 @vgorloff #### Changed - Added `en` as default value for knownRegions #390 @Saik0s - Update `PathKit`, `Spectre`, `Yams` and `xcodeproj` dependencies ## 1.11.1 #### Fixed - Fixed `FRAMEWORK_SEARCH_PATHS` for `framework` dependency paths with spaces #382 @brentleyjones - Fixed aggregate targets not being found with `transitivelyLinkDependencies` #383 @brentleyjones ## 1.11.0 #### Added - Added `showEnvVars` to build scripts to disable printing the environment #351 @keith - Added `requiresObjCLinking` to `target` #354 @brentleyjones - Added `targetTemplates` #355 @yonaskolb - Added `aggregateTargets` #353 @yonaskolb - Added `options.groupSortPosition` #356 @yonaskolb - Added ability to specify `copyFiles` build phase for sources #345 @brentleyjones - Added ability to specify a `minimumXcodeGenVersion` #349 @brentleyjones - Added `customArchiveName` and `revealArchiveInOrganizer` to `archive` #367 @sxua #### Fixed - Sort files using localizedStandardCompare #341 @rohitpal440 - Use the latest `xcdatamodel` when sorted by version #341 @rohitpal440 - Fixed compiler flags being set on non source files in mixed build phase target sources #347 @brentleyjones - Fixed `options.xcodeVersion` not being parsed #348 @brentleyjones - Fixed non-application targets using `carthage copy-frameworks` #361 @brentleyjones - Set `xcdatamodel` based on `xccurrentversion` if available #364 @rpassis - XPC Services are now correctly copied #368 @brentley - Fixed `.metal` files being added to resources #380 @vgorloff #### Changed - Improved linking for `static.library` targets #352 @brentleyjones - Changed default group sorting to be after files #356 @yonaskolb - Moved `Frameworks` and `Products` top level groups to bottom #356 @yonaskolb - `modulemap` files are automatically copied to the products directory for static library targets #346 @brentleyjones - Public header files are automatically copied to the products directory for static library targets #365 @brentleyjones - Swift Objective-C Interface Header files are automatically copied to the products directory for static library targets #366 @brentleyjones - `FRAMEWORK_SEARCH_PATHS` are adjusted for `framework` dependencies #373 @brentley - `library.static` targets have `SKIP_INSTALL` set to `YES` #358 @brentley - Copy files phases have descriptive names #360 @brentley #### Internal - Moved brew formula to homebrew core - Added `CONTRIBUTING.md` ## 1.10.3 #### Fixed - Fixed Mint installations finding `SettingPresets` #338 @yonaskolb ## 1.10.2 #### Changed - Set `transitivelyLinkDependencies` to false by default ## 1.10.1 #### Fixed - Fixed `transitivelyLinkDependencies` typo #332 @brentleyjones - Fixed framework target dependencies not being code signed by default #332 @yonaskolb #### Changed - Code sign all dependencies by default except target executables #332 @yonaskolb ## 1.10.0 #### Added - Added build rule support #306 @yonaskolb - Added support for frameworks in sources #308 @keith - Added ability to automatically embed transient dependencies. Controlled with `transitivelyLinkDependencies` #327 @brentleyjones #### Changed - Upgraded to Swift 4.1 - Improved Carthage dependency lookup performance with many targets #298 @keith - By default don't CodeSignOnCopy `target` dependencies. This can still be controlled with `Dependency.codeSign` #324 @yonaskolb #### Fixed - Fixed PBXBuildFile and PBXFileReference being incorrectly generated for Legacy targets #296 @sascha - Fixed required sources build phase not being generated if there are no sources #307 @yonaskolb - Fixed install script in binary release #303 @alvarhansen - Removed `ENABLE_TESTABILITY` from framework setting presets #299 @allu22 - Fixed homebrew installation #297 @vhbit - `cc` files are now automatically recognized as source files #317 @maicki - Fixed `commandLineArguments` not parsing when they had dots in them #323 @yonaskolb - Fixed excluding directories that only have sub directories #326 @brentleyjones - Made `PBXContainerItemProxy` ID more deterministic - Fixed generated framework schemes from being executable #328 @brentleyjones ## 1.9.0 #### Added - Scheme pre and post actions can now be added to `target.scheme` #280 @yonaskolb - Individual files can now be added to `fileGroups` #293 @yonaskolb #### Changed - Updated to `xcproj` 4.3.0 for Xcode 9.3 updates - Update default Xcode version to 9.3 including new settings #284 @LinusU - **Breaking for ProjectSpec library users** Changed `ProjectSpec` to `Project` and `ProjectSpec.Options` to `SpecOptions` #281 @jerrymarino #### Fixed - Fixed manual build phase of `none` not being applied to folders #288 @yonaskolb - Quoted values now correctly get parsed as strings #282 @yonaskolb - Fixed adding a root source folder when `createIntermediateGroups` is on #291 @yonaskolb - Fixed Homebrew installations issues on some machines #289 @vhbit - Fixed files that are added as root sources from having invalid parent groups outside the project directory #293 @yonaskolb ## 1.8.0 #### Added - Added Project `defaultConfig` #269 @keith - Added Target `attributes` #276 @yonaskolb - Automatically set `DevelopmentTeam` and `ProvisioningStyle` within `TargetAttributes` if relevant build settings are defined #277 @yonaskolb #### Fixed - Fixed default `LD_RUNPATH_SEARCH_PATHS` for app extensions #272 @LinusU #### Internal - Make `LegacyTarget` init public #264 @jerrymarino - Upgrade to _xcproj_ to 4.2.0, _Yams_ to 0.6.0 and _PathKit_ to 0.9.1 @yonaskolb ## 1.7.0 #### Added - Added support for scheme environment variables #239 @turekj @toshi0383 - Added `carthageExecutablePath` option #244 @akkyie - Added `parallelizeBuild` and `buildImplicitDependencies` to Schemes #241 @rahul-malik @yonaskolb - Added support for Core Data `xcdatamodeld` files #249 @yonaskolb - Projects are now generated atomically by writing to a temporary directory first #250 @yonaskolb - Added script for adding precompiled binary to releases #246 @toshi0383 - Added optional `headerVisibilty` to target source. This still defaults to public #252 @yonaskolb - Releases now include a pre-compiled binary and setting presets, including an install script #### Fixed - Fixed Mint installation from reading setting presets #248 @yonaskolb - Fixed setting `buildPhase` on a `folder` source. This allows for a folder of header files #254 @toshi0383 - Carthage dependencies are not automatically embedded into test targets #256 @yonaskolb - Carthage dependencies now respect the `embed` property #256 @yonaskolb - iMessage extensions now have proper setting presets in regards to app icon and runtime search paths #255 @yonaskolb - Excluded files are not added within .lproj directories #238 @toshi0383 ## 1.6.0 #### Added - Added scheme pre-actions and post-actions #231 @kastiglione - Added `options.disabledValidations` including `missingConfigs` to disable project validation errors #220 @keith - Generate UI Test Target Attributes #221 @anreitersimon #### Fixed - Filter out duplicate source files #217 @allu22 - Fixed how `lastKnownFileType` and `explicitFileType` were generated across platforms #115 @toshi0383 - Removed a few cases of project diffs when opening the project in Xcode @yonaskolb - Fixed Swift not being embedded by default in watch apps @yonaskolb #### Changed - Change arrays to strings in setting presets #218 @allu22 - Updated to xcproj 4.0 #227 ## 1.5.0 #### Added - added support for `gatherCoverageData` flag in target schemes #170 @alexruperez - added support for `commandLineOptions` in target schemes #172 @rahul-malik - added Project spec as a SwiftPM library for reuse in other projects #164 @soffes - added `implicit` option for framework dependencies #166 @sbarow - added `--quite` option to CLI #167 @soffes - can now print version with `-v` in addition to `--version` #174 @kastiglione - added support for legacy targets #175 @bkase - added support for indentation options #190 @bkase - added source excludes #135 @peymankh @ - added `options.xcodeVersion` #197 @yonaskolb @peymankh - add test targets to Scheme #195 @vhbit - add option to make a source file optional incase it will be generated later #200 @vhbit - finalize Scheme spec #201 @yonaskolb - added `buildPhase` setting to target source for overriding the guessed build phase of files #206 @yonaskolb - added `deploymentTarget` setting to project and target #205 @yonaskolb #### Changed - huge performance improvements when writing the project file due to changes in xcproj - updated dependencies - minor logging changes - updated Project Spec documentation - scan for `Info.plist` lazely #194 @kastiglione - change setting presets so that icon settings only get applied to application targets #204 @yonaskolb - changed scheme build targets format #203 @yonaskolb - when specifying a `--spec` argument, the default for the `--project` path is now the directory containing the spec #211 @yonaskolb #### Fixed - fixed shell scripts escaping quotes twice #186 @allu22 - fixed `createIntermediateGroups` when using a relative spec path #184 @kastiglione - fixed command line arguments for test and profile from being overridden #199 @vhbit - fixed files deep within a hierarchy having the path for a name - fixed source files from being duplicated if referenced with different casing #212 @yonaskolb - fixed target product name not being written. Fixes integration with R.swift #213 @yonaskolb ## 1.4.0 #### Added - added `--version` flag #112 @mironal - added support for adding individual file sources #106 @bkase - added source compiler flag support #121 @bkase - added `ProjectSpec.options.createIntermediateGroups` #108 @bkase - added better json loading support #127 @rahul-malik - added source `name` for customizing names of source directories and file #146 @yonaskolb - added folder reference source support via a new `type` property #151 @yonaskolb - added `ProjectSpec.options.developmentLanguage` #155 @yonaskolb #### Changed - updated to xcproj 1.2.0 #113 @yonaskolb - build settings from presets will be removed if they are provided in `xcconfig` files #77 @toshi0383 - all files and groups are sorted by type and then alphabetically #144 @yonaskolb - target sources can now have an expanded form #119 @yonaskolb - empty build phases are now not generated #149 @yonaskolb - make UUIDs more deterministic #154 @yonaskolb #### Fixed - only add headers to frameworks and libraries #118 @ryohey - fixed localized files with the same name #126 @ryohey - fix intermediate sources #144 @yonaskolb - fix cyclical target dependencies not working #147 @yonaskolb - fix directory bundles not being added properly when referenced directly #148 @yonaskolb - made `mm`, `c` and `S` file be parsed as source files #120 @bkase @enmiller - fix the generation of localized variant groups if there is no `Base.lproj` #157 @ryohey - all localizations found are added to a projects known regions #157 @ryohey #### Internal - refactoring - more tests - added release scripts ## 1.3.0 #### Added - generate output files for Carthage copy-frameworks script #84 @mironal - added options.settingPreset to choose which setting presets get applied #100 @yonaskolb - added `link` option for target dependencies #109 @keith #### Changed - updated to xcproj 0.4.1 #85 @enmiller - don't copy base settings if config type has been left out #100 @yonaskolb - generate localised files under a single variant group #70 @ryohey - don't apply common project settings to configs with no type #100 @yonaskolb - config references in settings can now be partially matched and are case insensitive #111 @yonaskolb - other small internal changes @yonaskolb #### Fixed - embed Carthage frameworks for macOS #82 @toshi0383 - fixed copying of watchOS app resources #96 @keith - automatically ignore more file types for a target's sources (entitlements, gpx, apns) #94 @keith - change make build to a PHONY task #98 @keith - allow copying of resource files from dependant targets #95 @keith - fixed library linking #93 @keith - fixed duplicate carthage file references #107 @yonaskolb - an error is now shown if you try and generate a target scheme and don't have debug and release builds @yonaskolb ## 1.2.4 #### Fixed - setting presets only apply `ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES: YES` to applications - don't add carthage dependency to `copy-frameworks` script if `embed: false` - sort group children on APFS #### Changed - update to xcproj 0.3.0 ## 1.2.3 #### Fixed - Fixed wrong carthage directory name reference for macOS #74 @toshi0383 - Removed unnecessary `carthage copy-frameworks` for macOS app target #76 @toshi0383 - Added some missing default settings for framework targets. `SKIP_INSTALL: YES` fixes archiving - Filter out nulls from setting presets if specifying an empty string ## 1.2.2 #### Added - automatically set `TEST_TARGET_NAME` on UI test targets if one of the dependencies is an application target #### Fixed - set `DYLIB_INSTALL_NAME_BASE` to `@rpath` in framework target presets - fixed tvOS launch screen setting. `ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME` is now `LaunchImage` not `tvOS LaunchImage` ## 1.2.0 #### Added - `include` now supports a single string as well as a list - add support setting xcconfig files on a project with `configFiles` #64 - add `fileGroups` to project spec for adding groups of files that aren't target source files #64 - better output (more info, emoji, colors) - add `options.bundleIdPrefix` for autogenerating `PRODUCT_BUNDLE_IDENTIFIER` #67 - add `:REPLACE` syntax when merging `include` #68 - add `mint` installation support #### Fixed - fixed homebrew installation - fixed target xcconfig files not working via `configFiles` #64 - look for `INFOPLIST_FILE` setting in project and xcconfig files before adding it automatically. It was just looking in target settings before #64 - exit with error on failure ## 1.1.0 #### Changed - set project version to Xcode 9 - `LastUpgradeVersion` attribute to `0900` - set default Swift version to 4.0 - `SWIFT_VERSION` build setting to `4.0` ### 1.0.1 ### Fixed - fixed incorrect default build script shell path - fixed install scripts ## 1.0.0 #### Added - Swift 4 support #52 - Support for C and C++ files #48 by @antoniocasero - Xcode 9 default settings #### Fixed - fixed empty string in YAML not being parsed properly #50 by @antoniocasero #### Changed - updated to xcodeproj 0.1.2 #56 - **BREAKING**: changed target definitions from list to map #54 ## 0.6.1 #### Added - Ability to set PBXProject attributes #45 #### Changed - Don't bother linking target frameworks for target dependencies. - Move code signing default settings from all iOS targets to iOS application targets, via Product + Platform setting preset files #46 ## 0.6.0 #### Added - Allow a project spec to include other project specs #44 #### Changed - Changed default spec path to `project.yml` - Changed default project directory to the current directory instead of the spec file's directory ## 0.5.1 #### Fixed - Fix embedded framework dependencies - Add `CODE_SIGN_IDENTITY[sdk=iphoneos*]` back to iOS targets - Fix build scripts with "" generating invalid projects #43 ## 0.5.0 #### Added - Added multi platform targets #35 - Automatically generate platform specific `FRAMEWORK_SEARCH_PATHS` for Carthage dependencies #38 - Automatically find Info.plist and set `INFOPLIST_FILE` build setting if it doesn't exist on a target #40 - Add options for controlling embedding of dependencies #37 #### Fixed - Fixed localized files not being added to a target's resources #### Changed - Renamed Setting Presets to Setting Groups - Carthage group is now created under top level Frameworks group ## 0.4.0 ##### Added - Homebrew support #16 by @pepibumur - Added `runOnlyWhenInstalling` to build scripts #32 - Added `carthageBuildPath` option #34 #### Fixed - Fixed installations of XcodeGen not applying build setting presets for configs, products, and platforms, due to missing resources #### Changed - Upgraded to 0.1.1 #33 ## 0.3.0 - Extensions and Scheme Tests #### Added - Support for app extension dependencies, using the same `target: MyExtension` syntax #19 - Added test targets to generated target schemes via `Target.scheme.testTargets` #21 #### Changed - Updated xcodeproj to 0.0.9 #### Fixed - Fixed watch and messages apps not copying carthage dependencies #### Breaking changes - Changed `Target.generatedSchemes` to `Target.scheme.configVariants` ## 0.2.0 - Build scripts #### Added - Added Target build scripts with `Target.prebuildScripts` and `Target.postbuildScripts` #17 - Support for absolute paths in target sources, run script files, and config files - Add validation for incorrect `Target.configFiles` #### Fixed - Fixed some project objects sometimes having duplicate ids ## 0.1.0 First official release ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to XcodeGen There are various ways to contribute to XcodeGen, and all are welcome and appreciated! - [Bug Reports](#bug-reports) - [Feature Requests](#feature-requests) - [Answering Questions](#answering-questions) - [Example Specs](#example-specs) - [Documentation](#documentation) - [Code](#code) ## Bug reports Open issues about problems you may be encountering. When doing so please mention the version you're using `xcodegen --version`. ## Feature Requests If you have a good idea for a feature or enhancement open an issue. ## Answering Questions Look through the open issues and answer any questions you can. ## Example specs Submit your open source xcodegen spec to the [Examples](Docs/Examples.md) page. ## Documentation Improve the documentation in the [Docs](Docs) directory. ## Code You can submit your own code. This can be bug fixes or new features. If you're not sure what to work on check out the open [Issues](https://github.com/yonaskolb/XcodeGen/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) ### Getting started Make sure you have at least Xcode 9.2 installed. First clone the repo: ```shell git clone https://github.com/yonaskolb/XcodeGen.git cd XcodeGen make ``` To make editing easier you can generate the Xcode project using Swift PM: ```shell swift package generate-xcodeproj ``` ### Targets - `ProjectSpec`: Project spec definitions, loading, parsing and validation - `XcodeGen`: XcodeGen CLI - `XcodeGenKit`: All the logic for generation - `XcodeGenKitTests`: Generation tests ### Tests Before submitting your PR run the tests to make sure they pass. This can be done either in Xcode or by running `swift test`. As part of the tests there is a [TestProject](/Tests/Fixtures/TestProject) fixture that will be generated, and if the generated xcode project has any diff in it the test will fail. If the diff is a valid change, commit it as part of your changes. > Note that sometimes having the `TestProject` open in Xcode will generate it's own diffs, so make sure to have it closed when running the tests. If your change contains any new features or logic changes please add a unit test of your own to cover it. If it's a new feature, see if it can be integrated into the `TestProject` by adding any required files and then editing the [project spec](/Tests/Fixtures/TestProject/project.yml). ### Submitting your PR Please give a small summary of what has changed. Also add any github issues links (`Resolves #100`). Once your PR is created, please add a changelog entry to [CHANGELOG.md](/CHANGELOG.md) along with the PR number. ================================================ FILE: Docs/Examples.md ================================================ # Examples These are a bunch of real world examples of XcodeGen project specs. Feel free to add your own via PR. - [toshi0383/Bitrise-iOS](https://github.com/toshi0383/Bitrise-iOS/blob/master/project.yml) - [johndpope/swift-models](https://github.com/johndpope/swift-models/tree/stable/Inference) - [atelier-socle/AppRepositoryTemplate](https://github.com/atelier-socle/AppRepositoryTemplate/blob/master/project.yml) - [atelier-socle/FrameworkRepositoryTemplate](https://github.com/atelier-socle/FrameworkRepositoryTemplate/blob/master/project.yml) - [scelis/XcodeGen-TestStickers](https://github.com/scelis/XcodeGen-TestStickers/blob/master/project.yml) - [minvws/nl-covid19-notification-app-ios](https://github.com/minvws/nl-covid19-notification-app-ios/blob/master/project.yml) - [pvinis/react-native-xcodegen](https://github.com/pvinis/react-native-xcodegen/blob/master/templates) - [covid19cz/erouska-ios](https://github.com/covid19cz/erouska-ios/blob/develop/project.yml) - [markst/hotreloading-vscode-ios](https://github.com/markst/hotreloading-vscode-ios) - [MultiPlatformApp](https://github.com/hgq287/HGSwift/tree/master/Examples/MultiPlatformApp) - A modern **SwiftUI** multi-platform (iOS & macOS) example featuring a modular 3-tier architecture and shared logic. ================================================ FILE: Docs/FAQ.md ================================================ # Frequently asked questions - [Can I still check in my project](#can-i-still-check-in-my-project) - [Can I use CocoaPods](#can-i-use-cocoapods) - [Can I use Crashlytics](#can-i-use-crashlytics) - [How do I setup code signing](#how-do-i-setup-code-signing) ## Can I still check in my project Absolutely. You will get the most out of XcodeGen by adding your project to your `.gitignore`, as this way you avoid merge conflicts. But you can also check it in as a halfway step. >Note that you can run `xcodegen` as a step in your build process on CI. ## What happens when I switch branches If files were added or removed in the new checkout you will most likely need to run `xcodegen` again so that your project will reference all your files. It's recommended to set up some [git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) to automate the process: - run `xcodegen generate --use-cache` on the following hooks. This will make sure the project is up to date when checking out, merging and rebasing - `post-checkout` - `post-rewrite` - `post-merge` - run `xcodegen cache` on `pre-commit`. This will make sure that when switching branches the cache will be updated in case you made local changes, or are ammending a commit that added a new file. ## Can I use CocoaPods Yes, you will just need to run `pod install` after the project is generated to integrate Cocoapods changes. It's recommended to use a combination of `--use-cache` and the `postGenCommand` option which will only generate the project if required, and then only run `pod install` if the project has been regenerated. ## Can I use Crashlytics Yes, but you need to use a little trick when using CocoaPods. Add this script in your `Podfile`: ```ruby:Podfile // Your dependencies pod 'Firebase/Crashlytics' script_phase name: 'Run Firebase Crashlytics', shell_path: '/bin/sh', script: '"${PODS_ROOT}/FirebaseCrashlytics/run"', input_files: ['$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)'] ``` This script will be added after `[CP] Embed Pods Frameworks.` ## How do I setup code signing At the moment there are no special options for code signing in XcodeGen, and this must be configured via regular build settings. For code signing to work, you need to tell Xcode which development team to use. This requires setting the `DEVELOPMENT_TEAM` and possibly `CODE_SIGN_STYLE` build settings. See [Configuring build settings](Usage.md#configuring-build-settings) for how to do that ================================================ FILE: Docs/ProjectSpec.md ================================================ # Project Spec The project spec can be written in either YAML or JSON. All the examples below use YAML. - [x] required property - [ ] optional property Some of the YAML examples below don't show all the required properties. For example not all target examples will have a platform or type, even though they are required. You can also use environment variables in your configuration file, by using `${SOME_VARIABLE}` in a string. - [Project](#project) - [Include](#include) - [Options](#options) - [GroupOrdering](#groupordering) - [FileType](#filetype) - [Breakpoints](#breakpoints) - [Breakpoint Action](#breakpoint-action) - [Configs](#configs) - [Setting Groups](#setting-groups) - [Settings](#settings) - [Target](#target) - [Product Type](#product-type) - [Platform](#platform) - [Supported Destinations](#supported-destinations) - [Sources](#sources) - [Target Source](#target-source) - [Dependency](#dependency) - [Config Files](#config-files) - [Plist](#plist) - [Build Tool Plug-ins](#build-tool-plug-ins) - [Build Script](#build-script) - [Build Rule](#build-rule) - [Target Scheme](#target-scheme) - [Legacy Target](#legacy-target) - [Aggregate Target](#aggregate-target) - [Target Template](#target-template) - [Scheme](#scheme) - [Build](#build) - [Common Build Action options](#common-build-action-options) - [Execution Action](#execution-action) - [Run Action](#run-action) - [Test Action](#test-action) - [Test Target](#test-target) - [Other Parameters](#other-parameters) - [Testable Target Reference](#testable-target-reference) - [Archive Action](#archive-action) - [Simulate Location](#simulate-location) - [Scheme Management](#scheme-management) - [Environment Variable](#environment-variable) - [Test Plan](#test-plan) - [Scheme Template](#scheme-template) - [Swift Package](#swift-package) - [Remote Package](#remote-package) - [Local Package](#local-package) - [Project Reference](#project-reference) ## Project - [x] **name**: **String** - Name of the generated project - [ ] **include**: **[Include](#include)** - One or more paths to other specs - [ ] **options**: **[Options](#options)** - Various options to override default behaviour - [ ] **attributes**: **[String: Any]** - The PBXProject attributes. This is for advanced use. If no value is set for `LastUpgradeCheck`, it will be defaulted to ``{"LastUpgradeCheck": "XcodeVersion"}`` with `xcodeVersion` being set by [Options](#options)`.xcodeVersion` - [ ] **breakpoints**: [Breakpoints](#breakpoints) - Add shared breakpoints to the generated project - [ ] **configs**: **[Configs](#configs)** - Project build configurations. Defaults to `Debug` and `Release` configs - [ ] **configFiles**: **[Config Files](#config-files)** - `.xcconfig` files per config - [ ] **settings**: **[Settings](#settings)** - Project specific settings. Default base and config type settings will be applied first before any settings defined here - [ ] **settingGroups**: **[Setting Groups](#setting-groups)** - Setting groups mapped by name - [ ] **targets**: **[String: [Target](#target)]** - The list of targets in the project mapped by name - [ ] **fileGroups**: **[String]** - A list of paths to add to the root of the project. These aren't files that will be included in your targets, but that you'd like to include in the project hierarchy anyway. For example a folder of xcconfig files that aren't already added by any target sources, or a Readme file. - [ ] **schemes**: **[Scheme](#scheme)** - A list of schemes by name. This allows more control over what is found in [Target Scheme](#target-scheme) - [ ] **schemeTemplates**: **[String: [Scheme Template](#scheme-template)]** - a list of schemes that can be used as templates for actual schemes which reference them via a `template` property. They can be used to extract common scheme settings. Works great in combination with `include`. - [ ] **targetTemplates**: **[String: [Target Template](#target-template)]** - a list of targets that can be used as templates for actual targets which reference them via a `template` property. They can be used to extract common target settings. Works great in combination with `include`. - [ ] **packages**: **[String: [Swift Package](#swift-package)]** - a map of Swift packages by name. - [ ] **projectReferences**: **[String: [Project Reference](#project-reference)]** - a map of project references by name ### Include One or more specs can be included in the project spec. This can be used to split your project spec into multiple files, for easier structuring or sharing between multiple specs. Included specs can also include other specs and so on. Include can either be a list of includes or a single include. They will be merged in order and then the current spec will be merged on top. An include can be provided via a string (the path) or an object of the form: **Include Object** - [x] **path**: **String** - The path to the included file. - [ ] **relativePaths**: **Bool** - Dictates whether the included spec specifies paths relative to itself (the default) or the root spec file. - [ ] **enable**: **Bool** - Dictates whether the specified spec should be included or not. You can also specify it by environment variable. ```yaml include: - includedFile.yml - path: path/to/includedFile.yml relativePaths: false enable: ${INCLUDE_ADDITIONAL_YAML} ``` By default specs are merged additively. That is for every value: - if existing value and new value are both dictionaries merge them and continue down the hierarchy - if existing value and new value are both an array then add the new value to the end of the array - otherwise replace the existing value with the new value This merging behaviour can be overridden on a value basis. If you wish to replace a whole value (set a new dictionary or new array instead of merging them) then just affix `:REPLACE` to the key ```yaml include: - base.yml name: CustomSpec targets: MyTarget: # target lives in base.yml sources:REPLACE: - my_new_sources ``` Note that target names can also be changed by adding a `name` property to a target. ### Options - [ ] **minimumXcodeGenVersion**: **String** - The minimum version of XcodeGen required. - [ ] **carthageBuildPath**: **String** - The path to the carthage build directory. Defaults to `Carthage/Build`. This is used when specifying target carthage dependencies - [ ] **carthageExecutablePath**: **String** - The path to the carthage executable. Defaults to `carthage`. You can specify when you use custom built or locally installed Carthage using [Mint](https://github.com/yonaskolb/Mint), for example. - [ ] **createIntermediateGroups**: **Bool** - If this is specified and set to `true`, then intermediate groups will be created for every path component between the folder containing the source and next existing group it finds or the base path. For example, when enabled if a source path is specified as `Vendor/Foo/Hello.swift`, the group `Vendor` will created as a parent of the `Foo` group. This can be overridden in a specific [Target source](#target-source) - [ ] **bundleIdPrefix**: **String** - If this is specified then any target that doesn't have an `PRODUCT_BUNDLE_IDENTIFIER` (via all levels of build settings) will get an autogenerated one by combining `bundleIdPrefix` and the target name: `bundleIdPrefix.name`. The target name will be stripped of all characters that aren't alphanumerics, hyphens, or periods. Underscores will be replaced with hyphens. - [ ] **settingPresets**: **String** - This controls the settings that are automatically applied to the project and its targets. These are the same build settings that Xcode would add when creating a new project. Project settings are applied by config type. Target settings are applied by the product type and platform. By default this is set to `all` - `all`: project and target settings - `project`: only project settings - `targets`: only target settings - `none`: no settings are automatically applied - [ ] **developmentLanguage**: **String** - Sets the development language of the project. Defaults to `en` - [ ] **usesTabs**: **Bool** - If this is specified, the Xcode project will override the user's setting determining whether or not tabs or spaces should be used in the project. - [ ] **indentWidth**: **Int** - If this is specified, the Xcode project will override the user's setting for indent width in number of spaces. - [ ] **tabWidth**: **Int** - If this is specified, the Xcode project will override the user's setting for indent width in number of spaces. - [ ] **xcodeVersion**: **String** - The version of Xcode. This defaults to the latest version periodically. You can specify it in the format `0910` or `9.1` - [ ] **projectFormat**: **String** - The version of Xcode project. By default this is set to `xcode16_0` - `xcode16_3`: Xcode 16.3 - `xcode16_0`: Xcode 16.0 - `xcode15_3`: Xcode 15.3 - `xcode15_0`: Xcode 15.0 - `xcode14_0`: Xcode 14.0 - [ ] **deploymentTarget**: **[[Platform](#platform): String]** - A project wide deployment target can be specified for each platform otherwise the default SDK version in Xcode will be used. This will be overridden by any custom build settings that set the deployment target eg `IPHONEOS_DEPLOYMENT_TARGET`. Target specific deployment targets can also be set with [Target](#target).deploymentTarget. - [ ] **disabledValidations**: **[String]** - A list of validations that can be disabled if they're too strict for your use case. By default this is set to an empty array. Currently these are the available options: - `missingConfigs`: Disable errors for configurations in yaml files that don't exist in the project itself. This can be useful if you include the same yaml file in different projects - `missingConfigFiles`: Disable checking for the existence of configuration files. This can be useful for generating a project in a context where config files are not available. - `missingTestPlans`: Disable checking if test plan paths exist. This can be useful if your test plans haven't been created yet. - [ ] **defaultConfig**: **String** - The default configuration for command line builds from Xcode. If the configuration provided here doesn't match one in your [configs](#configs) key, XcodeGen will fail. If you don't set this, the first configuration alphabetically will be chosen. - [ ] **groupSortPosition**: **String** - Where groups are sorted in relation to other files. Either: - `none` - sorted alphabetically with all the other files - `top` - at the top, before files - `bottom` (default) - at the bottom, after other files - [ ] **groupOrdering**: **[[GroupOrdering]](#groupOrdering)** - An order of groups. - [ ] **transitivelyLinkDependencies**: **Bool** - If this is `true` then targets will link to the dependencies of their target dependencies. If a target should embed its dependencies, such as application and test bundles, it will embed these transitive dependencies as well. Some complex setups might want to set this to `false` and explicitly specify dependencies at every level. Targets can override this with [Target](#target).transitivelyLinkDependencies. Defaults to `false`. - [ ] **generateEmptyDirectories**: **Bool** - If this is `true` then empty directories will be added to project too else will be missed. Defaults to `false`. - [ ] **findCarthageFrameworks**: **Bool** - When this is set to `true`, all the individual frameworks for Carthage framework dependencies will automatically be found. This property can be overridden individually for each carthage dependency - for more details see See **findFrameworks** in the [Dependency](#dependency) section. Defaults to `false`. - [ ] **localPackagesGroup**: **String** - The group name that local packages are put into. This defaults to `Packages`. Use `""` to specify the project root. - [ ] **fileTypes**: **[String: [FileType](#filetype)]** - A list of default file options for specific file extensions across the project. Values in [Sources](#sources) will overwrite these settings. - [ ] **preGenCommand**: **String** - A bash command to run before the project has been generated. If the project isn't generated due to no changes when using the cache then this won't run. This is useful for running things like generating resources files before the project is regenerated. - [ ] **postGenCommand**: **String** - A bash command to run after the project has been generated. If the project isn't generated due to no changes when using the cache then this won't run. This is useful for running things like `pod install` only if the project is actually regenerated. - [ ] **useBaseInternationalization**: **Bool** If this is `false` and your project does not include resources located in a **Base.lproj** directory then `Base` will not be included in the projects 'known regions'. The default value is `true`. - [ ] **schemePathPrefix**: **String** - A path prefix for relative paths in schemes, such as StoreKitConfiguration. The default is `"../../"`, which is suitable for non-workspace projects. For use in workspaces, use `"../"`. - [ ] **defaultSourceDirectoryType**: **String** - When a [Target source](#target-source) doesn't specify a type and is a directory, this is the type that will be used. If nothing is specified for either then `group` will be used. - `group` (default) - `folder` - `syncedFolder`: Can be used starting from **projectFormat** `xcode16_0` ```yaml options: deploymentTarget: watchOS: "2.0" tvOS: "10.0" postGenCommand: pod install ``` ### GroupOrdering Describe an order of groups. Available parameters: - [ ] **pattern**: **String** - A group name pattern. Can be just a single string and also can be a regex pattern. Optional option, if you don't set it, it will pattern for the main group, i.e. the project. - [ ] **order**: **[String]** - An order of groups. ```yaml options: groupOrdering: - order: [Sources, Resources, Tests, Support files, Configurations] - pattern: '^.*Screen$' order: [View, Presenter, Interactor, Entities, Assembly] ``` In this example, we set up the order of two groups. First one is the main group, i.e. the project, note that in this case, we shouldn't set `pattern` option and the second group order is for groups whose names ends with `Screen`. ### FileType Default settings for file extensions. See [Sources](#sources) for more documentation on properties. If you overwrite an extension that XcodeGen already provides by default, you will need to provide all the settings. - [ ] **file**: **Bool** - Whether this extension should be treated like a file. Defaults to true. - [ ] **buildPhase**: **String** - The default build phase. - [ ] **attributes**: **[String]** - Additional settings attributes that will be applied to any build files. - [ ] **resourceTags**: **[String]** - On Demand Resource Tags that will be applied to any resources. This also adds to the project attribute's knownAssetTags. - [ ] **compilerFlags**: **[String]** - A list of compiler flags to add. ### Breakpoints - [x] **type**: **String** - Breakpoint type - `File`: file breakpoint - `Exception`: exception breakpoint - `SwiftError`: swift error breakpoint - `OpenGLError`: OpenGL breakpoint - `Symbolic`: symbolic breakpoint - `IDEConstraintError`: IDE constraint breakpoint - `IDETestFailure`: IDE test failure breakpoint - `RuntimeIssue`: Runtime issue breakpoint - [ ] **enabled**: **Bool** - Indicates whether it should be active. Default to `true` - [ ] **ignoreCount**: **Int** - Indicates how many times it should be ignored before stopping, Default to `0` - [ ] **continueAfterRunningActions**: **Bool** - Indicates if should automatically continue after evaluating actions, Default to `false` - [ ] **path**: **String** - Breakpoint file path (only required by file breakpoints) - [ ] **line**: **Int** - Breakpoint line (only required by file breakpoints) - [ ] **symbol**: **String** - Breakpoint symbol (only used by symbolic breakpoints) - [ ] **module**: **String** - Breakpoint module (only used by symbolic breakpoints) - [ ] **scope**: **String** - Breakpoint scope (only used by exception breakpoints) - `All` - `Objective-C` (default) - `C++` - [ ] **stopOnStyle**: **String** - Indicates if should stop on style (only used by exception breakpoints) -`throw` (default) -`catch` - [ ] **condition**: **String** - Breakpoint condition - [ ] **actions**: **[[Breakpoint Action](#breakpoint-action)]** - breakpoint actions ```yaml breakpoints: - type: ExceptionBreakpoint enabled: true ignoreCount: 0 continueAfterRunningActions: false ``` #### Breakpoint Action - [x] **type**: **String** - Breakpoint action type - `DebuggerCommand`: execute debugger command - `Log`: log message - `ShellCommand`: execute shell command - `GraphicsTrace`: capture GPU frame - `AppleScript`: execute AppleScript - `Sound`: play sound - [ ] **command**: **String** - Debugger command (only used by debugger command breakpoint action) - [ ] **message**: **String** - Log message (only used log message breakpoint action) - [ ] **conveyanceType**: **String** - Conveyance type (only used by log message breakpoint action) - `console`: log message to console (default) - `speak`: speak message - [ ] **path**: **String** - Shell command file path (only used by shell command breakpoint action) - [ ] **arguments**: **String** - Shell command arguments (only used by shell command breakpoint action) - [ ] **waitUntilDone**: **Bool** - Indicates whether it should wait until done (only used by shell command breakpoint action). Default to `false` - [ ] **script**: **String** - AppleScript (only used by AppleScript breakpoint action) - [ ] **sound**: **String** - Sound name (only used by sound breakpoint action) - `Basso` (default) - `Blow` - `Bottle` - `Frog` - `Funk` - `Glass` - `Hero` - `Morse` - `Ping` - `Pop` - `Purr` - `Sosumi` - `Submarine` - `Tink` ```yaml actions: - type: Sound sound: Blow ``` ### Configs Each config maps to a build type of either `debug` or `release` which will then apply default `Build Settings` to the project. Any value other than `debug` or `release` (for example `none`), will mean no default `Build Settings` will be applied to the project. ```yaml configs: Debug: debug Beta: release AppStore: release ``` If no configs are specified, default `Debug` and `Release` configs will be created automatically. ### Setting Groups Setting groups are named groups of `Build Settings` that can be reused elsewhere. Each preset is a [Settings](#settings) schema, so can include other `groups` or define settings by `configs`. ```yaml settingGroups: preset_generic: CUSTOM_SETTING: value_custom preset_debug: BUILD_SETTING: value_debug preset_release: base: BUILD_SETTING: value_release preset_all: groups: - preset_generic configs: debug: groups: - preset_debug release: groups: - preset_release targets: Application: settings: groups: - preset_all ``` ## Settings Settings correspond to `Build Settings` tab in Xcode. To display Setting Names instead of Setting Titles, select `Editor -> Show Setting Names` in Xcode. Settings can either be a simple map of `Build Settings` `[String:String]`, or can be more advanced with the following properties: - [ ] **groups**: **[String]** - List of [Setting Groups](#setting-groups) to include and merge - [ ] **configs**: **[String:[Settings](#settings)]** - Mapping of config name to a settings spec. These settings will only be applied for that config. Each key will be matched to any configs that contain the key and is case insensitive. So if you had `Staging Debug` and `Staging Release`, you could apply settings to both of them using `staging`. However if a config name is an exact match to a config it won't be applied to any others. eg `Release` will be applied to config `Release` but not `Staging Release` - [ ] **base**: **[String:String]** - Used to specify default settings that apply to any config ```yaml settings: GENERATE_INFOPLIST_FILE: NO CODE_SIGNING_ALLOWED: NO WRAPPER_EXTENSION: bundle ``` Don't mix simple maps with `groups`, `base` and `configs`. If `groups`, `base`, `configs` are used then simple maps is silently ignored. In this example, `CURRENT_PROJECT_VERSION` will be set, but `MARKETING_VERSION` will be ignored: ```yaml settings: MARKETING_VERSION: 100.0.0 base: CURRENT_PROJECT_VERSION: 100.0 ``` ```yaml settings: base: PRODUCT_NAME: XcodeGenProduct configs: debug: CODE_SIGN_IDENTITY: iPhone Developer PRODUCT_BUNDLE_IDENTIFIER: com.tomtom.debug_app release: CODE_SIGN_IDENTITY: iPhone Distribution PRODUCT_BUNDLE_IDENTIFIER: com.tomtom.app PROVISIONING_PROFILE_SPECIFIER: "Xcodegen Release" groups: - my_settings ``` Settings are merged in the following order: `groups`, `base`, `configs` (simple maps are ignored). ## Target - [x] **type**: **[Product Type](#product-type)** - Product type of the target - [x] **platform**: **[Platform](#platform)** - Platform of the target - [ ] **supportedDestinations**: **[[Supported Destinations](#supported-destinations)]** - List of supported platform destinations for the target. - [ ] **deploymentTarget**: **String** - The deployment target (eg `9.2`). If this is not specified the value from the project set in [Options](#options)`.deploymentTarget.PLATFORM` will be used. - [ ] **sources**: **[Sources](#sources)** - Source directories of the target - [ ] **configFiles**: **[Config Files](#config-files)** - `.xcconfig` files per config - [ ] **settings**: **[Settings](#settings)** - Target specific build settings. Default platform and product type settings will be applied first before any custom settings defined here. Other context dependant settings will be set automatically as well: - `INFOPLIST_FILE`: If it doesn't exist your sources will be searched for `Info.plist` files and the first one found will be used for this setting - `FRAMEWORK_SEARCH_PATHS`: If carthage framework dependencies are used, the platform build path will be added to this setting - `OTHER_LDFLAGS`: See `requiresObjCLinking` below - `TEST_TARGET_NAME`: for ui tests that target an application - `TEST_HOST`: for unit tests that target an application - [ ] **dependencies**: **[[Dependency](#dependency)]** - Dependencies for the target - [ ] **info**: **[Plist](#plist)** - If defined, this will generate and write an `Info.plist` to the specified path and use it by setting the `INFOPLIST_FILE` build setting for every configuration, unless `INFOPLIST_FILE` is already defined in **settings** for this configuration. The following properties are generated automatically if appropriate, the rest will have to be provided. - `CFBundleIdentifier` - `CFBundleInfoDictionaryVersion` - `CFBundleExecutable` **Not generated for targets of type bundle** - `CFBundleName` - `CFBundleDevelopmentRegion` - `CFBundleShortVersionString` - `CFBundleVersion` - `CFBundlePackageType` - [ ] **entitlements**: **[Plist](#plist)** - If defined this will generate and write a `.entitlements` file, and use it by setting `CODE_SIGN_ENTITLEMENTS` build setting for every configuration. All properties must be provided - [ ] **templates**: **[String]** - A list of [Target Templates](#target-template) referenced by name that will be merged with the target in order. Any instances of `${target_name}` within these templates will be replaced with the target name. - [ ] **templateAttributes**: **[String: String]** - A list of attributes where each instance of `${attributeName}` within the templates listed in `templates` will be replaced with the value specified. - [ ] **transitivelyLinkDependencies**: **Bool** - If this is not specified the value from the project set in [Options](#options)`.transitivelyLinkDependencies` will be used. - [ ] **directlyEmbedCarthageDependencies**: **Bool** - If this is `true` Carthage framework dependencies will be embedded using an `Embed Frameworks` build phase instead of the `copy-frameworks` script. Defaults to `true` for all targets except iOS/tvOS/watchOS Applications. - [ ] **requiresObjCLinking**: **Bool** - If this is `true` any targets that link to this target will have `-ObjC` added to their `OTHER_LDFLAGS`. This is required if a static library has any categories or extensions on Objective-C code. See [this guide](https://pewpewthespells.com/blog/objc_linker_flags.html#objc) for more details. Defaults to `true` if `type` is `library.static`. If you are 100% sure you don't have categories or extensions on Objective-C code (pure Swift with no use of Foundation/UIKit) you can set this to `false`, otherwise it's best to leave it alone. - [ ] **onlyCopyFilesOnInstall**: **Bool** – If this is `true`, the `Embed Frameworks` and `Embed App Extensions` (if available) build phases will have the "Copy only when installing" chekbox checked. Defaults to `false`. - [ ] **buildToolPlugins**: **[[Build Tool Plug-ins](#build-tool-plug-ins)]** - Commands for the build system that run automatically *during* the build. - [ ] **preBuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *before* any other build phases - [ ] **postCompileScripts**: **[[Build Script](#build-script)]** - Build scripts that run after the Compile Sources phase - [ ] **postBuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *after* any other build phases - [ ] **buildRules**: **[[Build Rule](#build-rule)]** - Custom build rules - [ ] **scheme**: **[Target Scheme](#target-scheme)** - Generated scheme with tests or config variants - [ ] **legacy**: **[Legacy Target](#legacy-target)** - When present, opt-in to make an Xcode "External Build System" legacy target instead. - [ ] **attributes**: **[String: Any]** - This sets values in the project `TargetAttributes`. It is merged with `attributes` from the project and anything automatically added by XcodeGen, with any duplicate values being override by values specified here. This is for advanced use only. Properties that are already set include: - `DevelopmentTeam`: if all configurations have the same `DEVELOPMENT_TEAM` setting - `ProvisioningStyle`: if all configurations have the same `CODE_SIGN_STYLE` setting - `TestTargetID`: if all configurations have the same `TEST_TARGET_NAME` setting - [ ] **putResourcesBeforeSourcesBuildPhase**: **Bool** - If this is `true` the `Copy Resources` step will be placed before the `Compile Sources` build step. ### Product Type This will provide default build settings for a certain product type. It can be any of the following: - `application` - `application.on-demand-install-capable` - `application.messages` - `application.watchapp` - `application.watchapp2` - `application.watchapp2-container` - `app-extension` - `app-extension.intents-service` - `app-extension.messages` - `app-extension.messages-sticker-pack` - `bundle` - `bundle.ocunit-test` - `bundle.ui-testing` - `bundle.unit-test` - `extensionkit-extension` - `framework` - `instruments-package` - `library.dynamic` - `library.static` - `framework.static` - `tool` - `tv-app-extension` - `watchkit-extension` - `watchkit2-extension` - `xcode-extension` - `driver-extension` - `system-extension` - `xpc-service` - ``""`` (used for legacy targets) ### Platform This will provide default build settings for a certain platform. It can be any of the following: - `auto` (available only when we use `supportedDestinations`) - `iOS` - `tvOS` - `macOS` - `watchOS` - `visionOS` (`visionOS` doesn't support Carthage usage) Note that when we use supported destinations with Xcode 14+ we can avoid the definition of platform that fallbacks to the `auto` value. **Multi Platform targets** You can also specify an array of platforms. This will generate a target for each platform. If `deploymentTarget` is specified for a multi platform target, it can have different values per platform similar to how it's defined in [Options](#options). See below for an example. If you reference the string `${platform}` anywhere within the target spec, that will be replaced with the platform. The generated targets by default will have a suffix of `_${platform}` applied, you can change this by specifying a `platformSuffix` or `platformPrefix`. If no `PRODUCT_NAME` build setting is specified for a target, this will be set to the target name, so that this target can be imported under a single name. ```yaml targets: MyFramework: sources: MyFramework platform: [iOS, tvOS] deploymentTarget: iOS: 9.0 tvOS: 10.0 type: framework settings: base: INFOPLIST_FILE: MyApp/Info.plist PRODUCT_BUNDLE_IDENTIFIER: com.myapp MY_SETTING: platform ${platform} groups: - ${platform} ``` The above will generate 2 targets named `MyFramework_iOS` and `MyFramework_tvOS`, with all the relevant platform build settings. They will both have a `PRODUCT_NAME` of `MyFramework` ### Supported Destinations This will provide a mix of default build settings for the chosen platform destinations. It can be any of the following: - `iOS` - `tvOS` - `macOS` - `macCatalyst` - `visionOS` - `watchOS` ```yaml targets: MyFramework: type: framework supportedDestinations: [iOS, tvOS] deploymentTarget: iOS: 9.0 tvOS: 10.0 sources: - path: MySources inferDestinationFiltersByPath: true - path: OtherSources destinationFilters: [iOS] ``` Note that the definition of supported destinations can be applied to almost every type of bundle making everything more easy to manage (app targets, unit tests, UI tests etc). App targets currently do not support the watchOS destination. Create a separate target using `platform` for watchOS apps. See Apple's [Configuring a multiplatform app](https://developer.apple.com/documentation/xcode/configuring-a-multiplatform-app-target) for details. ### Sources Specifies the source directories for a target. This can either be a single source or a list of sources. Applicable source files, resources, headers, and `.lproj` files will be parsed appropriately. A source can be provided via a string (the path) or an object of the form: #### Target Source - [x] **path**: **String** - The path to the source file or directory. - [ ] **name**: **String** - Can be used to override the name of the source file or directory. By default the last component of the path is used for the name - [ ] **group**: **String** - Can be used to override the parent group of the source file or directory. By default a group is created at the root with the name of this source file or directory or intermediate groups are created if `createIntermediateGroups` is set to `true`. Multiple groups can be created by separating each one using a `/`. If multiple target sources share the same `group`, they will be put together in the same parent group. - [ ] **compilerFlags**: **[String]** or **String** - A list of compilerFlags to add to files under this specific path provided as a list or a space delimited string. Defaults to empty. - [ ] **excludes**: **[String]** - A list of [global patterns](https://en.wikipedia.org/wiki/Glob_(programming)) representing the files to exclude. These rules are relative to `path` and _not the directory where `project.yml` resides_. XcodeGen uses Bash 4's Glob behaviors where globstar (**) is enabled. - [ ] **includes**: **[String]** - A list of global patterns in the same format as `excludes` representing the files to include. These rules are relative to `path` and _not the directory where `project.yml` resides_. If **excludes** is present and file conflicts with **includes**, **excludes** will override the **includes** behavior. - [ ] **explicitFolders**: **[String]** - Only valid for `syncedFolder` type. A list of global patterns in the same format as `excludes` to child folders that Xcode should treat as folder references. - [ ] **destinationFilters**: **[[Supported Destinations](#supported-destinations)]** - List of supported platform destinations the files should filter to. Defaults to all supported destinations. - [ ] **inferDestinationFiltersByPath**: **Bool** - This is a convenience filter that helps you to filter the files if their paths match these patterns `**//*` or `*_.swift`. Note, if you use `destinationFilters` this flag will be ignored. - [ ] **createIntermediateGroups**: **Bool** - This overrides the value in [Options](#options). - [ ] **optional**: **Bool** - Disable missing path check. Defaults to false. - [ ] **buildPhase**: **String** - This manually sets the build phase this file or files in this directory will be added to, otherwise XcodeGen will guess based on the file extension. Note that `Info.plist` files will never be added to any build phases, no matter what this setting is. Possible values are: - `sources` - Compile Sources phase - `resources` - Copy Bundle Resources phase - `headers` - Headers Phase - `copyFiles` - Copy Files Phase. Must be specified as an object with the following fields: - [x] **destination**: **String** - Destination of the Copy Files phase. This can be one of the following values: - `absolutePath` - `productsDirectory` - `wrapper` - `executables` - `resources` - `javaResources` - `frameworks` - `sharedFrameworks` - `sharedSupport` - `plugins` - [ ] **subpath**: **String** - The path inside of the destination to copy the files. - `none` - Will not be added to any build phases - [ ] **type**: **String**: This can be one of the following values - `file`: a file reference with a parent group will be created (Default for files or directories with extensions) - `group`: a group with all it's containing files. (Default for directories without extensions) - `folder`: a folder reference. - `syncedFolder`: Xcode 16's synchronized folders, also knows as buildable folders - [ ] **headerVisibility**: **String** - The visibility of any headers. This defaults to `public`, but can be either: - `public` - `private` - `project` - [ ] **attributes**: **[String]** - Additional settings attributes that will be applied to any build files. - [ ] **resourceTags**: **[String]** - On Demand Resource Tags that will be applied to any resources. This also adds to the project attribute's knownAssetTags ```yaml targets: MyTarget: sources: MyTargetSource MyOtherTarget: supportedDestinations: [iOS, tvOS] sources: - MyOtherTargetSource1 - path: MyOtherTargetSource2 inferDestinationFiltersByPath: true name: MyNewName excludes: - "ios/*.[mh]" - "configs/server[0-2].json" - "*-Private.h" - "**/*.md" # excludes all files with the .md extension - "ios/**/*Tests.[hm]" # excludes all files with an h or m extension within the ios directory. compilerFlags: - "-Werror" - "-Wextra" - path: MyOtherTargetSource3 destinationFilters: [iOS] compilerFlags: "-Werror -Wextra" - path: ModuleMaps buildPhase: copyFiles: destination: productsDirectory subpath: include/$(PRODUCT_NAME) - path: Resources type: folder - path: Path/To/File.asset resourceTags: [tag1, tag2] ``` ### Dependency A dependency can be one of a 6 types: - `target: name` - links to another target. If you are using project references you can specify a target within another project by using `ProjectName/TargetName` for the name - `framework: path` - links to a framework or XCFramework - `carthage: name` - helper for linking to a Carthage framework (not XCFramework) - `sdk: name` - links to a dependency with the SDK. This can either be a relative path within the sdk root or a single filename that references a framework (.framework) or lib (.tbd) - `package: name` - links to a Swift Package. The name must match the name of a package defined in the top level `packages` - `bundle: name` - adds the pre-built bundle for the supplied name to the copy resources build phase. This is useful when a dependency exists on a static library target that has an associated bundle target, both existing in a separate project. Only usable in target types which can copy resources. **Linking options**: - [ ] **embed**: **Bool** - Whether to embed the dependency. Defaults to true for application target and false for non application targets. - [ ] **link**: **Bool** - Whether to link the dependency. Defaults to `true` depending on the type of the dependency and the type of the target (e.g. static libraries will only link to executables by default). - [ ] **codeSign**: **Bool** - Whether the `codeSignOnCopy` setting is applied when embedding framework. Defaults to true. - [ ] **removeHeaders**: **Bool** - Whether the `removeHeadersOnCopy` setting is applied when embedding the framework. Defaults to true. - [ ] **weak**: **Bool** - Whether the `Weak` setting is applied when linking the framework. Defaults to false. - [ ] **platformFilter**: **String** - This field is specific to Mac Catalyst. It corresponds to the "Platforms" dropdown in the Frameworks & Libraries section of Target settings in Xcode. Available options are: **iOS**, **macOS** and **all**. Defaults is **all**. - [ ] **destinationFilters**: **[[Supported Destinations](#supported-destinations)]** - List of supported platform destinations this dependency should filter to. Defaults to all supported destinations. - [ ] **platforms**: **[[Platform](#platform)]** - List of platforms this dependency should apply to. Defaults to all applicable platforms. - **copy** - Copy Files Phase for this dependency. This only applies when `embed` is true. Must be specified as an object with the following fields: - [x] **destination**: **String** - Destination of the Copy Files phase. This can be one of the following values: - `absolutePath` - `productsDirectory` - `wrapper` - `executables` - `resources` - `javaResources` - `frameworks` - `sharedFrameworks` - `sharedSupport` - `plugins` - [ ] **subpath**: **String** - The path inside of the destination to copy the files. **Implicit Framework options**: This only applies to `framework` dependencies. Implicit framework dependencies are useful in Xcode Workspaces which have multiple `.xcodeproj` that are not embedded within each other yet have a dependency on a framework built in an adjacent `.xcodeproj`. By having `Find Implicit Dependencies` checked within your scheme `Build Options` Xcode can link built frameworks in `BUILT_PRODUCTS_DIR`. - [ ] **implicit**: **Bool** - Whether the framework is an implicit dependency. Defaults to `false` . **Carthage Dependency** - [ ] **findFrameworks**: **Bool** - Whether to find Carthage frameworks automatically. Defaults to `true` . - [ ] **linkType**: **String** - Dependency link type. This value should be `dynamic` or `static`. Default value is `dynamic` . Carthage frameworks are expected to be in `CARTHAGE_BUILD_PATH/PLATFORM/FRAMEWORK.framework` where: - `CARTHAGE_BUILD_PATH` = `options.carthageBuildPath` or `Carthage/Build` by default - `PLATFORM` = the target's platform - `FRAMEWORK` = the specified name. To link an XCFramework produced by Carthage (in `CARTHAGE_BUILD_PATH/FRAMEWORK.xcframework`), use a normal `framework:` dependency. The helper logic provided by this dependency type is not necessary. All the individual frameworks of a Carthage dependency can be automatically found via `findFrameworks: true`. This overrides the value of [Options](#options).findCarthageFrameworks. Otherwise each one will have to be listed individually. Xcodegen uses `.version` files generated by Carthage in order for this framework lookup to work, so the Carthage dependencies will need to have already been built at the time XcodeGen is run. If any applications contain carthage dependencies within itself or any dependent targets, a carthage copy files script is automatically added to the application containing all the relevant frameworks. A `FRAMEWORK_SEARCH_PATHS` setting is also automatically added Carthage officially supports static frameworks. In this case, frameworks are expected to be in `CARTHAGE_BUILD_PATH/PLATFORM/Static/FRAMEWORK.framework`. You can specify `linkType` to `static` to integrate static ones. ```yaml projectReferences: FooLib: path: path/to/FooLib.xcodeproj targets: MyTarget: supportedDestinations: [iOS, tvOS] dependencies: - target: MyFramework destinationFilters: [iOS] - target: FooLib/FooTarget - framework: path/to/framework.framework destinationFilters: [tvOS] - carthage: Result findFrameworks: false linkType: static destinationFilters: [iOS] - sdk: Contacts.framework - sdk: libc++.tbd - sdk: libz.dylib MyFramework: type: framework ``` **SDK Dependency** - [ ] **root**: **String** - Root of framework path, for example `DEVELOPER_DIR`. Default value is `BUILT_PRODUCTS_DIR` ```yaml targets: MyTestTarget: dependencies: - target: MyFramework - framework: path/to/framework.framework - sdk: Contacts.framework - sdk: Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest root: DEVELOPER_DIR MyFramework: type: framework ``` **Package dependency** - [ ] **product**: **String** - The product to use from the package. This defaults to the package name, so is only required if a Package has multiple libraries or a library with a differing name. Use this over `products` when you want to define different linking options per product. - [ ] **products**: **String** - A list of products to use from the package. This can be used when depending on multiple products from a package. ```yaml packages: Yams: url: https://github.com/jpsim/Yams majorVersion: 2.0.0 SwiftPM: url: https://github.com/apple/swift-package-manager branch: swift-5.0-branch targets: App: dependencies: - package: Yams - package: SwiftPM product: SPMUtility ``` Depending on multiple products from a package: ```yaml packages: FooFeature: path: Packages/FooFeature targets: App: dependencies: - package: FooFeature products: - FooDomain - FooUI ``` ### Config Files Specifies `.xcconfig` files for each configuration. ```yaml configFiles: Debug: debug.xcconfig Release: release.xcconfig targets: App: configFiles: Debug: App/debug.xcconfig Release: App/release.xcconfig ``` ### Plist Plists are created on disk on every generation of the project. They can be used as a way to define `Info.plist` or `.entitlement` files. Some `Info.plist` properties are generated automatically. - [x] **path**: **String** - This is the path where the plist will be written to - [x] **properties**: **[String: Any]** - This is a map of all the plist keys and values ```yml targets: App: info: path: App/Info.plist properties: UISupportedInterfaceOrientations: [UIInterfaceOrientationPortrait] UILaunchStoryboardName: LaunchScreen entitlements: path: App/App.entitlements properties: com.apple.security.application-groups: group.com.app ``` ### Build Tool Plug-ins To add `Build Tool Plug-ins`, you need to add information about plugins to [Target](#target): - **buildToolPlugins**: List of plugins to connect to the target Each plugin includes information: - [x] **plugin**: **String** - plugin name - [x] **package**: **String** - the name of the package that contains the plugin Сonnect the plugin to the desired target: ```yaml targets: App: buildToolPlugins: - plugin: MyPlugin package: MyPackage ``` Don't forget to add a package containing the plugin we need: ```yaml packages: MyPackage: url: https://github.com/MyPackage from: 1.3.0 ``` ### Build Script Run script build phases can be added at 3 different points in the build: - **preBuildScripts**: Before any other build phases - **postCompileScripts**: After the compile sources build phase - **postBuildScripts**: After any other build phases Each script can contain: - [x] **path**: **String** - a relative or absolute path to a shell script - [x] **script**: **String** - an inline shell script - [ ] **name**: **String** - name of a script. Defaults to `Run Script` - [ ] **inputFiles**: **[String]** - list of input files - [ ] **outputFiles**: **[String]** - list of output files - [ ] **inputFileLists**: **[String]** - list of input .xcfilelist - [ ] **outputFileLists**: **[String]** - list of output .xcfilelist - [ ] **shell**: **String** - shell used for the script. Defaults to `/bin/sh` - [ ] **showEnvVars**: **Bool** - whether the environment variables accessible to the script show be printed to the build log. Defaults to `true` - [ ] **runOnlyWhenInstalling**: **Bool** - whether the script is only run when installing (`runOnlyForDeploymentPostprocessing`). Defaults to `false` - [ ] **basedOnDependencyAnalysis**: **Bool** - whether to skip the script if inputs, context, or outputs haven't changed. Defaults to `true` - [ ] **discoveredDependencyFile**: **String** - discovered dependency .d file. Defaults to none Either a **path** or **script** must be defined, the rest are optional. A multiline script can be written using the various YAML multiline methods, for example with `|` as below: ```yaml targets: MyTarget: preBuildScripts: - path: myscripts/my_script.sh name: My Script inputFiles: - $(SRCROOT)/file1 - $(SRCROOT)/file2 inputFileLists: - $(SRCROOT)/inputFiles.xcfilelist outputFiles: - $(DERIVED_FILE_DIR)/file1 - $(DERIVED_FILE_DIR)/file2 outputFileLists: - $(SRCROOT)/outputFiles.xcfilelist discoveredDependencyFile: $(DERIVED_FILE_DIR)/target.d postCompileScripts: - script: swiftlint name: Swiftlint - script: | command do othercommand postBuildScripts: - path: myscripts/my_final_script.sh name: My Final Script ``` ### Build Rule - [ ] **filePattern**: **String** - A glob pattern for the files that will have the build rule run on them. This or `fileType` must be defined - [ ] **fileType**: **String** - A file type determined by Xcode. The available types can be seen by hovering your mouse of the `Process` dropdown in the Xcode interface. For example `sourcecode.swift` or `file.xib`. This or `filePattern` must be defined. - [ ] **script**: **String** - The script that will be run on each file. This or `compilerSpec` must be defined. - [ ] **compilerSpec**: **String**: A reference to a built in apple tool to run on each file. This is for advanced use and the the values for this must be checked. This or `script` must be defined. - [ ] **name**: **String** - The name of a build rule. Defaults to `Build Rule` - [ ] **outputFiles**: **[String]** - The list of output files - [ ] **outputFilesCompilerFlags**: **[String]** - The list of compiler flags to apply to the output files - [ ] **runOncePerArchitecture**: **Bool** - a boolean that indicates if this rule should run once per architecture. This defaults to true ```yaml targets: MyTarget: buildRules: - filePattern: "*.xcassets" script: generate_assets.py - fileType: sourcecode.swift script: pre_process_swift.py - filePattern: "*.txt" name: My Build Rule compilerSpec: com.apple.xcode.tools.swift.compiler outputFiles: - $(SRCROOT)/Generated.swift runOncePerArchitecture: false ``` ### Target Scheme This is a convenience used to automatically generate schemes for a target based on different configs or included tests. If you want more control check out the top level [Scheme](#scheme). - [x] **configVariants**: **[String]** - This generates a scheme for each entry, using configs that contain the name with debug and release variants. This is useful for having different environment schemes. - [ ] **testTargets**: **[[Test Target](#test-target)]** - a list of test targets that should be included in the scheme. These will be added to the build targets and the test entries. Each entry can either be a simple string, or a [Test Target](#test-target) - [ ] **gatherCoverageData**: **Bool** - a boolean that indicates if this scheme should gather coverage data. This defaults to false - [ ] **coverageTargets**: **[[Testable Target Reference](#testable-target-reference) - a list of targets to gather code coverage. Each entry can either be a simple string, a string using [Project Reference](#project-reference) or [Testable Target Reference](#testable-target-reference) - [ ] **disableMainThreadChecker**: **Bool** - a boolean that indicates if this scheme should disable the Main Thread Checker. This defaults to false - [ ] **stopOnEveryMainThreadCheckerIssue**: **Bool** - a boolean that indicates if this scheme should stop at every Main Thread Checker issue. This defaults to false - [ ] **disableThreadPerformanceChecker**: **Bool** - a boolean that indicates if this scheme should disable the Thread Performance Checker. This defaults to false - [ ] **buildImplicitDependencies**: **Bool** - Flag to determine if Xcode should build implicit dependencies of this scheme. By default this is `true` if not set. - [ ] **language**: **String** - a String that indicates the language used for running and testing. This defaults to nil - [ ] **region**: **String** - a String that indicates the region used for running and testing. This defaults to nil - [ ] **commandLineArguments**: **[String:Bool]** - a dictionary from the argument name (`String`) to if it is enabled (`Bool`). These arguments will be added to the Test, Profile and Run scheme actions - [ ] **environmentVariables**: **[[Environment Variable](#environment-variable)]** or **[String:String]** - environment variables for Run, Test and Profile scheme actions. When passing a dictionary, every key-value entry maps to a corresponding variable that is enabled. - [ ] **testPlans**: **[[Test Plan](#test-plan)]** - List of test plan locations that will be referenced in the scheme. - [ ] **preActions**: **[[Execution Action](#execution-action)]** - Scripts that are run *before* the build action - [ ] **postActions**: **[[Execution Action](#execution-action)]** - Scripts that are run *after* the build action - [ ] **management**: **[Scheme Management](#scheme-management)** - Management options for the scheme - [ ] **storeKitConfiguration**: **String** - specify storekit configuration to use during run. See [Options](#options). For example, the spec below would create 3 schemes called: - MyApp Test - MyApp Staging - MyApp Production Each scheme would use different build configuration for the different build types, specifically debug configs for `run`, `test`, and `analyze`, and release configs for `profile` and `archive`. The MyUnitTests target would also be linked. ```yaml configs: Test Debug: debug Staging Debug: debug Production Debug: debug Test Release: release Staging Release: release Production Release: release targets: MyApp: scheme: testTargets: - MyUnitTests configVariants: - Test - Staging - Production gatherCoverageData: true coverageTargets: - MyTarget1 - ExternalTarget/OtherTarget1 commandLineArguments: "-MyEnabledArg": true "-MyDisabledArg": false environmentVariables: MY_ENV_VAR: VALUE MyUnitTests: sources: Tests ``` ### Legacy Target By providing a legacy target, you are opting in to the "Legacy Target" mode. This is the "External Build Tool" from the Xcode GUI. This is useful for scripts that you want to run as dependencies of other targets, but you want to make sure that it only runs once even if it is specified as a dependency from multiple other targets. - [x] ***toolPath***: String - Path to the build tool used in the legacy target. - [ ] ***arguments***: String - Build arguments used for the build tool in the legacy target - [ ] ***passSettings***: Bool - Whether or not to pass build settings down to the build tool in the legacy target. - [ ] ***workingDirectory***: String - The working directory under which the build tool will be invoked in the legacy target. ## Aggregate Target This is used to override settings or run build scripts in specific targets - [x] **targets**: **[String]** - The list of target names to include as target dependencies - [ ] **configFiles**: **[Config Files](#config-files)** - `.xcconfig` files per config - [ ] **settings**: **[Settings](#settings)** - Target specific build settings. - [ ] **buildToolPlugins**: **[[Build Tool Plug-ins](#build-tool-plug-ins)]** - Commands for the build system that run automatically *during* the build - [ ] **buildScripts**: **[[Build Script](#build-script)]** - Build scripts to run - [ ] **scheme**: **[Target Scheme](#target-scheme)** - Generated scheme - [ ] **attributes**: **[String: Any]** - This sets values in the project `TargetAttributes`. It is merged with `attributes` from the project and anything automatically added by XcodeGen, with any duplicate values being override by values specified here ## Target Template This is a template that can be referenced from a normal target using the `templates` property. The properties of this template are the same as a [Target](#target). Any instances of `${target_name}` within each template will be replaced by the final target name which references the template. Any attributes defined within a targets `templateAttributes` will be used to replace any attribute references in the template using the syntax `${attribute_name}`. ```yaml targets: MyFramework: templates: - Framework templateAttributes: frameworkName: AwesomeFramework sources: - SomeSources targetTemplates: Framework: platform: iOS type: framework sources: - ${frameworkName}/${target_name} ``` ## Scheme Schemes allows for more control than the convenience [Target Scheme](#target-scheme) on [Target](#target) - [x] ***build***: Build options - [ ] ***run***: The run action - [ ] ***test***: The test action - [ ] ***profile***: The profile action - [ ] ***analyze***: The analyze action - [ ] ***archive***: The archive action - [ ] ***management***: management metadata ### Build - [x] **targets**: **[String:String]** or **[String:[String]]** - A map of target names to build and which build types they should be enabled for. The build types can be `all`, `none`, or an array of the following types: - `run` or `running` - `test` or `testing` - `profile` or `profiling` - `analyze` or `analyzing` - `archive` or `archiving` - [ ] **parallelizeBuild**: **Bool** - Whether or not your targets should be built in parallel. By default this is `true` if not set. - `true`: Build targets in parallel - `false`: Build targets serially - [ ] **buildImplicitDependencies**: **Bool** - Flag to determine if Xcode should build implicit dependencies of this scheme. By default this is `true` if not set. - `true`: Discover implicit dependencies of this scheme - `false`: Only build explicit dependencies of this scheme - [ ] **runPostActionsOnFailure**: **Bool** - Flag to determine if Xcode should run post scripts despite failure build. By default this is `false` if not set. - `true`: Run post scripts even if build is failed - `false`: Only run post scripts if build success ```yaml targets: MyTarget: all FooLib/FooTarget: [test, run] parallelizeBuild: true buildImplicitDependencies: true ``` ### Common Build Action options The different actions share some properties: - [ ] **config**: **String** - All build actions can be set to use a certain config. If a config, or the build action itself, is not defined the first configuration found of a certain type will be used, depending on the type: - `debug`: run, test, analyze - `release`: profile, archive - [ ] **commandLineArguments**: **[String:Bool]** - `run`, `test` and `profile` actions have a map of command line arguments to whether they are enabled - [ ] **preActions**: **[[Execution Action](#execution-action)]** - Scripts that are run *before* the action - [ ] **postActions**: **[[Execution Action](#execution-action)]** - Scripts that are run *after* the action - [ ] **environmentVariables**: **[[Environment Variable](#environment-variable)]** or **[String:String]** - `run`, `test` and `profile` actions can define the environment variables. When passing a dictionary, every key-value entry maps to a corresponding variable that is enabled. - [ ] **enableGPUFrameCaptureMode**: **GPUFrameCaptureMode** - Property value set for `GPU Frame Capture`. Possible values are `autoEnabled`, `metal`, `openGL`, `disabled`. Default is `autoEnabled`. - [ ] **enableGPUValidationMode**: **Bool** - Property value set for `Metal API Validation`. This defaults to true. - [ ] **disableMainThreadChecker**: **Bool** - `run` and `test` actions can define a boolean that indicates that this scheme should disable the Main Thread Checker. This defaults to false - [ ] **stopOnEveryMainThreadCheckerIssue**: **Bool** - a boolean that indicates if this scheme should stop at every Main Thread Checker issue. This defaults to false - [ ] **disableThreadPerformanceChecker**: **Bool** - `run` action can define a boolean that indicates that this scheme should disable the Thread Performance Checker. This defaults to false - [ ] **language**: **String** - `run` and `test` actions can define a language that is used for Application Language - [ ] **region**: **String** - `run` and `test` actions can define a language that is used for Application Region - [ ] **debugEnabled**: **Bool** - `run` and `test` actions can define a whether debugger should be used. This defaults to true. - [ ] **simulateLocation**: **[Simulate Location](#simulate-location)** - `run` action can define a simulated location - [ ] **askForAppToLaunch**: **Bool** - `run` and `profile` actions can define the executable set to ask to launch. This defaults to false. - [ ] **launchAutomaticallySubstyle**: **String** - `run` action can define the launch automatically substyle ('2' for extensions). - [ ] **storeKitConfiguration**: **String** - `run` action can specify a storekit configuration. See [Options](#options). - [ ] **macroExpansion**: **String** - `run` and `test` actions can define the macro expansion from other target. This defaults to nil. ### Execution Action Scheme run scripts added via **preActions** or **postActions**. They run before or after a build action, respectively, and in the order defined. Each execution action can contain: - [x] **script**: **String** - an inline shell script - [ ] **name**: **String** - name of a script. Defaults to `Run Script` - [ ] **settingsTarget**: **String** - name of a build or test target whose settings will be available as environment variables. A multiline script can be written using the various YAML multiline methods, for example with `|`. See [Build Script](#build-script). ### Run Action - [ ] **executable**: **String** - the name of the target to launch as an executable. Defaults to the first runnable build target in the scheme, or the first build target if a runnable build target is not found - [ ] **customLLDBInit**: **String** - the absolute path to the custom `.lldbinit` file - [ ] **customWorkingDirectory**: **String** - a path to use as the working directory when launching the executable. ### Test Action - [ ] **gatherCoverageData**: **Bool** - a boolean that indicates if this scheme should gather coverage data. This defaults to false - [ ] **coverageTargets**: **[[Testable Target Reference](#testable-target-reference)]** - a list of targets to gather code coverage. Each entry can either be a simple string, a string using [Project Reference](#project-reference) or [Testable Target Reference](#testable-target-reference) - [ ] **targets**: **[[Test Target](#test-target)]** - a list of targets to test. Each entry can either be a simple string, or a [Test Target](#test-target) - [ ] **customLLDBInit**: **String** - the absolute path to the custom `.lldbinit` file - [ ] **captureScreenshotsAutomatically**: **Bool** - indicates whether screenshots should be captured automatically while UI Testing. This defaults to true. - [ ] **deleteScreenshotsWhenEachTestSucceeds**: **Bool** - whether successful UI tests should cause automatically-captured screenshots to be deleted. If `captureScreenshotsAutomatically` is false, this value is ignored. This defaults to true. - [ ] **testPlans**: **[[Test Plan](#test-plan)]** - List of test plan locations that will be referenced in the scheme. - [ ] **preferredScreenCaptureFormat**: **String** - automatic screen capture format to use while UI Testing. Possible values are `screenshots`, `screenRecording`. Default is `screenRecording`. #### Test Target A target can be one of a 2 types: - **name**: **String** - The name of the target. - **target**: **[Testable Target Reference](#testable-target-reference)** - The information of the target. You can specify more detailed information than `name:`. As syntax sugar, you can also specify **[Testable Target Reference](#testable-target-reference)** without `target`. #### Other Parameters - [ ] **parallelizable**: **Bool** - Whether to run tests in parallel. Defaults to false - [ ] **randomExecutionOrder**: **Bool** - Whether to run tests in a random order. Defaults to false - [ ] **location**: **String** - GPX file or predefined value for simulating location. See [Simulate Location](#simulate-location) for location examples. - [ ] **skipped**: **Bool** - Whether to skip all of the test target tests. Defaults to false - [ ] **skippedTests**: **[String]** - List of tests in the test target to skip. Defaults to empty - [ ] **selectedTests**: **[String]** - List of tests in the test target to whitelist and select. Defaults to empty. This will override `skippedTests` if provided #### Testable Target Reference A Testable Target Reference can be one of 3 types: - `package: {local-swift-package-name}/{target-name}`: Name of local swift package and its target. - `local: {target-name}`: Name of local target. - `project: {project-reference-name}/{target-name}`: Name of local swift package and its target. ### Archive Action - [ ] **customArchiveName**: **String** - the custom name to give to the archive - [ ] **revealArchiveInOrganizer**: **Bool** - flag to determine whether the archive will be revealed in Xcode's Organizer after it's done building ### Simulate Location - [x] **allow**: **Bool** - enable location simulation - [ ] **defaultLocation**: **String** - set the default location, possible values: - `London, England` - `Johannesburg, South Africa` - `Moscow, Russia` - `Mumbai, India` - `Tokyo, Japan` - `Sydney, Australia` - `Hong Kong, China` - `Honolulu, HI, USA` - `San Francisco, CA, USA` - `Mexico City, Mexico` - `New York, NY, USA` - `Rio de Janeiro, Brazil` - `` (e.g. ./location.gpx) Setting the **defaultLocation** to a custom gpx file, you also need to add that file to `fileGroups` for Xcode be able to use it: ```yaml targets: MyTarget: fileGroups: - location.gpx ``` Note that the path the gpx file will be prefixed according to the `schemePathPrefix` option in order to support both `.xcodeproj` and `.xcworkspace` setups. See [Options](#options). ### Scheme Management - [ ] **shared**: **Bool** - indicates whether the scheme is shared - [ ] **orderHint**: **Int** - used by Xcode to sort the schemes - [ ] **isShown**: **Bool** - indicates whether the sheme is shown in the scheme list ### Environment Variable - [x] **variable**: **String** - variable's name. - [x] **value**: **String** - variable's value. - [ ] **isEnabled**: **Bool** - indicates whether the environment variable is enabled. This defaults to true. ```yaml schemes: Production: build: targets: MyTarget1: all MyTarget2: [run, archive] run: config: prod-debug commandLineArguments: "-MyEnabledArg": true "-MyDisabledArg": false environmentVariables: RUN_ENV_VAR: VALUE test: config: prod-debug commandLineArguments: "-MyEnabledArg": true "-MyDisabledArg": false gatherCoverageData: true coverageTargets: - MyTarget1 - ExternalTarget/OtherTarget1 - package: LocalPackage/TestTarget targets: - Tester1 - name: Tester2 parallelizable: true randomExecutionOrder: true skippedTests: [Test/testExample()] - package: APIClient/APIClientTests parallelizable: true randomExecutionOrder: true environmentVariables: - variable: TEST_ENV_VAR value: VALUE isEnabled: false profile: config: prod-release analyze: config: prod-debug archive: config: prod-release customArchiveName: MyTarget revealArchiveInOrganizer: false ``` ### Test Plan For now test plans are not generated by XcodeGen and must be created in Xcode and checked in, and then referenced by path. If the test targets are added, removed or renamed, the test plans may need to be updated in Xcode. - [x] **path**: **String** - path that provides the `xctestplan` location. - [ ] **defaultPlan**: **Bool** - a bool that defines if given plan is the default one. Defaults to false. If no default is set on any test plan, the first plan is set as the default. ```yaml schemes: TestTarget: test: testPlans: - path: app.xctestplan defaultPlan: true ``` ## Scheme Template This is a template that can be referenced from a normal scheme using the `templates` property. The properties of this template are the same as a [Scheme](#scheme). This functions identically in practice to [Target Template](#target-template). Any instances of `${scheme_name}` within each template will be replaced by the final scheme name which references the template. Any attributes defined within a scheme's `templateAttributes` will be used to replace any attribute references in the template using the syntax `${attribute_name}`. ```yaml schemes: MyModule: templates: - FeatureModuleScheme templateAttributes: testTargetName: MyModuleTests schemeTemplates: FeatureModuleScheme: templates: - TestScheme build: targets: ${scheme_name}: build TestScheme: test: gatherCoverageData: true targets: - name: ${testTargetName} parallelizable: true randomExecutionOrder: true ``` The result will be a scheme that builds `MyModule` when you request a build, and will test against `MyModuleTests` when you request to run tests. This is particularly useful when you work in a very modular application and each module has a similar structure. ## Swift Package Swift packages are defined at a project level, and then linked to individual targets via a [Dependency](#dependency). ### Remote Package - [x] **url**: **URL** - the url to the package - [x] **version**: **String** - the version of the package to use. It can take a few forms: - `majorVersion: 1.2.0` or `from: 1.2.0` - `minorVersion: 1.2.1` - `exactVersion: 1.2.1` or `version: 1.2.1` - `minVersion: 1.0.0, maxVersion: 1.2.9` - `branch: master` - `revision: xxxxxx` - [ ] **github** : **String**- this is an optional helper you can use for github repos. Instead of specifying the full url in `url` you can just specify the github org and repo ### Local Package - [x] **path**: **String** - the path to the package in local. The path must be directory with a `Package.swift`. - [ ] **group** : **String**- Optional path that specifies the location where the package will live in your xcode project. Use `""` to specify the project root. - [ ] **excludeFromProject** : **String**- Optional flag to exclude the package from the generated project (useful if the package is already added via xcworkspace and the project is not intended for standalone use), defaults to `false` ```yml packages: Yams: url: https://github.com/jpsim/Yams from: 2.0.0 Ink: github: JohnSundell/Ink from: 0.5.0 RxClient: path: ../RxClient AppFeature: path: ../Packages group: Domains/AppFeature excludeFromProject: false ``` ## Project Reference Project References are defined at a project level, and then you can use the project name to refer its target via a [Scheme](#scheme) - [x] **path**: **String** - The path to the `xcodeproj` file to reference. ```yml projectReferences: YamsProject: path: ./Carthage/Checkouts/Yams/Yams.xcodeproj schemes: TestTarget: build: targets: YamsProject/Yams: ["run"] ``` ================================================ FILE: Docs/Usage.md ================================================ - [Configuring build settings](#configuring-build-settings) - [Setting Presets](#setting-presets) - [Settings](#settings) - [Setting Groups](#setting-groups) - [xcconfig files](#xcconfig-files) - [Dependencies](#dependencies) - [CocoaPods](#cocoapods) - [Carthage](#carthage) - [Swift Package](#swift-package) - [SDK](#sdk) - [Framework](#framework) # Configuring build settings There are various ways of configuring build settings Xcode resolves a certain build setting for a configuration and target by looking up the different levels until it finds a value. This can be seen in Xcode when the `Levels` option is on in the `Build Settings` tab. The different levels of build settings are: - target - target xcconfig file - project - project xcconfig file - sdk defaults XcodeGen will apply settings to a target or project level by merging different methods - [Setting Presets](#setting-presets) - [Setting Groups](#setting-groups) - [Settings](#settings) `base` - [Settings](#settings) for a specific `config` The values from [xcconfig files](#xcconfig-files) will then sit a level above this. Note that as a convenience, any settings in an xcconfig file will also overwrite any settings from [Setting Presets](#setting-presets) >Note that when defining build settings you need to know the write name and value. In Xcode build settings are shown by default with a nicely formatted title and value. To be able to see what the actual build setting names and values are make sure you're in a `Build Settings` tab and go `Editor -> Show Setting Titles` and also `Editor -> Show Definitions`. This will then give you the actual names and values that XcodeGen expects. ### Setting Presets XcodeGen applies default settings to your project and targets similar to how Xcode creates them when you create a new project or target. Debug and Release settings will be applied to your project. Targets will also get specific settings depending on the platform and product type. >You can change or disable how these setting presets are applied via the `options.settingPresets` which you can find more about in [Options](ProjectSpec.md#options) ### Settings The `project` and each `target` have a `settings` object that you can define. This can be a simple map of build settings or can provide build settings per `config` via `configs` or `base`. See [Settings](ProjectSpec.md#settings) for more details. ```yaml settings: DEVELOPMENT_TEAM: T45H45J targets: App: settings: base: CODE_SIGN_ENTITLEMENTS: App/Entitlements.entitlements configs: Debug: DEBUG_MODE: YES Release: DEBUG_MODE: NO ``` ### Setting Groups Each `settings` can also reference one or more setting groups which let you reuse groups of build settings across targets or configurations. See [Setting Groups](ProjectSpec.md#setting-groups) for more details. Note that each setting group is also a full [Settings](ProjectSpec.md#settings) object, so you can reference other groups or define settings by config. ```yaml settingGroups: app: DEVELOPMENT_TEAM: T45H45J targets: App: settings: groups: [app] ``` ### xcconfig files The `project` and each `target` have a `configFiles` object that lets you reference `.xcconfig` files per configuration. >This is good guide to xcconfig files [https://pewpewthespells.com/blog/xcconfig_guide](https://pewpewthespells.com/blog/xcconfig_guide.html) ```yaml configFiles: Debug: debug.xcconfig Release: release.xcconfig targets: App: configFiles: Debug: App/debug.xcconfig Release: App/release.xcconfig ``` ### xcodebuild environment variables You can also always override any build settings on CI when building by passing specific build settings to xcodebuild like so: ```sh DEVELOPMENT_TEAM=XXXXXXXXX xcodebuild ... ``` # Dependencies Each target can declare one or more dependencies. See [Dependency](ProjectSpec.md#dependency) in the ProjectSpec for more info about all the properties ### CocoaPods Use your `Podfile` as normal. The pods themselves don't need to be referenced in the project spec. After you generate your project simply run `pod install` which will integrate with your project and create a workspace. ### Carthage XcodeGen makes integrating Carthage dependencies super easy! You simply reference them in each target that requires them and XcodeGen does the rest by automatically linking and embedding the carthage frameworks where necessary. ```yaml targets: App: dependencies: - target: Framework - carthage: Kingfisher Framework: dependencies: - carthage: Alamofire ``` Some Carthage dependencies actually vend multiple frameworks. For example `github "ReactiveCocoa/ReactiveCocoa" ~> 8.0` vends 2 frameworks `ReactiveCocoa` and `ReactiveMapKit`. By default these all have to be listed if you want to link and use them: ```yml targets: App: dependencies: - carthage: ReactiveCocoa - carthage: ReactiveMapKit ``` XcodeGen can look these up for you automatically! This can be enabled with a global `options.findCarthageFrameworks` or can be overridden for each Carthage dependency. Note that if this is enabled, the Carthage dependencies need to have already been built before XcodeGen is run. This is because XcodeGen loads `.version` files that Carthage writes in the `Carthage/Build` directory which lists the all the frameworks. The name you use must also be the name of the `.version` file Carthage writes to `Carthage/Build`. Be aware that in some cases this name can differ from the name of the repo in the Cartfile and even the framework name. If the `.version` file is not found or fails parsing, XcodeGen will fallback to the regular Framework lookup in the relevant Carthage directory. ```yml options: findCarthageFrameworks: true targets: App: dependencies: - carthage: ReactiveCocoa # will find ReactiveMapKit as well - carthage: OtherCarthageDependency findFrameworks: false # disables the global option ``` XcodeGen automatically creates the build phase that Carthage requires which lists all the files and runs `carthage copy-frameworks`. You can change the invocation of carthage to something different, for example if you are running it with [Mint](https://github.com/yonaskolb/mint). This is then prepended to ` copy frameworks` ```yaml options: carthageExecutablePath: mint run Carthage/Carthage ``` By default XcodeGen looks for carthage frameworks in `Carthage/Build`. You can change this with the `carthageBuildPath` option ```yaml options: carthageBuildPath: ../../Carthage/Build ``` ### Swift Package Swift Packages can be integrated by defining them at the project level and then referencing them in targets ```yaml packages: Yams: url: https://github.com/jpsim/Yams from: 2.0.0 SwiftPM: url: https://github.com/apple/swift-package-manager branch: swift-5.0-branch RxClient: path: ../RxClient targets: App: dependencies: # by default the package product that is linked to is the same as the package name - package: Yams - package: SwiftPM - package: RxClient - package: SwiftPM product: SPMUtility # specify a specific product ``` If you want to check in the `Package.resolved` file so that everyone is on the same versions, you need to check in `ProjectName.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved` > Note that Swift Packages don't work in projects with configurations other than `Debug` and `Release`. That limitation is tracked here bugs.swift.org/browse/SR-10927 Specified local packages get put into a `Packages` group in the root of the project by default. This can be changed with `options.localPackagesGroup`. ### SDK System frameworks and libs can be linked by using the `sdk` dependency type. You can either specify frameworks or libs by using a `.framework`, `.tbd` or `dylib` filename, respectively ```yaml targets: App: dependencies: - sdk: Contacts.framework - sdk: libc++.tbd - sdk: libz.dylib ``` ### Framework Individual frameworks can also be linked by specifying a path to them ```yamlå targets: App: dependencies: - framework: Vendor/MyFramework.framework ``` # Build Tool Plug-ins XCodeGen supports working with [Swift Package Plug-ins](https://github.com/apple/swift-package-manager/blob/main/Documentation/Plugins.md#using-a-package-plugin). To use plugins, you need to specify in your target which plugin you want to connect, and don't forget to connect the package to target. ```yaml packages: Prefire: url: https://github.com/BarredEwe/Prefire from: 1.3.0 targets: App: buildToolPlugins: - plugin: PrefirePlaybookPlugin package: Prefire ``` ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Yonas Kolb Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ TOOL_NAME = XcodeGen export EXECUTABLE_NAME = xcodegen VERSION = 2.45.3 PREFIX = /usr/local INSTALL_PATH = $(PREFIX)/bin/$(EXECUTABLE_NAME) SHARE_PATH = $(PREFIX)/share/$(EXECUTABLE_NAME) CURRENT_PATH = $(PWD) REPO = https://github.com/yonaskolb/$(TOOL_NAME) SWIFT_BUILD_FLAGS = --disable-sandbox -c release --arch arm64 --arch x86_64 BUILD_PATH = $(shell swift build $(SWIFT_BUILD_FLAGS) --show-bin-path) EXECUTABLE_PATH = $(BUILD_PATH)/$(EXECUTABLE_NAME) .PHONY: install build uninstall format_code release install: build mkdir -p $(PREFIX)/bin cp -f $(EXECUTABLE_PATH) $(INSTALL_PATH) mkdir -p $(SHARE_PATH) cp -R $(CURRENT_PATH)/SettingPresets $(SHARE_PATH)/SettingPresets build: swift build $(SWIFT_BUILD_FLAGS) uninstall: rm -f $(INSTALL_PATH) rm -rf $(SHARE_PATH) format_code: swiftformat . release: sed -i '' 's|\(let version = Version("\)\(.*\)\(")\)|\1$(VERSION)\3|' Sources/XcodeGen/main.swift sed -i '' 's|\(.package(url: "https://github.com/yonaskolb/XcodeGen.git", from: "\)\(.*\)\(")\)|\1$(VERSION)\3|' README.md git add . git commit -m "Update to $(VERSION)" #git tag $(VERSION) publish: archive echo "published $(VERSION)" archive: build ./scripts/archive.sh "$(EXECUTABLE_PATH)" swift package plugin --allow-writing-to-package-directory generate-artifact-bundle \ --package-version $(VERSION) \ --executable-name $(EXECUTABLE_NAME) \ --build-config release \ --include-resource-path LICENSE ================================================ FILE: Package.resolved ================================================ { "pins" : [ { "identity" : "aexml", "kind" : "remoteSourceControl", "location" : "https://github.com/tadija/AEXML.git", "state" : { "revision" : "db806756c989760b35108146381535aec231092b", "version" : "4.7.0" } }, { "identity" : "artifactbundlegen", "kind" : "remoteSourceControl", "location" : "https://github.com/freddi-kit/ArtifactBundleGen", "state" : { "revision" : "33f4a65acb296dcde04aeb828b6850fcf9dceb6c", "version" : "0.0.8" } }, { "identity" : "jsonutilities", "kind" : "remoteSourceControl", "location" : "https://github.com/yonaskolb/JSONUtilities.git", "state" : { "revision" : "128d2ffc22467f69569ef8ff971683e2393191a0", "version" : "4.2.0" } }, { "identity" : "pathkit", "kind" : "remoteSourceControl", "location" : "https://github.com/kylef/PathKit.git", "state" : { "revision" : "3bfd2737b700b9a36565a8c94f4ad2b050a5e574", "version" : "1.0.1" } }, { "identity" : "rainbow", "kind" : "remoteSourceControl", "location" : "https://github.com/onevcat/Rainbow.git", "state" : { "revision" : "e0dada9cd44e3fa7ec3b867e49a8ddbf543e3df3", "version" : "4.0.1" } }, { "identity" : "spectre", "kind" : "remoteSourceControl", "location" : "https://github.com/kylef/Spectre.git", "state" : { "revision" : "26cc5e9ae0947092c7139ef7ba612e34646086c7", "version" : "0.10.1" } }, { "identity" : "swiftcli", "kind" : "remoteSourceControl", "location" : "https://github.com/jakeheis/SwiftCLI.git", "state" : { "revision" : "2e949055d9797c1a6bddcda0e58dada16cc8e970", "version" : "6.0.3" } }, { "identity" : "version", "kind" : "remoteSourceControl", "location" : "https://github.com/mxcl/Version", "state" : { "revision" : "a94b48f36763c05629fc102837398505032dead9", "version" : "2.0.0" } }, { "identity" : "xcodeproj", "kind" : "remoteSourceControl", "location" : "https://github.com/tuist/XcodeProj.git", "state" : { "revision" : "01bb77000bc8c23a09ea2058f4954612f03cb705", "version" : "9.10.1" } }, { "identity" : "yams", "kind" : "remoteSourceControl", "location" : "https://github.com/jpsim/Yams.git", "state" : { "revision" : "01835dc202670b5bb90d07f3eae41867e9ed29f6", "version" : "5.0.1" } } ], "version" : 2 } ================================================ FILE: Package.resources ================================================ SettingPresets ================================================ FILE: Package.swift ================================================ // swift-tools-version:5.9 import PackageDescription let package = Package( name: "XcodeGen", platforms: [.macOS(.v11)], products: [ .executable(name: "xcodegen", targets: ["XcodeGen"]), .library(name: "XcodeGenKit", targets: ["XcodeGenKit"]), .library(name: "ProjectSpec", targets: ["ProjectSpec"]), ], dependencies: [ .package(url: "https://github.com/kylef/PathKit.git", from: "1.0.1"), .package(url: "https://github.com/jpsim/Yams.git", from: "5.0.0"), .package(url: "https://github.com/yonaskolb/JSONUtilities.git", from: "4.2.0"), .package(url: "https://github.com/kylef/Spectre.git", from: "0.9.2"), .package(url: "https://github.com/onevcat/Rainbow.git", from: "4.0.0"), .package(url: "https://github.com/tuist/XcodeProj.git", exact: "9.10.1"), .package(url: "https://github.com/jakeheis/SwiftCLI.git", from: "6.0.3"), .package(url: "https://github.com/mxcl/Version", from: "2.0.0"), .package(url: "https://github.com/freddi-kit/ArtifactBundleGen", exact: "0.0.8") ], targets: [ .executableTarget(name: "XcodeGen", dependencies: [ "XcodeGenCLI", .product(name: "Version", package: "Version"), ]), .target(name: "XcodeGenCLI", dependencies: [ "XcodeGenKit", "ProjectSpec", .product(name: "SwiftCLI", package: "SwiftCLI"), .product(name: "Rainbow", package: "Rainbow"), .product(name: "PathKit", package: "PathKit"), .product(name: "Version", package: "Version"), ]), .target(name: "XcodeGenKit", dependencies: [ "ProjectSpec", .product(name: "JSONUtilities", package: "JSONUtilities"), .product(name: "XcodeProj", package: "XcodeProj"), .product(name: "PathKit", package: "PathKit"), "XcodeGenCore", ], resources: [ .copy("SettingPresets") ]), .target(name: "ProjectSpec", dependencies: [ .product(name: "JSONUtilities", package: "JSONUtilities"), .product(name: "XcodeProj", package: "XcodeProj"), .product(name: "Yams", package: "yams"), "XcodeGenCore", .product(name: "Version", package: "Version"), ]), .target(name: "XcodeGenCore", dependencies: [ .product(name: "PathKit", package: "PathKit"), .product(name: "Yams", package: "yams"), ]), .target(name: "TestSupport", dependencies: [ .product(name: "XcodeProj", package: "XcodeProj"), .product(name: "Spectre", package: "Spectre"), .product(name: "PathKit", package: "PathKit"), ]), .testTarget(name: "XcodeGenKitTests", dependencies: [ "XcodeGenKit", .product(name: "Spectre", package: "Spectre"), .product(name: "PathKit", package: "PathKit"), "TestSupport", ]), .testTarget(name: "FixtureTests", dependencies: [ "XcodeGenKit", .product(name: "Spectre", package: "Spectre"), .product(name: "PathKit", package: "PathKit"), "TestSupport", ]), .testTarget(name: "XcodeGenCoreTests", dependencies: [ "XcodeGenCore", .product(name: "Spectre", package: "Spectre"), .product(name: "PathKit", package: "PathKit"), "TestSupport", ]), .testTarget(name: "ProjectSpecTests", dependencies: [ "ProjectSpec", .product(name: "Spectre", package: "Spectre"), .product(name: "PathKit", package: "PathKit"), "TestSupport", ]), .testTarget(name: "PerformanceTests", dependencies: [ "XcodeGenKit", .product(name: "Spectre", package: "Spectre"), .product(name: "PathKit", package: "PathKit"), "TestSupport", ]), ] ) ================================================ FILE: README.md ================================================

XcodeGen

Swift Package Manager Platforms Swift Versions

# XcodeGen XcodeGen is a command line tool written in Swift that generates your Xcode project using your folder structure and a project spec. The project spec is a YAML or JSON file that defines your targets, configurations, schemes, custom build settings and many other options. All your source directories are automatically parsed and referenced appropriately while preserving your folder structure. Sensible defaults are used in many places, so you only need to customize what is needed. Very complex projects can also be defined using more advanced features. - ✅ Generate projects on demand and remove your `.xcodeproj` from git, which means **no more merge conflicts**! - ✅ Groups and files in Xcode are always **synced** to your directories on disk - ✅ Easy **configuration** of projects which is human readable and git friendly - ✅ Easily copy and paste **files and directories** without having to edit anything in Xcode - ✅ Share build settings across multiple targets with **build setting groups** - ✅ Automatically generate Schemes for **different environments** like test and production - ✅ Easily **create new projects** with complicated setups on demand without messing around with Xcode - ✅ Generate from anywhere including on **CI** - ✅ Distribute your spec amongst multiple files for easy **sharing** and overriding - ✅ Easily create **multi-platform** frameworks - ✅ Integrate **Carthage** frameworks without any work Given an example project spec: ```yaml name: MyProject include: - base_spec.yml options: bundleIdPrefix: com.myapp packages: Yams: url: https://github.com/jpsim/Yams from: 2.0.0 targets: MyApp: type: application platform: iOS deploymentTarget: "10.0" sources: [MyApp] settings: configs: debug: CUSTOM_BUILD_SETTING: my_debug_value release: CUSTOM_BUILD_SETTING: my_release_value dependencies: - target: MyFramework - carthage: Alamofire - framework: Vendor/MyFramework.framework - sdk: Contacts.framework - sdk: libc++.tbd - package: Yams MyFramework: type: framework platform: iOS sources: [MyFramework] ``` A project would be created with 2 connected targets, with all the required configurations and build settings. See the [Project Spec](Docs/ProjectSpec.md) documentation for all the options you can specify, and [Usage](Docs/Usage.md) for more general documentation. ## Installing Make sure the latest stable (non-beta) version of Xcode is installed first. ### [Mint](https://github.com/yonaskolb/mint) ```sh mint install yonaskolb/xcodegen ``` ### Make ```shell git clone https://github.com/yonaskolb/XcodeGen.git cd XcodeGen make install ``` ### Homebrew ```shell brew install xcodegen ``` ### Swift Package Manager **Use as CLI** ```shell git clone https://github.com/yonaskolb/XcodeGen.git cd XcodeGen swift run xcodegen ``` **Use as dependency** Add the following to your Package.swift file's dependencies: ```swift .package(url: "https://github.com/yonaskolb/XcodeGen.git", from: "2.45.3"), ``` And then import wherever needed: `import XcodeGenKit` ## Usage Simply run: ```shell xcodegen generate ``` This will look for a project spec in the current directory called `project.yml` and generate an Xcode project with the name defined in the spec. Options: - **--spec**: An optional path to a `.yml` or `.json` project spec. Defaults to `project.yml`. (It is also possible to link to multiple spec files by comma separating them. Note that all other flags will be the same.) - **--project**: An optional path to a directory where the project will be generated. By default this is the directory the spec lives in. - **--quiet**: Suppress informational and success messages. - **--use-cache**: Used to prevent unnecessarily generating the project. If this is set, then a cache file will be written to when a project is generated. If `xcodegen` is later run but the spec and all the files it contains are the same, the project won't be generated. - **--cache-path**: A custom path to use for your cache file. This defaults to `~/.xcodegen/cache/{PROJECT_SPEC_PATH_HASH}` There are other commands as well such as `xcodegen dump` which lets one output the resolved spec in many different formats, or write it to a file. Use `xcodegen help` to see more detailed usage information. ## Editing ```shell git clone https://github.com/yonaskolb/XcodeGen.git cd XcodeGen swift package generate-xcodeproj ``` This uses Swift Package Manager to create an `xcodeproj` file that you can open, edit and run in Xcode, which makes editing any code easier. If you want to pass any required arguments when running in Xcode, you can edit the scheme to include launch arguments. ## Documentation - See [Project Spec](Docs/ProjectSpec.md) documentation for all the various properties and options that can be set - See [Usage](Docs/Usage.md) for more specific usage and use case documentation - See [FAQ](Docs/FAQ.md) for a list of some frequently asked questions - See [Examples](Docs/Examples.md) for some real world XcodeGen project specs out in the wild ## Alternatives If XcodeGen doesn't meet your needs try these great alternatives: - [Tuist](https://github.com/tuist/tuist) - [Xcake](https://github.com/igor-makarov/xcake) - [struct](https://github.com/workshop/struct) ## Attributions This tool is powered by: - [XcodeProj](https://github.com/tuist/XcodeProj) - [JSONUtilities](https://github.com/yonaskolb/JSONUtilities) - [Spectre](https://github.com/kylef/Spectre) - [PathKit](https://github.com/kylef/PathKit) - [Yams](https://github.com/jpsim/Yams) - [SwiftCLI](https://github.com/jakeheis/SwiftCLI) Inspiration for this tool came from: - [struct](https://github.com/workshop/struct) - [Xcake](https://github.com/igor-makarov/xcake) - [CocoaPods Xcodeproj](https://github.com/CocoaPods/Xcodeproj) ## Contributions Pull requests and issues are always welcome. Please open any issues and PRs for bugs, features, or documentation. [![](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/images/0)](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/links/0)[![](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/images/1)](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/links/1)[![](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/images/2)](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/links/2)[![](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/images/3)](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/links/3)[![](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/images/4)](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/links/4)[![](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/images/5)](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/links/5)[![](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/images/6)](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/links/6)[![](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/images/7)](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/links/7) ## License XcodeGen is licensed under the MIT license. See [LICENSE](LICENSE) for more info. ================================================ FILE: RELEASE.md ================================================ # The release process for XcodeGen 1. Make sure `CHANGELOG.md` is up to date: - All merged PRs since the last release have been added with the PR link and author (check `git log ..HEAD`) - The new version number is added at the top after `Master` 1. Update the version at the top of `Makefile` 1. Run `make release` 1. Run `make archive` 1. Push commit and tag to github 1. Create release from tag on GitHub using the version number and relevant changelog contents, attaching `xcodegen.zip` and `xcodegen.artifactbundle.zip` ================================================ FILE: SettingPresets/Configs/debug.yml ================================================ --- # Settings take from the following file and sorted # /Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/Project Templates/Base/Base_ProjectSettings.xctemplate/TemplateInfo.plist DEBUG_INFORMATION_FORMAT: dwarf ENABLE_TESTABILITY: YES GCC_DYNAMIC_NO_PIC: NO GCC_OPTIMIZATION_LEVEL: '0' GCC_PREPROCESSOR_DEFINITIONS: ["$(inherited)", "DEBUG=1"] MTL_ENABLE_DEBUG_INFO: INCLUDE_SOURCE ONLY_ACTIVE_ARCH: YES # Swift Settings SWIFT_ACTIVE_COMPILATION_CONDITIONS: DEBUG SWIFT_OPTIMIZATION_LEVEL: -Onone ================================================ FILE: SettingPresets/Configs/release.yml ================================================ --- # Settings take from the following file and sorted # /Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/Project Templates/Base/Base_ProjectSettings.xctemplate/TemplateInfo.plist DEBUG_INFORMATION_FORMAT: dwarf-with-dsym ENABLE_NS_ASSERTIONS: NO MTL_ENABLE_DEBUG_INFO: NO # Swift Settings SWIFT_COMPILATION_MODE: wholemodule SWIFT_OPTIMIZATION_LEVEL: -O ================================================ FILE: SettingPresets/Platforms/iOS.yml ================================================ LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks"] SDKROOT: iphoneos TARGETED_DEVICE_FAMILY: '1,2' ================================================ FILE: SettingPresets/Platforms/macOS.yml ================================================ LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/../Frameworks"] SDKROOT: macosx COMBINE_HIDPI_IMAGES: 'YES' ================================================ FILE: SettingPresets/Platforms/tvOS.yml ================================================ LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks"] SDKROOT: appletvos TARGETED_DEVICE_FAMILY: 3 ================================================ FILE: SettingPresets/Platforms/visionOS.yml ================================================ LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks"] SDKROOT: xros TARGETED_DEVICE_FAMILY: 7 ================================================ FILE: SettingPresets/Platforms/watchOS.yml ================================================ SDKROOT: watchos SKIP_INSTALL: 'YES' TARGETED_DEVICE_FAMILY: 4 ================================================ FILE: SettingPresets/Product_Platform/app-extension_macOS.yml ================================================ LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/../Frameworks", "@executable_path/../../../../Frameworks"] ================================================ FILE: SettingPresets/Product_Platform/application_iOS.yml ================================================ CODE_SIGN_IDENTITY: iPhone Developer ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon ================================================ FILE: SettingPresets/Product_Platform/application_macOS.yml ================================================ ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon ================================================ FILE: SettingPresets/Product_Platform/application_tvOS.yml ================================================ ASSETCATALOG_COMPILER_APPICON_NAME: App Icon & Top Shelf Image ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME: LaunchImage ================================================ FILE: SettingPresets/Product_Platform/application_visionOS.yml ================================================ ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon ================================================ FILE: SettingPresets/Product_Platform/application_watchOS.yml ================================================ ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon ================================================ FILE: SettingPresets/Product_Platform/bundle.unit-test_macOS.yml ================================================ LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks"] ================================================ FILE: SettingPresets/Products/app-extension.intents-service.yml ================================================ LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", "@executable_path/../../../../Frameworks"] ================================================ FILE: SettingPresets/Products/app-extension.messages.yml ================================================ ASSETCATALOG_COMPILER_APPICON_NAME: iMessage App Icon LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks"] ================================================ FILE: SettingPresets/Products/app-extension.yml ================================================ LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks"] ================================================ FILE: SettingPresets/Products/bundle.ui-testing.yml ================================================ BUNDLE_LOADER: $(TEST_HOST) LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks"] ================================================ FILE: SettingPresets/Products/bundle.unit-test.yml ================================================ BUNDLE_LOADER: $(TEST_HOST) LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks"] ================================================ FILE: SettingPresets/Products/framework.static.yml ================================================ CURRENT_PROJECT_VERSION: 1 DEFINES_MODULE: 'YES' CODE_SIGN_IDENTITY: "" DYLIB_COMPATIBILITY_VERSION: 1 DYLIB_CURRENT_VERSION: 1 VERSIONING_SYSTEM: "apple-generic" INSTALL_PATH: "$(LOCAL_LIBRARY_DIR)/Frameworks" DYLIB_INSTALL_NAME_BASE: "@rpath" SKIP_INSTALL: 'YES' ================================================ FILE: SettingPresets/Products/framework.yml ================================================ CURRENT_PROJECT_VERSION: 1 DEFINES_MODULE: 'YES' CODE_SIGN_IDENTITY: "" DYLIB_COMPATIBILITY_VERSION: 1 DYLIB_CURRENT_VERSION: 1 VERSIONING_SYSTEM: "apple-generic" INSTALL_PATH: "$(LOCAL_LIBRARY_DIR)/Frameworks" DYLIB_INSTALL_NAME_BASE: "@rpath" SKIP_INSTALL: 'YES' ================================================ FILE: SettingPresets/Products/library.static.yml ================================================ SKIP_INSTALL: 'YES' ================================================ FILE: SettingPresets/Products/tv-app-extension.yml ================================================ SKIP_INSTALL: 'YES' LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks"] ================================================ FILE: SettingPresets/Products/watchkit2-extension.yml ================================================ LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks"] ASSETCATALOG_COMPILER_COMPLICATION_NAME: Complication ================================================ FILE: SettingPresets/SupportedDestinations/iOS.yml ================================================ SUPPORTED_PLATFORMS: iphoneos iphonesimulator TARGETED_DEVICE_FAMILY: '1,2' SUPPORTS_MACCATALYST: NO SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD: YES SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD: YES ================================================ FILE: SettingPresets/SupportedDestinations/macCatalyst.yml ================================================ SUPPORTS_MACCATALYST: YES SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD: NO ================================================ FILE: SettingPresets/SupportedDestinations/macOS.yml ================================================ SUPPORTED_PLATFORMS: macosx SUPPORTS_MACCATALYST: NO SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD: NO ================================================ FILE: SettingPresets/SupportedDestinations/tvOS.yml ================================================ SUPPORTED_PLATFORMS: appletvos appletvsimulator TARGETED_DEVICE_FAMILY: '3' ================================================ FILE: SettingPresets/SupportedDestinations/visionOS.yml ================================================ SUPPORTED_PLATFORMS: xros xrsimulator TARGETED_DEVICE_FAMILY: '7' SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD: NO ================================================ FILE: SettingPresets/SupportedDestinations/watchOS.yml ================================================ SUPPORTED_PLATFORMS: watchos watchsimulator TARGETED_DEVICE_FAMILY: '4' ================================================ FILE: SettingPresets/base.yml ================================================ --- # Settings take from the following file and sorted # /Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/Project Templates/Base/Base_ProjectSettings.xctemplate/TemplateInfo.plist ALWAYS_SEARCH_USER_PATHS: NO CLANG_ANALYZER_NONNULL: YES CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION: YES_AGGRESSIVE CLANG_CXX_LANGUAGE_STANDARD: gnu++14 CLANG_CXX_LIBRARY: libc++ CLANG_ENABLE_MODULES: YES CLANG_ENABLE_OBJC_ARC: YES CLANG_ENABLE_OBJC_WEAK: YES CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING: YES CLANG_WARN_BOOL_CONVERSION: YES CLANG_WARN_COMMA: YES CLANG_WARN_CONSTANT_CONVERSION: YES CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS: YES CLANG_WARN_DIRECT_OBJC_ISA_USAGE: YES_ERROR CLANG_WARN_DOCUMENTATION_COMMENTS: YES CLANG_WARN_EMPTY_BODY: YES CLANG_WARN_ENUM_CONVERSION: YES CLANG_WARN_INFINITE_RECURSION: YES CLANG_WARN_INT_CONVERSION: YES CLANG_WARN_NON_LITERAL_NULL_CONVERSION: YES CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF: YES CLANG_WARN_OBJC_LITERAL_CONVERSION: YES CLANG_WARN_OBJC_ROOT_CLASS: YES_ERROR CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER: YES CLANG_WARN_RANGE_LOOP_ANALYSIS: YES CLANG_WARN_STRICT_PROTOTYPES: YES CLANG_WARN_SUSPICIOUS_MOVE: YES CLANG_WARN_UNGUARDED_AVAILABILITY: YES_AGGRESSIVE CLANG_WARN_UNREACHABLE_CODE: YES CLANG_WARN__DUPLICATE_METHOD_MATCH: YES COPY_PHASE_STRIP: NO ENABLE_STRICT_OBJC_MSGSEND: YES GCC_C_LANGUAGE_STANDARD: gnu11 GCC_NO_COMMON_BLOCKS: YES GCC_WARN_64_TO_32_BIT_CONVERSION: YES GCC_WARN_ABOUT_RETURN_TYPE: YES_ERROR GCC_WARN_UNDECLARED_SELECTOR: YES GCC_WARN_UNINITIALIZED_AUTOS: YES_AGGRESSIVE GCC_WARN_UNUSED_FUNCTION: YES GCC_WARN_UNUSED_VARIABLE: YES MTL_FAST_MATH: YES # Target Settings PRODUCT_NAME: $(TARGET_NAME) # Swift Settings SWIFT_VERSION: '5.0' ================================================ FILE: Sources/ProjectSpec/AggregateTarget.swift ================================================ import Foundation import JSONUtilities import XcodeProj public struct AggregateTarget: ProjectTarget { public var name: String public var type: PBXProductType = .none public var targets: [String] public var settings: Settings public var buildScripts: [BuildScript] public var buildToolPlugins: [BuildToolPlugin] public var configFiles: [String: String] public var scheme: TargetScheme? public var attributes: [String: Any] public init( name: String, targets: [String], settings: Settings = .empty, configFiles: [String: String] = [:], buildScripts: [BuildScript] = [], buildToolPlugins: [BuildToolPlugin] = [], scheme: TargetScheme? = nil, attributes: [String: Any] = [:] ) { self.name = name self.targets = targets self.settings = settings self.configFiles = configFiles self.buildScripts = buildScripts self.buildToolPlugins = buildToolPlugins self.scheme = scheme self.attributes = attributes } } extension AggregateTarget: CustomStringConvertible { public var description: String { "\(name)\(targets.isEmpty ? "" : ": \(targets.joined(separator: ", "))")" } } extension AggregateTarget: Equatable { public static func == (lhs: AggregateTarget, rhs: AggregateTarget) -> Bool { lhs.name == rhs.name && lhs.targets == rhs.targets && lhs.settings == rhs.settings && lhs.configFiles == rhs.configFiles && lhs.buildScripts == rhs.buildScripts && lhs.buildToolPlugins == rhs.buildToolPlugins && lhs.scheme == rhs.scheme && NSDictionary(dictionary: lhs.attributes).isEqual(to: rhs.attributes) } } extension AggregateTarget: NamedJSONDictionaryConvertible { public init(name: String, jsonDictionary: JSONDictionary) throws { self.name = jsonDictionary.json(atKeyPath: "name") ?? name targets = jsonDictionary.json(atKeyPath: "targets") ?? [] settings = try BuildSettingsParser(jsonDictionary: jsonDictionary).parse() configFiles = jsonDictionary.json(atKeyPath: "configFiles") ?? [:] buildScripts = jsonDictionary.json(atKeyPath: "buildScripts") ?? [] buildToolPlugins = jsonDictionary.json(atKeyPath: "buildToolPlugins") ?? [] scheme = jsonDictionary.json(atKeyPath: "scheme") attributes = jsonDictionary.json(atKeyPath: "attributes") ?? [:] } } extension AggregateTarget: JSONEncodable { public func toJSONValue() -> Any { [ "settings": settings.toJSONValue(), "targets": targets, "configFiles": configFiles, "attributes": attributes, "buildScripts": buildScripts.map { $0.toJSONValue() }, "buildToolPlugins": buildToolPlugins.map { $0.toJSONValue() }, "scheme": scheme?.toJSONValue(), ] as [String: Any?] } } extension AggregateTarget: PathContainer { static var pathProperties: [PathProperty] { [ .dictionary([ .string("configFiles"), .object("buildScripts", BuildScript.pathProperties), ]), ] } } ================================================ FILE: Sources/ProjectSpec/Array+Extension.swift ================================================ extension Array where Element == [String: Any?] { func removingEmptyArraysDictionariesAndNils() -> [[String: Any]] { var new: [[String: Any]] = [] forEach { element in new.append(element.removingEmptyArraysDictionariesAndNils()) } return new } } ================================================ FILE: Sources/ProjectSpec/Breakpoint.swift ================================================ import Foundation import XcodeProj import JSONUtilities public typealias BreakpointActionExtensionID = XCBreakpointList.BreakpointProxy.BreakpointContent.BreakpointActionProxy.ActionExtensionID public typealias BreakpointExtensionID = XCBreakpointList.BreakpointProxy.BreakpointExtensionID public struct Breakpoint: Equatable { public enum BreakpointType: Equatable { public struct Exception: Equatable { public enum Scope: String, Equatable { case all = "0" case objectiveC = "1" case cpp = "2" } public enum StopOnStyle: String, Equatable { case `throw` = "0" case `catch` = "1" } public var scope: Scope public var stopOnStyle: StopOnStyle public init(scope: Breakpoint.BreakpointType.Exception.Scope = .objectiveC, stopOnStyle: Breakpoint.BreakpointType.Exception.StopOnStyle = .throw) { self.scope = scope self.stopOnStyle = stopOnStyle } } case file(path: String, line: Int, column: Int?) case exception(Exception) case swiftError case openGLError case symbolic(symbol: String?, module: String?) case ideConstraintError case ideTestFailure case runtimeIssue } public enum Action: Equatable { public struct Log: Equatable { public enum ConveyanceType: String, Equatable { case console = "0" case speak = "1" } public var message: String? public var conveyanceType: ConveyanceType public init(message: String? = nil, conveyanceType: Breakpoint.Action.Log.ConveyanceType = .console) { self.message = message self.conveyanceType = conveyanceType } } public enum Sound: String, Equatable { case basso = "Basso" case blow = "Blow" case bottle = "Bottle" case frog = "Frog" case funk = "Funk" case glass = "Glass" case hero = "Hero" case morse = "Morse" case ping = "Ping" case pop = "Pop" case purr = "Purr" case sosumi = "Sosumi" case submarine = "Submarine" case tink = "Tink" } case debuggerCommand(String?) case log(Log) case shellCommand(path: String?, arguments: String?, waitUntilDone: Bool = false) case graphicsTrace case appleScript(String?) case sound(Sound) } public var type: BreakpointType public var enabled: Bool public var ignoreCount: Int public var continueAfterRunningActions: Bool public var condition: String? public var actions: [Breakpoint.Action] public init(type: BreakpointType, enabled: Bool = true, ignoreCount: Int = 0, continueAfterRunningActions: Bool = false, filePath: String? = nil, line: Int? = nil, condition: String? = nil, actions: [Breakpoint.Action] = []) { self.type = type self.enabled = enabled self.ignoreCount = ignoreCount self.continueAfterRunningActions = continueAfterRunningActions self.condition = condition self.actions = actions } } extension Breakpoint.BreakpointType.Exception.Scope { public init(string: String) throws { let string = string.lowercased() switch string { case "all": self = .all case "objective-c": self = .objectiveC case "c++": self = .cpp default: throw SpecParsingError.unknownBreakpointScope(string) } } } extension Breakpoint.BreakpointType.Exception.StopOnStyle { public init(string: String) throws { let string = string.lowercased() switch string { case "throw": self = .throw case "catch": self = .catch default: throw SpecParsingError.unknownBreakpointStopOnStyle(string) } } } extension Breakpoint.Action.Log.ConveyanceType { init(string: String) throws { let string = string.lowercased() switch string { case "console": self = .console case "speak": self = .speak default: throw SpecParsingError.unknownBreakpointActionConveyanceType(string) } } } extension Breakpoint.Action.Sound { init(name: String) throws { guard let sound = Self.init(rawValue: name) else { throw SpecParsingError.unknownBreakpointActionSoundName(name) } self = sound } } extension Breakpoint.Action: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { let idString: String = try jsonDictionary.json(atKeyPath: "type") let id = try BreakpointActionExtensionID(string: idString) switch id { case .debuggerCommand: let command: String? = jsonDictionary.json(atKeyPath: "command") self = .debuggerCommand(command) case .log: let message: String? = jsonDictionary.json(atKeyPath: "message") let conveyanceType: Log.ConveyanceType if jsonDictionary["conveyanceType"] != nil { let conveyanceTypeString: String = try jsonDictionary.json(atKeyPath: "conveyanceType") conveyanceType = try .init(string: conveyanceTypeString) } else { conveyanceType = .console } self = .log(.init(message: message, conveyanceType: conveyanceType)) case .shellCommand: let path: String? = jsonDictionary.json(atKeyPath: "path") let arguments: String? = jsonDictionary.json(atKeyPath: "arguments") let waitUntilDone = jsonDictionary.json(atKeyPath: "waitUntilDone") ?? false self = .shellCommand(path: path, arguments: arguments, waitUntilDone: waitUntilDone) case .graphicsTrace: self = .graphicsTrace case .appleScript: let script: String? = jsonDictionary.json(atKeyPath: "script") self = .appleScript(script) case .sound: let sound: Sound if jsonDictionary["sound"] != nil { let name: String = try jsonDictionary.json(atKeyPath: "sound") sound = try .init(name: name) } else { sound = .basso } self = .sound(sound) case .openGLError: throw SpecParsingError.unknownBreakpointActionType(idString) } } } extension Breakpoint: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { let idString: String = try jsonDictionary.json(atKeyPath: "type") let id = try BreakpointExtensionID(string: idString) switch id { case .file: let path: String = try jsonDictionary.json(atKeyPath: "path") let line: Int = try jsonDictionary.json(atKeyPath: "line") let column: Int? = jsonDictionary.json(atKeyPath: "column") type = .file(path: path, line: line, column: column) case .exception: let scope: BreakpointType.Exception.Scope if jsonDictionary["scope"] != nil { let scopeString: String = try jsonDictionary.json(atKeyPath: "scope") scope = try .init(string: scopeString) } else { scope = .objectiveC } let stopOnStyle: BreakpointType.Exception.StopOnStyle if jsonDictionary["stopOnStyle"] != nil { let stopOnStyleString: String = try jsonDictionary.json(atKeyPath: "stopOnStyle") stopOnStyle = try .init(string: stopOnStyleString) } else { stopOnStyle = .throw } type = .exception(.init(scope: scope, stopOnStyle: stopOnStyle)) case .swiftError: type = .swiftError case .openGLError: type = .openGLError case .symbolic: let symbol: String? = jsonDictionary.json(atKeyPath: "symbol") let module: String? = jsonDictionary.json(atKeyPath: "module") type = .symbolic(symbol: symbol, module: module) case .ideConstraintError: type = .ideConstraintError case .ideTestFailure: type = .ideTestFailure case .runtimeIssue: type = .runtimeIssue } enabled = jsonDictionary.json(atKeyPath: "enabled") ?? true ignoreCount = jsonDictionary.json(atKeyPath: "ignoreCount") ?? 0 continueAfterRunningActions = jsonDictionary.json(atKeyPath: "continueAfterRunningActions") ?? false condition = jsonDictionary.json(atKeyPath: "condition") if jsonDictionary["actions"] != nil { actions = try jsonDictionary.json(atKeyPath: "actions", invalidItemBehaviour: .fail) } else { actions = [] } } } ================================================ FILE: Sources/ProjectSpec/BuildPhaseSpec.swift ================================================ // // File.swift // // // Created by Yonas Kolb on 1/5/20. // import Foundation import XcodeProj import JSONUtilities public enum BuildPhaseSpec: Equatable { case sources case headers case resources case copyFiles(CopyFilesSettings) case none // Not currently exposed as selectable options, but used internally case frameworks case runScript case carbonResources public struct CopyFilesSettings: Equatable, Hashable { public static let xpcServices = CopyFilesSettings( destination: .productsDirectory, subpath: "$(CONTENTS_FOLDER_PATH)/XPCServices", phaseOrder: .postCompile ) public static let plugins = CopyFilesSettings( destination: .plugins, subpath: "$(CONTENTS_FOLDER_PATH)/PlugIns", phaseOrder: .postCompile ) public enum Destination: String { case absolutePath case productsDirectory case wrapper case executables case resources case javaResources case frameworks case sharedFrameworks case sharedSupport case plugins public var destination: PBXCopyFilesBuildPhase.SubFolder? { switch self { case .absolutePath: return .absolutePath case .productsDirectory: return .productsDirectory case .wrapper: return .wrapper case .executables: return .executables case .resources: return .resources case .javaResources: return .javaResources case .frameworks: return .frameworks case .sharedFrameworks: return .sharedFrameworks case .sharedSupport: return .sharedSupport case .plugins: return .plugins } } } public enum PhaseOrder: String { /// Run before the Compile Sources phase case preCompile /// Run after the Compile Sources and post-compile Run Script phases case postCompile } public var destination: Destination public var subpath: String public var phaseOrder: PhaseOrder public init( destination: Destination, subpath: String, phaseOrder: PhaseOrder ) { self.destination = destination self.subpath = subpath self.phaseOrder = phaseOrder } } public var buildPhase: BuildPhase? { switch self { case .sources: return .sources case .headers: return .headers case .resources: return .resources case .copyFiles: return .copyFiles case .frameworks: return .frameworks case .runScript: return .runScript case .carbonResources: return .carbonResources case .none: return nil } } } extension BuildPhaseSpec { public init(string: String) throws { switch string { case "sources": self = .sources case "headers": self = .headers case "resources": self = .resources case "copyFiles": throw SpecParsingError.invalidSourceBuildPhase("copyFiles must specify a \"destination\" and optional \"subpath\"") case "none": self = .none default: throw SpecParsingError.invalidSourceBuildPhase(string.quoted) } } } extension BuildPhaseSpec: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { self = .copyFiles(try jsonDictionary.json(atKeyPath: "copyFiles")) } } extension BuildPhaseSpec: JSONEncodable { public func toJSONValue() -> Any { switch self { case .sources: return "sources" case .headers: return "headers" case .resources: return "resources" case .copyFiles(let files): return ["copyFiles": files.toJSONValue()] case .none: return "none" case .frameworks: fatalError("invalid build phase") case .runScript: fatalError("invalid build phase") case .carbonResources: fatalError("invalid build phase") } } } extension BuildPhaseSpec.CopyFilesSettings: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { destination = try jsonDictionary.json(atKeyPath: "destination") subpath = jsonDictionary.json(atKeyPath: "subpath") ?? "" phaseOrder = .postCompile } } extension BuildPhaseSpec.CopyFilesSettings: JSONEncodable { public func toJSONValue() -> Any { [ "destination": destination.rawValue, "subpath": subpath, ] } } ================================================ FILE: Sources/ProjectSpec/BuildRule.swift ================================================ import Foundation import JSONUtilities public struct BuildRule: Equatable { public static let scriptCompilerSpec = "com.apple.compilers.proxy.script" public static let filePatternFileType = "pattern.proxy" public static let runOncePerArchitectureDefault = true public enum FileType: Equatable { case type(String) case pattern(String) public var fileType: String { switch self { case let .type(fileType): return fileType case .pattern: return BuildRule.filePatternFileType } } public var pattern: String? { switch self { case .type: return nil case let .pattern(pattern): return pattern } } } public enum Action: Equatable { case compilerSpec(String) case script(String) public var compilerSpec: String { switch self { case let .compilerSpec(compilerSpec): return compilerSpec case .script: return BuildRule.scriptCompilerSpec } } public var script: String? { switch self { case .compilerSpec: return nil case let .script(script): return script } } } public var fileType: FileType public var action: Action public var outputFiles: [String] public var outputFilesCompilerFlags: [String] public var name: String? public var runOncePerArchitecture: Bool public init( fileType: FileType, action: Action, name: String? = nil, outputFiles: [String] = [], outputFilesCompilerFlags: [String] = [], runOncePerArchitecture: Bool = runOncePerArchitectureDefault ) { self.fileType = fileType self.action = action self.name = name self.outputFiles = outputFiles self.outputFilesCompilerFlags = outputFilesCompilerFlags self.runOncePerArchitecture = runOncePerArchitecture } } extension BuildRule: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { if let fileType: String = jsonDictionary.json(atKeyPath: "fileType") { self.fileType = .type(fileType) } else { fileType = .pattern(try jsonDictionary.json(atKeyPath: "filePattern")) } if let compilerSpec: String = jsonDictionary.json(atKeyPath: "compilerSpec") { action = .compilerSpec(compilerSpec) } else { action = .script(try jsonDictionary.json(atKeyPath: "script")) } outputFiles = jsonDictionary.json(atKeyPath: "outputFiles") ?? [] outputFilesCompilerFlags = jsonDictionary.json(atKeyPath: "outputFilesCompilerFlags") ?? [] name = jsonDictionary.json(atKeyPath: "name") runOncePerArchitecture = jsonDictionary.json(atKeyPath: "runOncePerArchitecture") ?? BuildRule.runOncePerArchitectureDefault } } extension BuildRule: JSONEncodable { public func toJSONValue() -> Any { var dict: [String: Any?] = [ "outputFiles": outputFiles, "outputFilesCompilerFlags": outputFilesCompilerFlags, "name": name, ] switch fileType { case .pattern(let string): dict["filePattern"] = string case .type(let string): dict["fileType"] = string } switch action { case .compilerSpec(let string): dict["compilerSpec"] = string case .script(let string): dict["script"] = string } if runOncePerArchitecture != BuildRule.runOncePerArchitectureDefault { dict["runOncePerArchitecture"] = runOncePerArchitecture } return dict } } ================================================ FILE: Sources/ProjectSpec/BuildScript.swift ================================================ import Foundation import JSONUtilities public struct BuildScript: Equatable { public static let runOnlyWhenInstallingDefault = false public static let showEnvVarsDefault = true public static let basedOnDependencyAnalysisDefault = true public var script: ScriptType public var name: String? public var shell: String? public var inputFiles: [String] public var outputFiles: [String] public var inputFileLists: [String] public var outputFileLists: [String] public var runOnlyWhenInstalling: Bool public let showEnvVars: Bool public let basedOnDependencyAnalysis: Bool public let discoveredDependencyFile: String? public enum ScriptType: Equatable { case path(String) case script(String) } public init( script: ScriptType, name: String? = nil, inputFiles: [String] = [], outputFiles: [String] = [], inputFileLists: [String] = [], outputFileLists: [String] = [], shell: String? = nil, runOnlyWhenInstalling: Bool = runOnlyWhenInstallingDefault, showEnvVars: Bool = showEnvVarsDefault, basedOnDependencyAnalysis: Bool = basedOnDependencyAnalysisDefault, discoveredDependencyFile: String? = nil ) { self.script = script self.name = name self.inputFiles = inputFiles self.outputFiles = outputFiles self.inputFileLists = inputFileLists self.outputFileLists = outputFileLists self.shell = shell self.runOnlyWhenInstalling = runOnlyWhenInstalling self.showEnvVars = showEnvVars self.basedOnDependencyAnalysis = basedOnDependencyAnalysis self.discoveredDependencyFile = discoveredDependencyFile } } extension BuildScript: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { name = jsonDictionary.json(atKeyPath: "name") inputFiles = jsonDictionary.json(atKeyPath: "inputFiles") ?? [] outputFiles = jsonDictionary.json(atKeyPath: "outputFiles") ?? [] inputFileLists = jsonDictionary.json(atKeyPath: "inputFileLists") ?? [] outputFileLists = jsonDictionary.json(atKeyPath: "outputFileLists") ?? [] if let string: String = jsonDictionary.json(atKeyPath: "script") { script = .script(string) } else { let path: String = try jsonDictionary.json(atKeyPath: "path") script = .path(path) } shell = jsonDictionary.json(atKeyPath: "shell") runOnlyWhenInstalling = jsonDictionary.json(atKeyPath: "runOnlyWhenInstalling") ?? BuildScript.runOnlyWhenInstallingDefault showEnvVars = jsonDictionary.json(atKeyPath: "showEnvVars") ?? BuildScript.showEnvVarsDefault basedOnDependencyAnalysis = jsonDictionary.json(atKeyPath: "basedOnDependencyAnalysis") ?? BuildScript.basedOnDependencyAnalysisDefault discoveredDependencyFile = jsonDictionary.json(atKeyPath: "discoveredDependencyFile") } } extension BuildScript: JSONEncodable { public func toJSONValue() -> Any { var dict: [String: Any?] = [ "inputFiles": inputFiles, "inputFileLists": inputFileLists, "outputFiles": outputFiles, "outputFileLists": outputFileLists, "runOnlyWhenInstalling": runOnlyWhenInstalling, "name": name, "shell": shell, ] if showEnvVars != BuildScript.showEnvVarsDefault { dict["showEnvVars"] = showEnvVars } if basedOnDependencyAnalysis != BuildScript.basedOnDependencyAnalysisDefault { dict["basedOnDependencyAnalysis"] = basedOnDependencyAnalysis } switch script { case .path(let string): dict["path"] = string case .script(let string): dict["script"] = string } if let discoveredDependencyFile = discoveredDependencyFile { dict["discoveredDependencyFile"] = discoveredDependencyFile } return dict } } extension BuildScript: PathContainer { static var pathProperties: [PathProperty] { [ .string("path"), ] } } ================================================ FILE: Sources/ProjectSpec/BuildSettingsContainer.swift ================================================ import Foundation public protocol BuildSettingsContainer { var settings: Settings { get } var configFiles: [String: String] { get } } ================================================ FILE: Sources/ProjectSpec/BuildSettingsExtractor.swift ================================================ import Foundation import JSONUtilities /// A helper for extracting and validating the `Settings` object from a JSON dictionary. struct BuildSettingsParser { let jsonDictionary: JSONDictionary /// Attempts to extract and parse the `Settings` from the dictionary. /// /// - Returns: A valid `Settings` object func parse() throws -> Settings { do { return try jsonDictionary.json(atKeyPath: "settings") } catch let specParsingError as SpecParsingError { // Re-throw `SpecParsingError` to prevent the misuse of settings.configs. throw specParsingError } catch { // Ignore all errors except `SpecParsingError` return .empty } } /// Attempts to extract and parse setting groups from the dictionary with fallback defaults. /// /// - Returns: Parsed setting groups or default groups if parsing fails func parseSettingGroups() throws -> [String: Settings] { do { return try jsonDictionary.json(atKeyPath: "settingGroups", invalidItemBehaviour: .fail) } catch let specParsingError as SpecParsingError { // Re-throw `SpecParsingError` to prevent the misuse of settingGroups. throw specParsingError } catch { // Ignore all errors except `SpecParsingError` return jsonDictionary.json(atKeyPath: "settingPresets") ?? [:] } } } ================================================ FILE: Sources/ProjectSpec/BuildToolPlugin.swift ================================================ import Foundation import JSONUtilities /// Specifies the use of a plug-in product in a target. public struct BuildToolPlugin: Equatable { /// The name of the plug-in target. public var plugin: String /// The name of the package that defines the plug-in target. public var package: String public init( plugin: String, package: String ) { self.plugin = plugin self.package = package } } extension BuildToolPlugin: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { if let plugin: String = jsonDictionary.json(atKeyPath: "plugin") { self.plugin = plugin } else { throw SpecParsingError.invalidDependency(jsonDictionary) } if let package: String = jsonDictionary.json(atKeyPath: "package") { self.package = package } else { throw SpecParsingError.invalidDependency(jsonDictionary) } } } extension BuildToolPlugin { public var uniqueID: String { return "\(plugin)/\(package)" } } extension BuildToolPlugin: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(plugin) hasher.combine(package) } } extension BuildToolPlugin: JSONEncodable { public func toJSONValue() -> Any { [ "plugin": plugin, "package": package ] } } ================================================ FILE: Sources/ProjectSpec/CacheFile.swift ================================================ import Foundation import XcodeGenCore import Version public class CacheFile { public let string: String init?(version: Version, projectDictionary: [String: Any], project: Project) throws { guard #available(OSX 10.13, *) else { return nil } let files = Set(project.allTrackedFiles) .map { ((try? $0.relativePath(from: project.basePath)) ?? $0).string } .sorted { $0.localizedStandardCompare($1) == .orderedAscending } .joined(separator: "\n") let data = try JSONSerialization.data(withJSONObject: projectDictionary, options: [.sortedKeys, .prettyPrinted]) let spec = String(data: data, encoding: .utf8)! string = """ # XCODEGEN VERSION \(version) # SPEC \(spec) # FILES \(files)" """ } } ================================================ FILE: Sources/ProjectSpec/Config.swift ================================================ import Foundation import JSONUtilities public struct Config: Hashable { public var name: String public var type: ConfigType? public init(name: String, type: ConfigType? = nil) { self.name = name self.type = type } public static var defaultConfigs: [Config] = [Config(name: ConfigType.debug.name, type: .debug), Config(name: ConfigType.release.name, type: .release)] } public enum ConfigType: String, Hashable { case debug case release public var name: String { rawValue.prefix(1).uppercased() + rawValue.dropFirst() } } extension Config { public func matchesVariant(_ variant: String, for type: ConfigType) -> Bool { guard self.type == type else { return false } let nameWithoutType = self.name.lowercased() .replacingOccurrences(of: type.name.lowercased(), with: "") .trimmingCharacters(in: CharacterSet(charactersIn: " -_()")) return nameWithoutType == variant.lowercased() } } public extension Collection where Element == Config { func first(including configVariant: String, for type: ConfigType) -> Config? { first { $0.matchesVariant(configVariant, for: type) } } } ================================================ FILE: Sources/ProjectSpec/Decoding.swift ================================================ import Foundation import JSONUtilities import PathKit import Yams extension Dictionary where Key: JSONKey { public func json(atKeyPath keyPath: JSONUtilities.KeyPath, invalidItemBehaviour: InvalidItemBehaviour = .remove, parallel: Bool = false) throws -> [T] { guard let dictionary = json(atKeyPath: keyPath) as JSONDictionary? else { return [] } if parallel { let defaultError = NSError(domain: "Unspecified error", code: 0, userInfo: nil) let keys = Array(dictionary.keys) var itemResults: [Result] = Array(repeating: .failure(defaultError), count: keys.count) itemResults.withUnsafeMutableBufferPointer { buffer in let bufferWrapper = BufferWrapper(buffer: buffer) DispatchQueue.concurrentPerform(iterations: dictionary.count) { idx in do { let key = keys[idx] let jsonDictionary: JSONDictionary = try dictionary.json(atKeyPath: .key(key)) let item = try T(name: key, jsonDictionary: jsonDictionary) bufferWrapper.buffer[idx] = .success(item) } catch { bufferWrapper.buffer[idx] = .failure(error) } } } return try itemResults.map { try $0.get() } } else { var items: [T] = [] for (key, _) in dictionary { let jsonDictionary: JSONDictionary = try dictionary.json(atKeyPath: .key(key)) let item = try T(name: key, jsonDictionary: jsonDictionary) items.append(item) } return items } } public func json(atKeyPath keyPath: JSONUtilities.KeyPath, invalidItemBehaviour: InvalidItemBehaviour = .remove) throws -> [T] { guard let dictionary = json(atKeyPath: keyPath) as JSONDictionary? else { return [] } var items: [T] = [] for (key, value) in dictionary { let item = try T(name: key, json: value) items.append(item) } return items } } private final class BufferWrapper: @unchecked Sendable { var buffer: UnsafeMutableBufferPointer init(buffer: UnsafeMutableBufferPointer) { self.buffer = buffer } } public protocol NamedJSONDictionaryConvertible { init(name: String, jsonDictionary: JSONDictionary) throws } public protocol NamedJSONConvertible { init(name: String, json: Any) throws } extension JSONObjectConvertible { public init(path: Path) throws { let content: String = try path.read() if content == "" { try self.init(jsonDictionary: [:]) return } let yaml = try Yams.load(yaml: content) guard let jsonDictionary = yaml as? JSONDictionary else { throw JSONUtilsError.fileNotAJSONDictionary } try self.init(jsonDictionary: jsonDictionary) } } ================================================ FILE: Sources/ProjectSpec/Dependency.swift ================================================ import Foundation import JSONUtilities public struct Dependency: Equatable { public static let removeHeadersDefault = true public static let implicitDefault = false public static let weakLinkDefault = false public static let platformFilterDefault: PlatformFilter = .all public var type: DependencyType public var reference: String public var embed: Bool? public var codeSign: Bool? public var removeHeaders: Bool = removeHeadersDefault public var link: Bool? public var implicit: Bool = implicitDefault public var weakLink: Bool = weakLinkDefault public var platformFilter: PlatformFilter = platformFilterDefault public var destinationFilters: [SupportedDestination]? public var platforms: Set? public var copyPhase: BuildPhaseSpec.CopyFilesSettings? public init( type: DependencyType, reference: String, embed: Bool? = nil, codeSign: Bool? = nil, link: Bool? = nil, implicit: Bool = implicitDefault, weakLink: Bool = weakLinkDefault, platformFilter: PlatformFilter = platformFilterDefault, destinationFilters: [SupportedDestination]? = nil, platforms: Set? = nil, copyPhase: BuildPhaseSpec.CopyFilesSettings? = nil ) { self.type = type self.reference = reference self.embed = embed self.codeSign = codeSign self.link = link self.implicit = implicit self.weakLink = weakLink self.platformFilter = platformFilter self.destinationFilters = destinationFilters self.platforms = platforms self.copyPhase = copyPhase } public enum PlatformFilter: String, Equatable { case all case iOS case macOS } public enum CarthageLinkType: String { case dynamic case `static` public static let `default` = dynamic } public enum DependencyType: Hashable { case target case framework case carthage(findFrameworks: Bool?, linkType: CarthageLinkType) case sdk(root: String?) case package(products: [String]) case bundle } } extension Dependency { public var uniqueID: String { switch type { case .package(let products): if !products.isEmpty { return "\(reference)/\(products.joined(separator: ","))" } else { return reference } default: return reference } } } extension Dependency: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(reference) hasher.combine(type) } } extension Dependency: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { if let target: String = jsonDictionary.json(atKeyPath: "target") { type = .target reference = target } else if let framework: String = jsonDictionary.json(atKeyPath: "framework") { type = .framework reference = framework } else if let carthage: String = jsonDictionary.json(atKeyPath: "carthage") { let findFrameworks: Bool? = jsonDictionary.json(atKeyPath: "findFrameworks") let carthageLinkType: CarthageLinkType = (jsonDictionary.json(atKeyPath: "linkType") as String?).flatMap(CarthageLinkType.init(rawValue:)) ?? .default type = .carthage(findFrameworks: findFrameworks, linkType: carthageLinkType) reference = carthage } else if let sdk: String = jsonDictionary.json(atKeyPath: "sdk") { let sdkRoot: String? = jsonDictionary.json(atKeyPath: "root") type = .sdk(root: sdkRoot) reference = sdk } else if let package: String = jsonDictionary.json(atKeyPath: "package") { if let products: [String] = jsonDictionary.json(atKeyPath: "products") { type = .package(products: products) reference = package } else if let product: String = jsonDictionary.json(atKeyPath: "product") { type = .package(products: [product]) reference = package } else { type = .package(products: []) reference = package } } else if let bundle: String = jsonDictionary.json(atKeyPath: "bundle") { type = .bundle reference = bundle } else { throw SpecParsingError.invalidDependency(jsonDictionary) } embed = jsonDictionary.json(atKeyPath: "embed") codeSign = jsonDictionary.json(atKeyPath: "codeSign") link = jsonDictionary.json(atKeyPath: "link") if let bool: Bool = jsonDictionary.json(atKeyPath: "removeHeaders") { removeHeaders = bool } if let bool: Bool = jsonDictionary.json(atKeyPath: "implicit") { implicit = bool } if let bool: Bool = jsonDictionary.json(atKeyPath: "weak") { weakLink = bool } if let platformFilterString: String = jsonDictionary.json(atKeyPath: "platformFilter"), let platformFilter = PlatformFilter(rawValue: platformFilterString) { self.platformFilter = platformFilter } else { self.platformFilter = .all } if let destinationFilters: [SupportedDestination] = jsonDictionary.json(atKeyPath: "destinationFilters") { self.destinationFilters = destinationFilters } if let platforms: [ProjectSpec.Platform] = jsonDictionary.json(atKeyPath: "platforms") { self.platforms = Set(platforms) } if let object: JSONDictionary = jsonDictionary.json(atKeyPath: "copy") { copyPhase = try BuildPhaseSpec.CopyFilesSettings(jsonDictionary: object) } } } extension Dependency: JSONEncodable { public func toJSONValue() -> Any { var dict: [String: Any?] = [ "embed": embed, "codeSign": codeSign, "link": link, "platforms": platforms?.map(\.rawValue).sorted(), "copy": copyPhase?.toJSONValue(), "destinationFilters": destinationFilters?.map { $0.rawValue }, ] if removeHeaders != Dependency.removeHeadersDefault { dict["removeHeaders"] = removeHeaders } if implicit != Dependency.implicitDefault { dict["implicit"] = implicit } if weakLink != Dependency.weakLinkDefault { dict["weak"] = weakLink } switch type { case .target: dict["target"] = reference case .framework: dict["framework"] = reference case .carthage(let findFrameworks, let linkType): dict["carthage"] = reference if let findFrameworks = findFrameworks { dict["findFrameworks"] = findFrameworks } dict["linkType"] = linkType.rawValue case .sdk: dict["sdk"] = reference case .package: dict["package"] = reference case .bundle: dict["bundle"] = reference } return dict } } extension Dependency: PathContainer { static var pathProperties: [PathProperty] { [ .string("framework"), ] } } ================================================ FILE: Sources/ProjectSpec/DeploymentTarget.swift ================================================ import Foundation import JSONUtilities import Version public struct DeploymentTarget: Equatable { public var iOS: Version? public var tvOS: Version? public var watchOS: Version? public var macOS: Version? public var visionOS: Version? public init( iOS: Version? = nil, tvOS: Version? = nil, watchOS: Version? = nil, macOS: Version? = nil, visionOS: Version? = nil ) { self.iOS = iOS self.tvOS = tvOS self.watchOS = watchOS self.macOS = macOS self.visionOS = visionOS } public func version(for platform: Platform) -> Version? { switch platform { case .auto: return nil case .iOS: return iOS case .tvOS: return tvOS case .watchOS: return watchOS case .macOS: return macOS case .visionOS: return visionOS } } } extension Platform { public var deploymentTargetSetting: String { switch self { case .auto: return "" case .iOS: return "IPHONEOS_DEPLOYMENT_TARGET" case .tvOS: return "TVOS_DEPLOYMENT_TARGET" case .watchOS: return "WATCHOS_DEPLOYMENT_TARGET" case .macOS: return "MACOSX_DEPLOYMENT_TARGET" case .visionOS: return "XROS_DEPLOYMENT_TARGET" } } public var sdkRoot: String { switch self { case .auto: return "auto" case .iOS: return "iphoneos" case .tvOS: return "appletvos" case .watchOS: return "watchos" case .macOS: return "macosx" case .visionOS: return "xros" } } } extension Version { /// doesn't print patch if 0 public var deploymentTarget: String { "\(major).\(minor)\(patch > 0 ? ".\(patch)" : "")" } } extension DeploymentTarget: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { func parseVersion(_ platform: String) throws -> Version? { if let string: String = jsonDictionary.json(atKeyPath: .key(platform)) { return try Version.parse(string) } else if let double: Double = jsonDictionary.json(atKeyPath: .key(platform)) { return try Version.parse(double) } else { return nil } } iOS = try parseVersion("iOS") tvOS = try parseVersion("tvOS") watchOS = try parseVersion("watchOS") macOS = try parseVersion("macOS") visionOS = try parseVersion("visionOS") } } extension DeploymentTarget: JSONEncodable { public func toJSONValue() -> Any { [ "iOS": iOS?.description, "tvOS": tvOS?.description, "watchOS": watchOS?.description, "macOS": macOS?.description, "visionOS": visionOS?.description, ] } } ================================================ FILE: Sources/ProjectSpec/Dictionary+Extension.swift ================================================ extension Dictionary where Key == String, Value == Any? { func removingEmptyArraysDictionariesAndNils() -> [String: Any] { var new: [String: Any] = [:] filter(outNil).forEach { pair in let value: Any if let array = pair.value as? [[String: Any?]] { value = array.removingEmptyArraysDictionariesAndNils() } else if let dictionary = pair.value as? [String: Any?] { value = dictionary.removingEmptyArraysDictionariesAndNils() } else { value = pair.value! // nil is filtered out :) } new[pair.key] = value } return new .filter(outEmptyArrays) .filter(outEmptyDictionaries) } func outEmptyArrays(_ pair: (key: String, value: Any)) -> Bool { guard let array = pair.value as? [Any] else { return true } return !array.isEmpty } func outEmptyDictionaries(_ pair: (key: String, value: Any)) -> Bool { guard let dictionary = pair.value as? [String: Any] else { return true } return !dictionary.isEmpty } func outNil(_ pair: (key: String, value: Any?)) -> Bool { return pair.value != nil } } ================================================ FILE: Sources/ProjectSpec/Encoding.swift ================================================ import Foundation import JSONUtilities public protocol JSONEncodable { // returns JSONDictionary or JSONArray or JSONRawType or nil func toJSONValue() -> Any } ================================================ FILE: Sources/ProjectSpec/FileType.swift ================================================ // // File.swift // // // Created by Yonas Kolb on 1/5/20. // import Foundation import JSONUtilities import enum XcodeProj.BuildPhase public struct FileType: Equatable { public enum Defaults { public static let file = true } public var file: Bool public var buildPhase: BuildPhaseSpec? public var attributes: [String] public var resourceTags: [String] public var compilerFlags: [String] public init( file: Bool = Defaults.file, buildPhase: BuildPhaseSpec? = nil, attributes: [String] = [], resourceTags: [String] = [], compilerFlags: [String] = [] ) { self.file = file self.buildPhase = buildPhase self.attributes = attributes self.resourceTags = resourceTags self.compilerFlags = compilerFlags } } extension FileType: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { if let string: String = jsonDictionary.json(atKeyPath: "buildPhase") { buildPhase = try BuildPhaseSpec(string: string) } else if let dict: JSONDictionary = jsonDictionary.json(atKeyPath: "buildPhase") { buildPhase = try BuildPhaseSpec(jsonDictionary: dict) } file = jsonDictionary.json(atKeyPath: "file") ?? Defaults.file attributes = jsonDictionary.json(atKeyPath: "attributes") ?? [] resourceTags = jsonDictionary.json(atKeyPath: "resourceTags") ?? [] compilerFlags = jsonDictionary.json(atKeyPath: "compilerFlags") ?? [] } } extension FileType: JSONEncodable { public func toJSONValue() -> Any { var dict: [String: Any?] = [ "buildPhase": buildPhase?.toJSONValue(), "attributes": attributes, "resourceTags": resourceTags, "compilerFlags": compilerFlags, ] if file != Defaults.file { dict["file"] = file } return dict } } extension FileType { public static let defaultFileTypes: [String: FileType] = [ // resources "bundle": FileType(buildPhase: .resources), "xcassets": FileType(buildPhase: .resources), "storekit": FileType(buildPhase: .resources), "xcstrings": FileType(buildPhase: .resources), "icon": FileType(buildPhase: .resources), // sources "swift": FileType(buildPhase: .sources), "gyb": FileType(buildPhase: .sources), "m": FileType(buildPhase: .sources), "mm": FileType(buildPhase: .sources), "cpp": FileType(buildPhase: .sources), "cp": FileType(buildPhase: .sources), "cxx": FileType(buildPhase: .sources), "c": FileType(buildPhase: .sources), "cc": FileType(buildPhase: .sources), "S": FileType(buildPhase: .sources), "xcdatamodeld": FileType(buildPhase: .sources), "xcmappingmodel": FileType(buildPhase: .sources), "intentdefinition": FileType(buildPhase: .sources), "metal": FileType(buildPhase: .sources), "mlmodel": FileType(buildPhase: .sources), "mlpackage" : FileType(buildPhase: .sources), "mlmodelc": FileType(buildPhase: .resources), "rcproject": FileType(buildPhase: .sources), "iig": FileType(buildPhase: .sources), "docc": FileType(buildPhase: .sources), // headers "h": FileType(buildPhase: .headers), "hh": FileType(buildPhase: .headers), "hpp": FileType(buildPhase: .headers), "ipp": FileType(buildPhase: .headers), "tpp": FileType(buildPhase: .headers), "hxx": FileType(buildPhase: .headers), "def": FileType(buildPhase: .headers), // frameworks "framework": FileType(buildPhase: .frameworks), // copyfiles "xpc": FileType(buildPhase: .copyFiles(.xpcServices)), "appex": FileType(buildPhase: .copyFiles(.plugins)), // no build phase (not resources) "xcconfig": FileType(buildPhase: BuildPhaseSpec.none), "entitlements": FileType(buildPhase: BuildPhaseSpec.none), "gpx": FileType(buildPhase: BuildPhaseSpec.none), "lproj": FileType(buildPhase: BuildPhaseSpec.none), "xcfilelist": FileType(buildPhase: BuildPhaseSpec.none), "apns": FileType(buildPhase: BuildPhaseSpec.none), "pch": FileType(buildPhase: BuildPhaseSpec.none), "xctestplan": FileType(buildPhase: BuildPhaseSpec.none), ] } ================================================ FILE: Sources/ProjectSpec/GroupOrdering.swift ================================================ import Foundation import JSONUtilities /// Describes an order of groups. public struct GroupOrdering: Equatable { /// A group name pattern. public var pattern: String /// A group name regex. public var regex: NSRegularExpression? /// Subgroups orders. public var order: [String] public init(pattern: String = "", order: [String] = []) { self.pattern = pattern self.regex = try? NSRegularExpression(pattern: pattern) self.order = order } } extension GroupOrdering: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { pattern = jsonDictionary.json(atKeyPath: "pattern") ?? "" regex = try? NSRegularExpression(pattern: pattern) order = jsonDictionary.json(atKeyPath: "order") ?? [] } } ================================================ FILE: Sources/ProjectSpec/Linkage.swift ================================================ import Foundation import XcodeProj public enum Linkage { case dynamic case `static` case none } extension Target { public var defaultLinkage: Linkage { switch type { case .none, .appExtension, .application, .bundle, .commandLineTool, .instrumentsPackage, .intentsServiceExtension, .messagesApplication, .messagesExtension, .metalLibrary, .ocUnitTestBundle, .onDemandInstallCapableApplication, .stickerPack, .tvExtension, .uiTestBundle, .unitTestBundle, .watchApp, .watchExtension, .watch2App, .watch2AppContainer, .watch2Extension, .xcodeExtension, .xpcService, .systemExtension, .driverExtension, .extensionKitExtension: return .none case .framework, .xcFramework: // Check the MACH_O_TYPE for "Static Framework" if settings.buildSettings.machOType == "staticlib" { return .static } else { return .dynamic } case .dynamicLibrary: return .dynamic case .staticLibrary, .staticFramework: return .static } } } private extension BuildSettings { var machOType: String? { self["MACH_O_TYPE"]?.stringValue } } ================================================ FILE: Sources/ProjectSpec/NSRegularExpressionExtensions.swift ================================================ import Foundation public extension NSRegularExpression { func isMatch(to string: String) -> Bool { let range = NSRange(location: 0, length: string.utf16.count) return self.firstMatch(in: string, options: [], range: range) != nil } } ================================================ FILE: Sources/ProjectSpec/PathContainer.swift ================================================ import Foundation import JSONUtilities import PathKit protocol PathContainer { static var pathProperties: [PathProperty] { get } } enum PathProperty { case string(String) case dictionary([PathProperty]) case object(String, [PathProperty]) } extension Array where Element == PathProperty { func resolvingPaths(in jsonDictionary: JSONDictionary, relativeTo path: Path) -> JSONDictionary { var result = jsonDictionary for pathProperty in self { switch pathProperty { case .string(let key): if let source = result[key] as? String { result[key] = (path + source).string } else if let source = result[key] as? [Any] { result[key] = source.map { any -> Any in if let string = any as? String { return (path + string).string } else { return any } } } else if let source = result[key] as? [String: String] { result[key] = source.mapValues { (path + $0).string } } case .dictionary(let pathProperties): for (key, dictionary) in result { if let source = dictionary as? JSONDictionary { result[key] = pathProperties.resolvingPaths(in: source, relativeTo: path) } } case .object(let key, let pathProperties): if let source = result[key] as? JSONDictionary { result[key] = pathProperties.resolvingPaths(in: source, relativeTo: path) } else if let source = result[key] as? [Any] { result[key] = source.map { any -> Any in if let dictionary = any as? JSONDictionary { return pathProperties.resolvingPaths(in: dictionary, relativeTo: path) } else { return any } } } else if let source = result[key] as? [String: JSONDictionary] { result[key] = source.mapValues { pathProperties.resolvingPaths(in: $0, relativeTo: path) } } } } return result } } ================================================ FILE: Sources/ProjectSpec/Platform.swift ================================================ import Foundation public enum Platform: String, Hashable, CaseIterable { case auto case iOS case tvOS case macOS case watchOS case visionOS } ================================================ FILE: Sources/ProjectSpec/Plist.swift ================================================ import Foundation import JSONUtilities public struct Plist: Equatable { public let path: String public let properties: [String: Any] public init(path: String, attributes: [String: Any] = [:]) { self.path = path properties = attributes } public static func == (lhs: Plist, rhs: Plist) -> Bool { lhs.path == rhs.path && NSDictionary(dictionary: lhs.properties).isEqual(to: rhs.properties) } } extension Plist: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { path = try jsonDictionary.json(atKeyPath: "path") properties = jsonDictionary.json(atKeyPath: "properties") ?? [:] } } extension Plist: JSONEncodable { public func toJSONValue() -> Any { [ "path": path, "properties": properties, ] as [String : Any] } } extension Plist: PathContainer { static var pathProperties: [PathProperty] { [ .string("path"), ] } } ================================================ FILE: Sources/ProjectSpec/Project.swift ================================================ import Foundation import JSONUtilities import PathKit import Yams public struct Project: BuildSettingsContainer { public var basePath: Path public var name: String public var targets: [Target] { didSet { targetsMap = Dictionary(uniqueKeysWithValues: targets.map { ($0.name, $0) }) } } public var aggregateTargets: [AggregateTarget] { didSet { aggregateTargetsMap = Dictionary(uniqueKeysWithValues: aggregateTargets.map { ($0.name, $0) }) } } public var packages: [String: SwiftPackage] public var settings: Settings public var settingGroups: [String: Settings] public var configs: [Config] public var schemes: [Scheme] public var breakpoints: [Breakpoint] public var options: SpecOptions public var attributes: [String: Any] public var fileGroups: [String] public var configFiles: [String: String] public var include: [String] = [] public var projectReferences: [ProjectReference] = [] { didSet { projectReferencesMap = Dictionary(uniqueKeysWithValues: projectReferences.map { ($0.name, $0) }) } } private var targetsMap: [String: Target] private var aggregateTargetsMap: [String: AggregateTarget] private var projectReferencesMap: [String: ProjectReference] public init( basePath: Path = "", name: String, configs: [Config] = Config.defaultConfigs, targets: [Target] = [], aggregateTargets: [AggregateTarget] = [], settings: Settings = .empty, settingGroups: [String: Settings] = [:], schemes: [Scheme] = [], breakpoints: [Breakpoint] = [], packages: [String: SwiftPackage] = [:], options: SpecOptions = SpecOptions(), fileGroups: [String] = [], configFiles: [String: String] = [:], attributes: [String: Any] = [:], projectReferences: [ProjectReference] = [] ) { self.basePath = basePath self.name = name self.targets = targets targetsMap = Dictionary(uniqueKeysWithValues: self.targets.map { ($0.name, $0) }) self.aggregateTargets = aggregateTargets aggregateTargetsMap = Dictionary(uniqueKeysWithValues: self.aggregateTargets.map { ($0.name, $0) }) self.configs = configs self.settings = settings self.settingGroups = settingGroups self.schemes = schemes self.breakpoints = breakpoints self.packages = packages self.options = options self.fileGroups = fileGroups self.configFiles = configFiles self.attributes = attributes self.projectReferences = projectReferences projectReferencesMap = Dictionary(uniqueKeysWithValues: self.projectReferences.map { ($0.name, $0) }) } public func getProjectReference(_ projectName: String) -> ProjectReference? { projectReferencesMap[projectName] } public func getTarget(_ targetName: String) -> Target? { targetsMap[targetName] } public func getPackage(_ packageName: String) -> SwiftPackage? { packages[packageName] } public func getAggregateTarget(_ targetName: String) -> AggregateTarget? { aggregateTargetsMap[targetName] } public func getProjectTarget(_ targetName: String) -> ProjectTarget? { targetsMap[targetName] ?? aggregateTargetsMap[targetName] } public func getConfig(_ configName: String) -> Config? { configs.first { $0.name == configName } } public var defaultProjectPath: Path { basePath + "\(name).xcodeproj" } } extension Project: CustomDebugStringConvertible { public var debugDescription: String { var string = "Name: \(name)" let indent = " " if !include.isEmpty { string += "\nInclude:\n\(indent)" + include.map { $0.description }.joined(separator: "\n\(indent)") } if !settingGroups.isEmpty { string += "\nSetting Groups:\n\(indent)" + settingGroups.keys .sorted() .joined(separator: "\n\(indent)") } if !targets.isEmpty { string += "\nTargets:\n\(indent)" + targets.map { $0.description }.joined(separator: "\n\(indent)") } if !aggregateTargets.isEmpty { string += "\nAggregate Targets:\n\(indent)" + aggregateTargets.map { $0.description }.joined(separator: "\n\(indent)") } if !schemes.isEmpty { let allSchemes = targets.filter { $0.scheme != nil }.map { $0.name } + schemes.map { $0.name } string += "\nSchemes:\n\(indent)" + allSchemes.joined(separator: "\n\(indent)") } return string } } extension Project: Equatable { public static func == (lhs: Project, rhs: Project) -> Bool { lhs.name == rhs.name && lhs.targets == rhs.targets && lhs.aggregateTargets == rhs.aggregateTargets && lhs.settings == rhs.settings && lhs.settingGroups == rhs.settingGroups && lhs.configs == rhs.configs && lhs.schemes == rhs.schemes && lhs.breakpoints == rhs.breakpoints && lhs.fileGroups == rhs.fileGroups && lhs.configFiles == rhs.configFiles && lhs.options == rhs.options && lhs.packages == rhs.packages && NSDictionary(dictionary: lhs.attributes).isEqual(to: rhs.attributes) } } extension Project { public init(path: Path) throws { let spec = try SpecFile(path: path) try self.init(spec: spec) } public init(spec: SpecFile) throws { try self.init(basePath: spec.basePath, jsonDictionary: spec.resolvedDictionary()) } public init(basePath: Path = "", jsonDictionary: JSONDictionary) throws { self.basePath = basePath let jsonDictionary = Project.resolveProject(jsonDictionary: jsonDictionary) let buildSettingsParser = BuildSettingsParser(jsonDictionary: jsonDictionary) name = try jsonDictionary.json(atKeyPath: "name") settings = try buildSettingsParser.parse() settingGroups = try buildSettingsParser.parseSettingGroups() let configs: [String: String] = jsonDictionary.json(atKeyPath: "configs") ?? [:] self.configs = configs.isEmpty ? Config.defaultConfigs : configs.map { Config(name: $0, type: ConfigType(rawValue: $1)) }.sorted { $0.name < $1.name } targets = try jsonDictionary.json(atKeyPath: "targets", parallel: true).sorted { $0.name < $1.name } aggregateTargets = try jsonDictionary.json(atKeyPath: "aggregateTargets").sorted { $0.name < $1.name } projectReferences = try jsonDictionary.json(atKeyPath: "projectReferences").sorted { $0.name < $1.name } schemes = try jsonDictionary.json(atKeyPath: "schemes") if jsonDictionary["breakpoints"] != nil { breakpoints = try jsonDictionary.json(atKeyPath: "breakpoints", invalidItemBehaviour: .fail) } else { breakpoints = [] } fileGroups = jsonDictionary.json(atKeyPath: "fileGroups") ?? [] configFiles = jsonDictionary.json(atKeyPath: "configFiles") ?? [:] attributes = jsonDictionary.json(atKeyPath: "attributes") ?? [:] include = jsonDictionary.json(atKeyPath: "include") ?? [] if jsonDictionary["packages"] != nil { packages = try jsonDictionary.json(atKeyPath: "packages", invalidItemBehaviour: .fail) } else { packages = [:] } // For backward compatibility of old `localPackages:` format if let localPackages: [String] = jsonDictionary.json(atKeyPath: "localPackages") { packages.merge(localPackages.reduce(into: [String: SwiftPackage]()) { // Project name will be obtained by resolved abstractpath's lastComponent for dealing with some path case, like "../" let packageName = (basePath + Path($1).normalize()).lastComponent $0[packageName] = .local(path: $1, group: nil, excludeFromProject: false) } ) } if jsonDictionary["options"] != nil { options = try jsonDictionary.json(atKeyPath: "options") } else { options = SpecOptions() } targetsMap = Dictionary(uniqueKeysWithValues: targets.map { ($0.name, $0) }) aggregateTargetsMap = Dictionary(uniqueKeysWithValues: aggregateTargets.map { ($0.name, $0) }) projectReferencesMap = Dictionary(uniqueKeysWithValues: projectReferences.map { ($0.name, $0) }) } static func resolveProject(jsonDictionary: JSONDictionary) -> JSONDictionary { var jsonDictionary = jsonDictionary // resolve multiple times so that we support both multi-platform templates, // as well as platform specific templates in multi-platform targets jsonDictionary = Target.resolveMultiplatformTargets(jsonDictionary: jsonDictionary) jsonDictionary = Target.resolveTargetTemplates(jsonDictionary: jsonDictionary) jsonDictionary = Scheme.resolveSchemeTemplates(jsonDictionary: jsonDictionary) jsonDictionary = Target.resolveMultiplatformTargets(jsonDictionary: jsonDictionary) return jsonDictionary } } extension Project: PathContainer { static var pathProperties: [PathProperty] { [ .string("configFiles"), .object("options", SpecOptions.pathProperties), .object("targets", Target.pathProperties), .object("targetTemplates", Target.pathProperties), .object("aggregateTargets", AggregateTarget.pathProperties), .object("schemes", Scheme.pathProperties), .object("projectReferences", ProjectReference.pathProperties), .object("packages", SwiftPackage.pathProperties), .string("localPackages"), .string("fileGroups") ] } } extension Project { public var allTrackedFiles: [Path] { var files: [Path] = [] files.append(contentsOf: configFilePaths) for fileGroup in fileGroups { let fileGroupPath = basePath + fileGroup let fileGroupChildren = (try? fileGroupPath.recursiveChildren()) ?? [] files.append(contentsOf: fileGroupChildren) files.append(fileGroupPath) } for target in aggregateTargets { files.append(contentsOf: target.configFilePaths) } for target in targets { files.append(contentsOf: target.configFilePaths) for source in target.sources { let sourcePath = basePath + source.path let type = source.type ?? options.defaultSourceDirectoryType ?? .group if type.projectTracksChildren { let sourceChildren = (try? sourcePath.recursiveChildren()) ?? [] files.append(contentsOf: sourceChildren) } files.append(sourcePath) } } return files } } extension SourceType { var projectTracksChildren: Bool { switch self { case .file: false case .folder: false case .group: true case .syncedFolder: false } } } extension BuildSettingsContainer { fileprivate var configFilePaths: [Path] { configFiles.values.map { Path($0) } } } extension Project: JSONEncodable { public func toJSONValue() -> Any { toJSONDictionary() } public func toJSONDictionary() -> JSONDictionary { let targetPairs = targets.map { ($0.name, $0.toJSONValue()) } let configsPairs = configs.map { ($0.name, $0.type?.rawValue) } let aggregateTargetsPairs = aggregateTargets.map { ($0.name, $0.toJSONValue()) } let schemesPairs = schemes.map { ($0.name, $0.toJSONValue()) } let projectReferencesPairs = projectReferences.map { ($0.name, $0.toJSONValue()) } var dictionary: JSONDictionary = [:] dictionary["name"] = name dictionary["options"] = options.toJSONValue() dictionary["settings"] = settings.toJSONValue() dictionary["fileGroups"] = fileGroups dictionary["configFiles"] = configFiles dictionary["include"] = include dictionary["attributes"] = attributes dictionary["packages"] = packages.mapValues { $0.toJSONValue() } dictionary["targets"] = Dictionary(uniqueKeysWithValues: targetPairs) dictionary["configs"] = Dictionary(uniqueKeysWithValues: configsPairs) dictionary["aggregateTargets"] = Dictionary(uniqueKeysWithValues: aggregateTargetsPairs) dictionary["schemes"] = Dictionary(uniqueKeysWithValues: schemesPairs) dictionary["settingGroups"] = settingGroups.mapValues { $0.toJSONValue() } dictionary["projectReferences"] = Dictionary(uniqueKeysWithValues: projectReferencesPairs) return dictionary } } ================================================ FILE: Sources/ProjectSpec/ProjectReference.swift ================================================ import Foundation import JSONUtilities public struct ProjectReference: Hashable { public var name: String public var path: String public init(name: String, path: String) { self.name = name self.path = path } } extension ProjectReference: PathContainer { static var pathProperties: [PathProperty] { [ .dictionary([ .string("path"), ]), ] } } extension ProjectReference: NamedJSONDictionaryConvertible { public init(name: String, jsonDictionary: JSONDictionary) throws { self.name = name self.path = try jsonDictionary.json(atKeyPath: "path") } } extension ProjectReference: JSONEncodable { public func toJSONValue() -> Any { [ "path": path, ] } } ================================================ FILE: Sources/ProjectSpec/ProjectTarget.swift ================================================ import Foundation import XcodeProj public protocol ProjectTarget: BuildSettingsContainer { var name: String { get } var type: PBXProductType { get } var buildScripts: [BuildScript] { get } var buildToolPlugins: [BuildToolPlugin] { get } var scheme: TargetScheme? { get } var attributes: [String: Any] { get } } extension Target { public var buildScripts: [BuildScript] { preBuildScripts + postCompileScripts + postBuildScripts } } extension Project { public var projectTargets: [ProjectTarget] { targets.map { $0 as ProjectTarget } + aggregateTargets.map { $0 as ProjectTarget } } } ================================================ FILE: Sources/ProjectSpec/Scheme.swift ================================================ import Foundation import JSONUtilities import PathKit import XcodeProj public typealias BuildType = XCScheme.BuildAction.Entry.BuildFor public struct Scheme: Equatable { public var name: String public var build: Build public var run: Run? public var archive: Archive? public var analyze: Analyze? public var test: Test? public var profile: Profile? public var management: Management? public init( name: String, build: Build, run: Run? = nil, test: Test? = nil, profile: Profile? = nil, analyze: Analyze? = nil, archive: Archive? = nil, management: Management? = nil ) { self.name = name self.build = build self.run = run self.test = test self.profile = profile self.analyze = analyze self.archive = archive self.management = management } public struct Management: Equatable { public static let sharedDefault = true public var shared: Bool public var orderHint: Int? public var isShown: Bool? public init?( shared: Bool = Scheme.Management.sharedDefault, orderHint: Int? = nil, isShown: Bool? = nil ) { if shared == Scheme.Management.sharedDefault, orderHint == nil, isShown == nil { return nil } self.shared = shared self.orderHint = orderHint self.isShown = isShown } } public struct SimulateLocation: Equatable { public enum ReferenceType: String { case predefined = "1" case gpx = "0" } public var allow: Bool public var defaultLocation: String? public var referenceType: ReferenceType? { guard let defaultLocation = self.defaultLocation else { return nil } if defaultLocation.contains(".gpx") { return .gpx } return .predefined } public init(allow: Bool, defaultLocation: String) { self.allow = allow self.defaultLocation = defaultLocation } } public struct ExecutionAction: Equatable { public var script: String public var name: String public var settingsTarget: String? public var shell: String? public init(name: String, script: String, shell: String? = nil, settingsTarget: String? = nil) { self.script = script self.name = name self.settingsTarget = settingsTarget self.shell = shell } } public struct Build: Equatable { public static let parallelizeBuildDefault = true public static let buildImplicitDependenciesDefault = true public static let runPostActionsOnFailureDefault = false public var targets: [BuildTarget] public var parallelizeBuild: Bool public var buildImplicitDependencies: Bool public var preActions: [ExecutionAction] public var postActions: [ExecutionAction] public var runPostActionsOnFailure: Bool public init( targets: [BuildTarget], parallelizeBuild: Bool = parallelizeBuildDefault, buildImplicitDependencies: Bool = buildImplicitDependenciesDefault, preActions: [ExecutionAction] = [], postActions: [ExecutionAction] = [], runPostActionsOnFailure: Bool = false ) { self.targets = targets self.parallelizeBuild = parallelizeBuild self.buildImplicitDependencies = buildImplicitDependencies self.preActions = preActions self.postActions = postActions self.runPostActionsOnFailure = runPostActionsOnFailure } } public struct Run: BuildAction { public static let enableAddressSanitizerDefault = false public static let enableASanStackUseAfterReturnDefault = false public static let enableThreadSanitizerDefault = false public static let enableUBSanitizerDefault = false public static let disableMainThreadCheckerDefault = false public static let stopOnEveryMainThreadCheckerIssueDefault = false public static let disableThreadPerformanceCheckerDefault = false public static let debugEnabledDefault = true public static let enableGPUValidationModeDefault = true public var config: String? public var commandLineArguments: [String: Bool] public var preActions: [ExecutionAction] public var postActions: [ExecutionAction] public var environmentVariables: [XCScheme.EnvironmentVariable] public var enableGPUFrameCaptureMode: XCScheme.LaunchAction.GPUFrameCaptureMode public var enableGPUValidationMode: Bool public var enableAddressSanitizer: Bool public var enableASanStackUseAfterReturn: Bool public var enableThreadSanitizer: Bool public var enableUBSanitizer: Bool public var disableMainThreadChecker: Bool public var stopOnEveryMainThreadCheckerIssue: Bool public var disableThreadPerformanceChecker: Bool public var language: String? public var region: String? public var askForAppToLaunch: Bool? public var launchAutomaticallySubstyle: String? public var debugEnabled: Bool public var simulateLocation: SimulateLocation? public var executable: String? public var storeKitConfiguration: String? public var customLLDBInit: String? public var macroExpansion: String? public var customWorkingDirectory: String? public init( config: String? = nil, executable: String? = nil, commandLineArguments: [String: Bool] = [:], preActions: [ExecutionAction] = [], postActions: [ExecutionAction] = [], environmentVariables: [XCScheme.EnvironmentVariable] = [], enableGPUFrameCaptureMode: XCScheme.LaunchAction.GPUFrameCaptureMode = XCScheme.LaunchAction.defaultGPUFrameCaptureMode, enableGPUValidationMode: Bool = enableGPUValidationModeDefault, enableAddressSanitizer: Bool = enableAddressSanitizerDefault, enableASanStackUseAfterReturn: Bool = enableASanStackUseAfterReturnDefault, enableThreadSanitizer: Bool = enableThreadSanitizerDefault, enableUBSanitizer: Bool = enableUBSanitizerDefault, disableMainThreadChecker: Bool = disableMainThreadCheckerDefault, stopOnEveryMainThreadCheckerIssue: Bool = stopOnEveryMainThreadCheckerIssueDefault, disableThreadPerformanceChecker: Bool = disableThreadPerformanceCheckerDefault, language: String? = nil, region: String? = nil, askForAppToLaunch: Bool? = nil, launchAutomaticallySubstyle: String? = nil, debugEnabled: Bool = debugEnabledDefault, simulateLocation: SimulateLocation? = nil, storeKitConfiguration: String? = nil, customLLDBInit: String? = nil, macroExpansion: String? = nil, customWorkingDirectory: String? = nil ) { self.config = config self.commandLineArguments = commandLineArguments self.preActions = preActions self.postActions = postActions self.environmentVariables = environmentVariables self.enableAddressSanitizer = enableAddressSanitizer self.enableASanStackUseAfterReturn = enableASanStackUseAfterReturn self.enableThreadSanitizer = enableThreadSanitizer self.enableUBSanitizer = enableUBSanitizer self.disableMainThreadChecker = disableMainThreadChecker self.enableGPUFrameCaptureMode = enableGPUFrameCaptureMode self.enableGPUValidationMode = enableGPUValidationMode self.stopOnEveryMainThreadCheckerIssue = stopOnEveryMainThreadCheckerIssue self.disableThreadPerformanceChecker = disableThreadPerformanceChecker self.language = language self.region = region self.askForAppToLaunch = askForAppToLaunch self.launchAutomaticallySubstyle = launchAutomaticallySubstyle self.debugEnabled = debugEnabled self.simulateLocation = simulateLocation self.storeKitConfiguration = storeKitConfiguration self.customLLDBInit = customLLDBInit self.macroExpansion = macroExpansion self.customWorkingDirectory = customWorkingDirectory } } public struct Test: BuildAction { public static let gatherCoverageDataDefault = false public static let enableAddressSanitizerDefault = false public static let enableASanStackUseAfterReturnDefault = false public static let enableThreadSanitizerDefault = false public static let enableUBSanitizerDefault = false public static let disableMainThreadCheckerDefault = false public static let debugEnabledDefault = true public static let captureScreenshotsAutomaticallyDefault = true public static let deleteScreenshotsWhenEachTestSucceedsDefault = true public static let preferredScreenCaptureFormatDefault = XCScheme.TestAction.ScreenCaptureFormat.screenRecording public var config: String? public var gatherCoverageData: Bool public var coverageTargets: [TestableTargetReference] public var enableAddressSanitizer: Bool public var enableASanStackUseAfterReturn: Bool public var enableThreadSanitizer: Bool public var enableUBSanitizer: Bool public var disableMainThreadChecker: Bool public var commandLineArguments: [String: Bool] public var targets: [TestTarget] public var preActions: [ExecutionAction] public var postActions: [ExecutionAction] public var environmentVariables: [XCScheme.EnvironmentVariable] public var language: String? public var region: String? public var debugEnabled: Bool public var customLLDBInit: String? public var captureScreenshotsAutomatically: Bool public var deleteScreenshotsWhenEachTestSucceeds: Bool public var testPlans: [TestPlan] public var macroExpansion: String? public var preferredScreenCaptureFormat: XCScheme.TestAction.ScreenCaptureFormat public struct TestTarget: Equatable, ExpressibleByStringLiteral { public static let randomExecutionOrderDefault = false public static let parallelizableDefault = false public var name: String { targetReference.name } public let targetReference: TestableTargetReference public var randomExecutionOrder: Bool public var parallelizable: Bool public var location: String? public var skipped: Bool public var skippedTests: [String] public var selectedTests: [String] public init( targetReference: TestableTargetReference, randomExecutionOrder: Bool = randomExecutionOrderDefault, parallelizable: Bool = parallelizableDefault, location: String? = nil, skipped: Bool = false, skippedTests: [String] = [], selectedTests: [String] = [] ) { self.targetReference = targetReference self.randomExecutionOrder = randomExecutionOrder self.parallelizable = parallelizable self.location = location self.skipped = skipped self.skippedTests = skippedTests self.selectedTests = selectedTests } public init(stringLiteral value: String) { do { targetReference = try TestableTargetReference(value) randomExecutionOrder = false parallelizable = false location = nil skipped = false skippedTests = [] selectedTests = [] } catch { fatalError(SpecParsingError.invalidTargetReference(value).description) } } } public init( config: String? = nil, gatherCoverageData: Bool = gatherCoverageDataDefault, coverageTargets: [TestableTargetReference] = [], enableAddressSanitizer: Bool = enableAddressSanitizerDefault, enableASanStackUseAfterReturn: Bool = enableASanStackUseAfterReturnDefault, enableThreadSanitizer: Bool = enableThreadSanitizerDefault, enableUBSanitizer: Bool = enableUBSanitizerDefault, disableMainThreadChecker: Bool = disableMainThreadCheckerDefault, randomExecutionOrder: Bool = false, parallelizable: Bool = false, commandLineArguments: [String: Bool] = [:], targets: [TestTarget] = [], preActions: [ExecutionAction] = [], postActions: [ExecutionAction] = [], environmentVariables: [XCScheme.EnvironmentVariable] = [], testPlans: [TestPlan] = [], language: String? = nil, region: String? = nil, debugEnabled: Bool = debugEnabledDefault, customLLDBInit: String? = nil, captureScreenshotsAutomatically: Bool = captureScreenshotsAutomaticallyDefault, deleteScreenshotsWhenEachTestSucceeds: Bool = deleteScreenshotsWhenEachTestSucceedsDefault, macroExpansion: String? = nil, preferredScreenCaptureFormat: XCScheme.TestAction.ScreenCaptureFormat = preferredScreenCaptureFormatDefault ) { self.config = config self.gatherCoverageData = gatherCoverageData self.coverageTargets = coverageTargets self.enableAddressSanitizer = enableAddressSanitizer self.enableASanStackUseAfterReturn = enableASanStackUseAfterReturn self.enableThreadSanitizer = enableThreadSanitizer self.enableUBSanitizer = enableUBSanitizer self.disableMainThreadChecker = disableMainThreadChecker self.commandLineArguments = commandLineArguments self.targets = targets self.preActions = preActions self.postActions = postActions self.environmentVariables = environmentVariables self.testPlans = testPlans self.language = language self.region = region self.debugEnabled = debugEnabled self.customLLDBInit = customLLDBInit self.captureScreenshotsAutomatically = captureScreenshotsAutomatically self.deleteScreenshotsWhenEachTestSucceeds = deleteScreenshotsWhenEachTestSucceeds self.macroExpansion = macroExpansion self.preferredScreenCaptureFormat = preferredScreenCaptureFormat } public var shouldUseLaunchSchemeArgsEnv: Bool { commandLineArguments.isEmpty && environmentVariables.isEmpty } } public struct Analyze: BuildAction { public var config: String? public init(config: String) { self.config = config } } public struct Profile: BuildAction { public var config: String? public var commandLineArguments: [String: Bool] public var preActions: [ExecutionAction] public var postActions: [ExecutionAction] public var environmentVariables: [XCScheme.EnvironmentVariable] public var askForAppToLaunch: Bool? public init( config: String? = nil, commandLineArguments: [String: Bool] = [:], preActions: [ExecutionAction] = [], postActions: [ExecutionAction] = [], environmentVariables: [XCScheme.EnvironmentVariable] = [], askForAppToLaunch: Bool? = nil ) { self.config = config self.commandLineArguments = commandLineArguments self.preActions = preActions self.postActions = postActions self.environmentVariables = environmentVariables self.askForAppToLaunch = askForAppToLaunch } public var shouldUseLaunchSchemeArgsEnv: Bool { commandLineArguments.isEmpty && environmentVariables.isEmpty } } public struct Archive: BuildAction { public static let revealArchiveInOrganizerDefault = true public var config: String? public var customArchiveName: String? public var revealArchiveInOrganizer: Bool public var preActions: [ExecutionAction] public var postActions: [ExecutionAction] public init( config: String? = nil, customArchiveName: String? = nil, revealArchiveInOrganizer: Bool = revealArchiveInOrganizerDefault, preActions: [ExecutionAction] = [], postActions: [ExecutionAction] = [] ) { self.config = config self.customArchiveName = customArchiveName self.revealArchiveInOrganizer = revealArchiveInOrganizer self.preActions = preActions self.postActions = postActions } } public struct BuildTarget: Equatable, Hashable { public var target: TestableTargetReference public var buildTypes: [BuildType] public init(target: TestableTargetReference, buildTypes: [BuildType] = BuildType.all) { self.target = target self.buildTypes = buildTypes } } } extension Scheme: PathContainer { static var pathProperties: [PathProperty] { [ .dictionary([ .object("test", Test.pathProperties), ]), ] } } protocol BuildAction: Equatable { var config: String? { get } } extension Scheme.ExecutionAction: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { script = try jsonDictionary.json(atKeyPath: "script") name = jsonDictionary.json(atKeyPath: "name") ?? "Run Script" settingsTarget = jsonDictionary.json(atKeyPath: "settingsTarget") shell = jsonDictionary.json(atKeyPath: "shell") } } extension Scheme.ExecutionAction: JSONEncodable { public func toJSONValue() -> Any { [ "script": script, "name": name, "settingsTarget": settingsTarget, "shell": shell ] } } extension Scheme.SimulateLocation: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { allow = try jsonDictionary.json(atKeyPath: "allow") defaultLocation = jsonDictionary.json(atKeyPath: "defaultLocation") } } extension Scheme.SimulateLocation: JSONEncodable { public func toJSONValue() -> Any { var dict: [String: Any] = [ "allow": allow, ] if let defaultLocation = defaultLocation { dict["defaultLocation"] = defaultLocation } return dict } } extension Scheme.Management: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { shared = jsonDictionary.json(atKeyPath: "shared") ?? Scheme.Management.sharedDefault orderHint = jsonDictionary.json(atKeyPath: "orderHint") isShown = jsonDictionary.json(atKeyPath: "isShown") } } extension Scheme.Management: JSONEncodable { public func toJSONValue() -> Any { var dict: [String: Any?] = [:] if shared != Scheme.Management.sharedDefault { dict["shared"] = shared } if let isShown = isShown { dict["isShown"] = isShown } if let orderHint = orderHint { dict["orderHint"] = orderHint } return dict } } extension Scheme.Run: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { config = jsonDictionary.json(atKeyPath: "config") commandLineArguments = jsonDictionary.json(atKeyPath: "commandLineArguments") ?? [:] preActions = jsonDictionary.json(atKeyPath: "preActions") ?? [] postActions = jsonDictionary.json(atKeyPath: "postActions") ?? [] environmentVariables = try XCScheme.EnvironmentVariable.parseAll(jsonDictionary: jsonDictionary) if let gpuFrameCaptureMode: String = jsonDictionary.json(atKeyPath: "enableGPUFrameCaptureMode") { enableGPUFrameCaptureMode = XCScheme.LaunchAction.GPUFrameCaptureMode.fromJSONValue(gpuFrameCaptureMode) } else { enableGPUFrameCaptureMode = XCScheme.LaunchAction.defaultGPUFrameCaptureMode } // support deprecated gpuValidationMode enum that was removed from XcodeProj if let gpuValidationMode: String = jsonDictionary.json(atKeyPath: "enableGPUValidationMode") { switch gpuValidationMode { case "enabled", "extended": enableGPUValidationMode = true case "disabled": enableGPUValidationMode = false default: enableGPUValidationMode = Scheme.Run.enableGPUValidationModeDefault } } else { enableGPUValidationMode = jsonDictionary.json(atKeyPath: "enableGPUValidationMode") ?? Scheme.Run.enableGPUValidationModeDefault } enableAddressSanitizer = jsonDictionary.json(atKeyPath: "enableAddressSanitizer") ?? Scheme.Run.enableAddressSanitizerDefault enableASanStackUseAfterReturn = jsonDictionary.json(atKeyPath: "enableASanStackUseAfterReturn") ?? Scheme.Run.enableASanStackUseAfterReturnDefault enableThreadSanitizer = jsonDictionary.json(atKeyPath: "enableThreadSanitizer") ?? Scheme.Run.enableThreadSanitizerDefault enableUBSanitizer = jsonDictionary.json(atKeyPath: "enableUBSanitizer") ?? Scheme.Run.enableUBSanitizerDefault disableMainThreadChecker = jsonDictionary.json(atKeyPath: "disableMainThreadChecker") ?? Scheme.Run.disableMainThreadCheckerDefault stopOnEveryMainThreadCheckerIssue = jsonDictionary.json(atKeyPath: "stopOnEveryMainThreadCheckerIssue") ?? Scheme.Run.stopOnEveryMainThreadCheckerIssueDefault disableThreadPerformanceChecker = jsonDictionary.json(atKeyPath: "disableThreadPerformanceChecker") ?? Scheme.Run.disableThreadPerformanceCheckerDefault language = jsonDictionary.json(atKeyPath: "language") region = jsonDictionary.json(atKeyPath: "region") debugEnabled = jsonDictionary.json(atKeyPath: "debugEnabled") ?? Scheme.Run.debugEnabledDefault simulateLocation = jsonDictionary.json(atKeyPath: "simulateLocation") storeKitConfiguration = jsonDictionary.json(atKeyPath: "storeKitConfiguration") executable = jsonDictionary.json(atKeyPath: "executable") // launchAutomaticallySubstyle is defined as a String in XcodeProj but its value is often // an integer. Parse both to be nice. if let int: Int = jsonDictionary.json(atKeyPath: "launchAutomaticallySubstyle") { launchAutomaticallySubstyle = String(int) } else if let string: String = jsonDictionary.json(atKeyPath: "launchAutomaticallySubstyle") { launchAutomaticallySubstyle = string } if let askLaunch: Bool = jsonDictionary.json(atKeyPath: "askForAppToLaunch") { askForAppToLaunch = askLaunch } customLLDBInit = jsonDictionary.json(atKeyPath: "customLLDBInit") macroExpansion = jsonDictionary.json(atKeyPath: "macroExpansion") customWorkingDirectory = jsonDictionary.json(atKeyPath: "customWorkingDirectory") } } extension Scheme.Run: JSONEncodable { public func toJSONValue() -> Any { var dict: [String: Any?] = [ "commandLineArguments": commandLineArguments, "preActions": preActions.map { $0.toJSONValue() }, "postActions": postActions.map { $0.toJSONValue() }, "environmentVariables": environmentVariables.map { $0.toJSONValue() }, "config": config, "language": language, "region": region, "askForAppToLaunch": askForAppToLaunch, "launchAutomaticallySubstyle": launchAutomaticallySubstyle, "executable": executable, "macroExpansion": macroExpansion ] if enableGPUFrameCaptureMode != XCScheme.LaunchAction.defaultGPUFrameCaptureMode { dict["enableGPUFrameCaptureMode"] = enableGPUFrameCaptureMode.toJSONValue() } if enableGPUValidationMode != Scheme.Run.enableGPUValidationModeDefault { dict["enableGPUValidationMode"] = enableGPUValidationMode } if enableAddressSanitizer != Scheme.Run.enableAddressSanitizerDefault { dict["enableAddressSanitizer"] = enableAddressSanitizer } if enableASanStackUseAfterReturn != Scheme.Run.enableASanStackUseAfterReturnDefault { dict["enableASanStackUseAfterReturn"] = enableASanStackUseAfterReturn } if enableThreadSanitizer != Scheme.Run.enableThreadSanitizerDefault { dict["enableThreadSanitizer"] = enableThreadSanitizer } if enableUBSanitizer != Scheme.Run.enableUBSanitizerDefault { dict["enableUBSanitizer"] = enableUBSanitizer } if disableMainThreadChecker != Scheme.Run.disableMainThreadCheckerDefault { dict["disableMainThreadChecker"] = disableMainThreadChecker } if stopOnEveryMainThreadCheckerIssue != Scheme.Run.stopOnEveryMainThreadCheckerIssueDefault { dict["stopOnEveryMainThreadCheckerIssue"] = stopOnEveryMainThreadCheckerIssue } if disableThreadPerformanceChecker != Scheme.Run.disableThreadPerformanceCheckerDefault { dict["disableThreadPerformanceChecker"] = disableThreadPerformanceChecker } if debugEnabled != Scheme.Run.debugEnabledDefault { dict["debugEnabled"] = debugEnabled } if let simulateLocation = simulateLocation { dict["simulateLocation"] = simulateLocation.toJSONValue() } if let storeKitConfiguration = storeKitConfiguration { dict["storeKitConfiguration"] = storeKitConfiguration } if let customLLDBInit = customLLDBInit { dict["customLLDBInit"] = customLLDBInit } if let customWorkingDirectory = customWorkingDirectory { dict["customWorkingDirectory"] = customWorkingDirectory } return dict } } extension Scheme.Test: PathContainer { static var pathProperties: [PathProperty] { [ .object("testPlans", TestPlan.pathProperties), ] } } extension Scheme.Test: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { config = jsonDictionary.json(atKeyPath: "config") gatherCoverageData = jsonDictionary.json(atKeyPath: "gatherCoverageData") ?? Scheme.Test.gatherCoverageDataDefault if let coverages = jsonDictionary["coverageTargets"] as? [Any] { coverageTargets = try coverages.compactMap { target in if let string = target as? String { return try TestableTargetReference(string) } else if let dictionary = target as? JSONDictionary, let target: TestableTargetReference = try? .init(jsonDictionary: dictionary) { return target } else { return nil } } } else { coverageTargets = [] } enableAddressSanitizer = jsonDictionary.json(atKeyPath: "enableAddressSanitizer") ?? Scheme.Test.enableAddressSanitizerDefault enableASanStackUseAfterReturn = jsonDictionary.json(atKeyPath: "enableASanStackUseAfterReturn") ?? Scheme.Test.enableASanStackUseAfterReturnDefault enableThreadSanitizer = jsonDictionary.json(atKeyPath: "enableThreadSanitizer") ?? Scheme.Test.enableThreadSanitizerDefault enableUBSanitizer = jsonDictionary.json(atKeyPath: "enableUBSanitizer") ?? Scheme.Test.enableUBSanitizerDefault disableMainThreadChecker = jsonDictionary.json(atKeyPath: "disableMainThreadChecker") ?? Scheme.Test.disableMainThreadCheckerDefault commandLineArguments = jsonDictionary.json(atKeyPath: "commandLineArguments") ?? [:] if let targets = jsonDictionary["targets"] as? [Any] { self.targets = try targets.compactMap { target in if let string = target as? String { return try TestTarget(targetReference: TestableTargetReference(string)) } else if let dictionary = target as? JSONDictionary { return try TestTarget(jsonDictionary: dictionary) } else { return nil } } } else { targets = [] } preActions = jsonDictionary.json(atKeyPath: "preActions") ?? [] postActions = jsonDictionary.json(atKeyPath: "postActions") ?? [] environmentVariables = try XCScheme.EnvironmentVariable.parseAll(jsonDictionary: jsonDictionary) testPlans = try (jsonDictionary.json(atKeyPath: "testPlans") ?? []).map { try TestPlan(jsonDictionary: $0) } language = jsonDictionary.json(atKeyPath: "language") region = jsonDictionary.json(atKeyPath: "region") debugEnabled = jsonDictionary.json(atKeyPath: "debugEnabled") ?? Scheme.Test.debugEnabledDefault customLLDBInit = jsonDictionary.json(atKeyPath: "customLLDBInit") captureScreenshotsAutomatically = jsonDictionary.json(atKeyPath: "captureScreenshotsAutomatically") ?? Scheme.Test.captureScreenshotsAutomaticallyDefault deleteScreenshotsWhenEachTestSucceeds = jsonDictionary.json(atKeyPath: "deleteScreenshotsWhenEachTestSucceeds") ?? Scheme.Test.deleteScreenshotsWhenEachTestSucceedsDefault macroExpansion = jsonDictionary.json(atKeyPath: "macroExpansion") preferredScreenCaptureFormat = jsonDictionary.json(atKeyPath: "preferredScreenCaptureFormat") ?? Scheme.Test.preferredScreenCaptureFormatDefault } } extension Scheme.Test: JSONEncodable { public func toJSONValue() -> Any { var dict: [String: Any?] = [ "commandLineArguments": commandLineArguments, "targets": targets.map { $0.toJSONValue() }, "preActions": preActions.map { $0.toJSONValue() }, "postActions": postActions.map { $0.toJSONValue() }, "environmentVariables": environmentVariables.map { $0.toJSONValue() }, "testPlans": testPlans.map { $0.toJSONValue() }, "config": config, "language": language, "region": region, "coverageTargets": coverageTargets.map { $0.reference }, "macroExpansion": macroExpansion ] if gatherCoverageData != Scheme.Test.gatherCoverageDataDefault { dict["gatherCoverageData"] = gatherCoverageData } if enableAddressSanitizer != Scheme.Test.enableAddressSanitizerDefault { dict["enableAddressSanitizer"] = enableAddressSanitizer } if enableASanStackUseAfterReturn != Scheme.Test.enableASanStackUseAfterReturnDefault { dict["enableASanStackUseAfterReturn"] = enableASanStackUseAfterReturn } if enableThreadSanitizer != Scheme.Test.enableThreadSanitizerDefault { dict["enableThreadSanitizer"] = enableThreadSanitizer } if enableUBSanitizer != Scheme.Test.enableUBSanitizerDefault { dict["enableUBSanitizer"] = enableUBSanitizer } if disableMainThreadChecker != Scheme.Test.disableMainThreadCheckerDefault { dict["disableMainThreadChecker"] = disableMainThreadChecker } if debugEnabled != Scheme.Run.debugEnabledDefault { dict["debugEnabled"] = debugEnabled } if let customLLDBInit = customLLDBInit { dict["customLLDBInit"] = customLLDBInit } if captureScreenshotsAutomatically != Scheme.Test.captureScreenshotsAutomaticallyDefault { dict["captureScreenshotsAutomatically"] = captureScreenshotsAutomatically } if deleteScreenshotsWhenEachTestSucceeds != Scheme.Test.deleteScreenshotsWhenEachTestSucceedsDefault { dict["deleteScreenshotsWhenEachTestSucceeds"] = deleteScreenshotsWhenEachTestSucceeds } if preferredScreenCaptureFormat != Scheme.Test.preferredScreenCaptureFormatDefault { dict["preferredScreenCaptureFormat"] = preferredScreenCaptureFormat.toJSONValue() } return dict } } extension Scheme.Test.TestTarget: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { if let name: String = jsonDictionary.json(atKeyPath: "name") { targetReference = try TestableTargetReference(name) } else if let local: String = jsonDictionary.json(atKeyPath: "local") { self.targetReference = TestableTargetReference.local(local) } else if let project: String = jsonDictionary.json(atKeyPath: "project") { self.targetReference = TestableTargetReference.project(project) } else if let package: String = jsonDictionary.json(atKeyPath: "package") { self.targetReference = TestableTargetReference.package(package) } else { self.targetReference = try jsonDictionary.json(atKeyPath: "target") } randomExecutionOrder = jsonDictionary.json(atKeyPath: "randomExecutionOrder") ?? Scheme.Test.TestTarget.randomExecutionOrderDefault parallelizable = jsonDictionary.json(atKeyPath: "parallelizable") ?? Scheme.Test.TestTarget.parallelizableDefault location = jsonDictionary.json(atKeyPath: "location") ?? nil skipped = jsonDictionary.json(atKeyPath: "skipped") ?? false skippedTests = jsonDictionary.json(atKeyPath: "skippedTests") ?? [] selectedTests = jsonDictionary.json(atKeyPath: "selectedTests") ?? [] } } extension Scheme.Test.TestTarget: JSONEncodable { public func toJSONValue() -> Any { if randomExecutionOrder == Scheme.Test.TestTarget.randomExecutionOrderDefault, parallelizable == Scheme.Test.TestTarget.parallelizableDefault { return targetReference.reference } var dict: JSONDictionary = [ "name": targetReference.reference, ] if randomExecutionOrder != Scheme.Test.TestTarget.randomExecutionOrderDefault { dict["randomExecutionOrder"] = randomExecutionOrder } if parallelizable != Scheme.Test.TestTarget.parallelizableDefault { dict["parallelizable"] = parallelizable } if let location = location { dict["location"] = location } if skipped { dict["skipped"] = skipped } return dict } } extension Scheme.Profile: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { config = jsonDictionary.json(atKeyPath: "config") commandLineArguments = jsonDictionary.json(atKeyPath: "commandLineArguments") ?? [:] preActions = jsonDictionary.json(atKeyPath: "preActions") ?? [] postActions = jsonDictionary.json(atKeyPath: "postActions") ?? [] environmentVariables = try XCScheme.EnvironmentVariable.parseAll(jsonDictionary: jsonDictionary) if let askLaunch: Bool = jsonDictionary.json(atKeyPath: "askForAppToLaunch") { askForAppToLaunch = askLaunch } } } extension Scheme.Profile: JSONEncodable { public func toJSONValue() -> Any { [ "commandLineArguments": commandLineArguments, "preActions": preActions.map { $0.toJSONValue() }, "postActions": postActions.map { $0.toJSONValue() }, "environmentVariables": environmentVariables.map { $0.toJSONValue() }, "config": config, "askForAppToLaunch": askForAppToLaunch, ] as [String: Any?] } } extension Scheme.Analyze: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { config = jsonDictionary.json(atKeyPath: "config") } } extension Scheme.Analyze: JSONEncodable { public func toJSONValue() -> Any { [ "config": config, ] } } extension Scheme.Archive: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { config = jsonDictionary.json(atKeyPath: "config") customArchiveName = jsonDictionary.json(atKeyPath: "customArchiveName") revealArchiveInOrganizer = jsonDictionary.json(atKeyPath: "revealArchiveInOrganizer") ?? Scheme.Archive.revealArchiveInOrganizerDefault preActions = jsonDictionary.json(atKeyPath: "preActions") ?? [] postActions = jsonDictionary.json(atKeyPath: "postActions") ?? [] } } extension Scheme.Archive: JSONEncodable { public func toJSONValue() -> Any { var dict: [String: Any?] = [ "preActions": preActions.map { $0.toJSONValue() }, "postActions": postActions.map { $0.toJSONValue() }, "config": config, "customArchiveName": customArchiveName, ] if revealArchiveInOrganizer != Scheme.Archive.revealArchiveInOrganizerDefault { dict["revealArchiveInOrganizer"] = revealArchiveInOrganizer } return dict } } extension Scheme: NamedJSONDictionaryConvertible { public init(name: String, jsonDictionary: JSONDictionary) throws { self.name = name build = try jsonDictionary.json(atKeyPath: "build") run = jsonDictionary.json(atKeyPath: "run") test = jsonDictionary.json(atKeyPath: "test") analyze = jsonDictionary.json(atKeyPath: "analyze") profile = jsonDictionary.json(atKeyPath: "profile") archive = jsonDictionary.json(atKeyPath: "archive") management = jsonDictionary.json(atKeyPath: "management") } } extension Scheme: JSONEncodable { public func toJSONValue() -> Any { [ "build": build.toJSONValue(), "run": run?.toJSONValue(), "test": test?.toJSONValue(), "analyze": analyze?.toJSONValue(), "profile": profile?.toJSONValue(), "archive": archive?.toJSONValue(), "management": management?.toJSONValue(), ] as [String: Any?] } } extension Scheme.Build: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { let targetDictionary: JSONDictionary = try jsonDictionary.json(atKeyPath: "targets") var targets: [Scheme.BuildTarget] = [] for (targetRepr, possibleBuildTypes) in targetDictionary { let buildTypes: [BuildType] if let string = possibleBuildTypes as? String { switch string { case "all": buildTypes = BuildType.all case "none": buildTypes = [] case "testing": buildTypes = [.testing, .analyzing] case "indexing": buildTypes = [.testing, .analyzing, .archiving] default: buildTypes = BuildType.all } } else if let enabledDictionary = possibleBuildTypes as? [String: Bool] { buildTypes = enabledDictionary.filter { $0.value }.compactMap { BuildType.from(jsonValue: $0.key) } } else if let array = possibleBuildTypes as? [String] { buildTypes = array.compactMap(BuildType.from) } else { buildTypes = BuildType.all } let target = try TestableTargetReference(targetRepr) targets.append(Scheme.BuildTarget(target: target, buildTypes: buildTypes)) } self.targets = targets.sorted { $0.target.name < $1.target.name } preActions = try jsonDictionary.json(atKeyPath: "preActions")?.map(Scheme.ExecutionAction.init) ?? [] postActions = try jsonDictionary.json(atKeyPath: "postActions")?.map(Scheme.ExecutionAction.init) ?? [] parallelizeBuild = jsonDictionary.json(atKeyPath: "parallelizeBuild") ?? Scheme.Build.parallelizeBuildDefault buildImplicitDependencies = jsonDictionary.json(atKeyPath: "buildImplicitDependencies") ?? Scheme.Build.buildImplicitDependenciesDefault runPostActionsOnFailure = jsonDictionary.json(atKeyPath: "runPostActionsOnFailure") ?? Scheme.Build.runPostActionsOnFailureDefault } } extension Scheme.Build: JSONEncodable { public func toJSONValue() -> Any { let targetPairs = targets.map { ($0.target.reference, $0.buildTypes.map { $0.toJSONValue() }) } var dict: JSONDictionary = [ "targets": Dictionary(uniqueKeysWithValues: targetPairs), "preActions": preActions.map { $0.toJSONValue() }, "postActions": postActions.map { $0.toJSONValue() }, ] if parallelizeBuild != Scheme.Build.parallelizeBuildDefault { dict["parallelizeBuild"] = parallelizeBuild } if buildImplicitDependencies != Scheme.Build.buildImplicitDependenciesDefault { dict["buildImplicitDependencies"] = buildImplicitDependencies } if runPostActionsOnFailure != Scheme.Build.runPostActionsOnFailureDefault { dict["runPostActionsOnFailure"] = runPostActionsOnFailure } return dict } } extension BuildType: JSONUtilities.JSONPrimitiveConvertible { public typealias JSONType = String public static func from(jsonValue: String) -> BuildType? { switch jsonValue { case "test", "testing": return .testing case "profile", "profiling": return .profiling case "run", "running": return .running case "archive", "archiving": return .archiving case "analyze", "analyzing": return .analyzing default: return nil } } public static var all: [BuildType] { [.running, .testing, .profiling, .analyzing, .archiving] } } extension BuildType: JSONEncodable { public func toJSONValue() -> Any { switch self { case .testing: return "testing" case .profiling: return "profiling" case .running: return "running" case .archiving: return "archiving" case .analyzing: return "analyzing" } } } extension XCScheme.EnvironmentVariable: JSONUtilities.JSONObjectConvertible { public static let enabledDefault = true private static func parseValue(_ value: Any) -> String { if let bool = value as? Bool { return bool ? "YES" : "NO" } else { return String(describing: value) } } public init(jsonDictionary: JSONDictionary) throws { let value: String if let jsonValue = jsonDictionary["value"] { value = XCScheme.EnvironmentVariable.parseValue(jsonValue) } else { // will throw error value = try jsonDictionary.json(atKeyPath: "value") } let variable: String = try jsonDictionary.json(atKeyPath: "variable") let enabled: Bool = jsonDictionary.json(atKeyPath: "isEnabled") ?? XCScheme.EnvironmentVariable.enabledDefault self.init(variable: variable, value: value, enabled: enabled) } static func parseAll(jsonDictionary: JSONDictionary) throws -> [XCScheme.EnvironmentVariable] { if let variablesDictionary: [String: Any] = jsonDictionary.json(atKeyPath: "environmentVariables") { return variablesDictionary.mapValues(parseValue) .map { XCScheme.EnvironmentVariable(variable: $0.key, value: $0.value, enabled: true) } .sorted { $0.variable < $1.variable } } else if let variablesArray: [JSONDictionary] = jsonDictionary.json(atKeyPath: "environmentVariables") { return try variablesArray.map(XCScheme.EnvironmentVariable.init) } else { return [] } } } extension XCScheme.EnvironmentVariable: JSONEncodable { public func toJSONValue() -> Any { var dict: [String: Any] = [ "variable": variable, "value": value, ] if enabled != XCScheme.EnvironmentVariable.enabledDefault { dict["isEnabled"] = enabled } return dict } } extension XCScheme.LaunchAction.GPUFrameCaptureMode: JSONEncodable { public func toJSONValue() -> Any { switch self { case .autoEnabled: return "autoEnabled" case .metal: return "metal" case .openGL: return "openGL" case .disabled: return "disabled" } } static func fromJSONValue(_ string: String) -> XCScheme.LaunchAction.GPUFrameCaptureMode { switch string { case "autoEnabled": return .autoEnabled case "metal": return .metal case "openGL": return .openGL case "disabled": return .disabled default: fatalError("Invalid enableGPUFrameCaptureMode value. Valid values are: autoEnabled, metal, openGL, disabled") } } } extension XCScheme.TestAction.ScreenCaptureFormat: JSONEncodable { public func toJSONValue() -> Any { rawValue } } ================================================ FILE: Sources/ProjectSpec/Settings.swift ================================================ import Foundation import JSONUtilities import PathKit import XcodeProj public struct Settings: Equatable, JSONObjectConvertible, CustomStringConvertible { public var buildSettings: BuildSettings public var configSettings: [String: Settings] public var groups: [String] public init(buildSettings: BuildSettings = [:], configSettings: [String: Settings] = [:], groups: [String] = []) { self.buildSettings = buildSettings self.configSettings = configSettings self.groups = groups } public static let empty: Settings = Settings(buildSettings: [:]) public init(jsonDictionary: JSONDictionary) throws { if jsonDictionary["configs"] != nil || jsonDictionary["groups"] != nil || jsonDictionary["base"] != nil { groups = jsonDictionary.json(atKeyPath: "groups") ?? jsonDictionary.json(atKeyPath: "presets") ?? [] let buildSettingsDictionary: JSONDictionary = jsonDictionary.json(atKeyPath: "base") ?? [:] buildSettings = buildSettingsDictionary.mapValues { BuildSetting(any: $0) } self.configSettings = try Self.extractValidConfigs(from: jsonDictionary) } else { buildSettings = jsonDictionary.mapValues { BuildSetting(any: $0) } configSettings = [:] groups = [] } } /// Extracts and validates the `configs` mapping from the given JSON dictionary. /// - Parameter jsonDictionary: The JSON dictionary to extract `configs` from. /// - Returns: A dictionary mapping configuration names to `Settings` objects. private static func extractValidConfigs(from jsonDictionary: JSONDictionary) throws -> [String: Settings] { guard let configSettings = jsonDictionary["configs"] as? JSONDictionary else { return [:] } let invalidConfigKeys = Set( configSettings.filter { !($0.value is JSONDictionary) } .map(\.key) ) guard invalidConfigKeys.isEmpty else { throw SpecParsingError.invalidConfigsMappingFormat(keys: invalidConfigKeys) } return try jsonDictionary.json(atKeyPath: "configs") } public static func == (lhs: Settings, rhs: Settings) -> Bool { lhs.buildSettings == rhs.buildSettings && lhs.configSettings == rhs.configSettings && lhs.groups == rhs.groups } public var description: String { var string: String = "" if !buildSettings.isEmpty { let buildSettingDescription = buildSettings.map { "\($0) = \($1)" }.joined(separator: "\n") if !configSettings.isEmpty || !groups.isEmpty { string += "base:\n " + buildSettingDescription.replacingOccurrences(of: "(.)\n", with: "$1\n ", options: .regularExpression, range: nil) } else { string += buildSettingDescription } } if !configSettings.isEmpty { if !string.isEmpty { string += "\n" } for (config, buildSettings) in configSettings { if !buildSettings.description.isEmpty { string += "configs:\n" string += " \(config):\n " + buildSettings.description.replacingOccurrences(of: "(.)\n", with: "$1\n ", options: .regularExpression, range: nil) } } } if !groups.isEmpty { if !string.isEmpty { string += "\n" } string += "groups:\n \(groups.joined(separator: "\n "))" } return string } } extension Settings: ExpressibleByDictionaryLiteral { public init(dictionaryLiteral elements: (String, BuildSetting)...) { var buildSettings: BuildSettings = [:] elements.forEach { buildSettings[$0.0] = $0.1 } self.init(buildSettings: buildSettings) } } extension Dictionary where Key == String { public func merged(_ dictionary: [Key: Value]) -> [Key: Value] { var mergedDictionary = self mergedDictionary.merge(dictionary) return mergedDictionary } public mutating func merge(_ dictionary: [Key: Value]) { for (key, value) in dictionary { self[key] = value } } } public func += (lhs: inout BuildSettings, rhs: BuildSettings?) { guard let rhs = rhs else { return } lhs.merge(rhs) } extension BuildSetting { public init(any value: Any) { if let array = value as? [String] { self = .array(array) } else if let bool = value as? Bool { self = .init(booleanLiteral: bool) } else { self = .string("\(value)") } } public func toAny() -> Any { switch self { case let .string(value): return value case let .array(value): return value } } } extension ProjectAttribute { public init(any value: Any) { if let array = value as? [String] { self = .array(array) } else if let object = value as? PBXObject { self = .targetReference(object) } else { self = .string("\(value)") } } } extension Settings: JSONEncodable { public func toJSONValue() -> Any { let anySettings = buildSettings.mapValues { $0.toAny() } if groups.count > 0 || configSettings.count > 0 { return [ "base": anySettings, "groups": groups, "configs": configSettings.mapValues { $0.toJSONValue() }, ] as [String : Any] } return anySettings } } ================================================ FILE: Sources/ProjectSpec/SourceType.swift ================================================ // // File.swift // // // Created by Yonas Kolb on 1/5/20. // import Foundation public enum SourceType: String { case group case file case folder case syncedFolder } ================================================ FILE: Sources/ProjectSpec/SpecFile.swift ================================================ import Foundation import JSONUtilities import PathKit import Yams public struct SpecFile { /// For the root spec, this is the folder containing the SpecFile. For subSpecs this is the path /// to the folder of the parent spec that is including this SpecFile. public let basePath: Path public let jsonDictionary: JSONDictionary public let subSpecs: [SpecFile] /// The relative path to use when resolving paths in the json dictionary. Is an empty path when /// included with relativePaths disabled. private let relativePath: Path /// The path to the file relative to the basePath. private let filePath: Path fileprivate struct Include { let path: Path let relativePaths: Bool let enable: Bool static let defaultRelativePaths = true static let defaultEnable = true init?(any: Any) { if let string = any as? String { path = Path(string) relativePaths = Include.defaultRelativePaths enable = Include.defaultEnable } else if let dictionary = any as? JSONDictionary, let path = dictionary["path"] as? String { self.path = Path(path) relativePaths = Self.resolveBoolean(dictionary, key: "relativePaths") ?? Include.defaultRelativePaths enable = Self.resolveBoolean(dictionary, key: "enable") ?? Include.defaultEnable } else { return nil } } static func parse(json: Any?) -> [Include] { if let array = json as? [Any] { return array.compactMap(Include.init) } else if let object = json, let include = Include(any: object) { return [include] } else { return [] } } private static func resolveBoolean(_ dictionary: [String: Any], key: String) -> Bool? { dictionary[key] as? Bool ?? (dictionary[key] as? NSString)?.boolValue } } /// Create a SpecFile for a Project /// - Parameters: /// - path: The absolute path to the spec file /// - projectRoot: The root of the project to use as the base path. When nil, uses the parent /// of the path. public init(path: Path, projectRoot: Path? = nil, variables: [String: String] = [:]) throws { let basePath = projectRoot ?? path.parent() let filePath = try path.relativePath(from: basePath) var cachedSpecFiles: [Path: SpecFile] = [:] try self.init(filePath: filePath, basePath: basePath, cachedSpecFiles: &cachedSpecFiles, variables: variables) } /// Memberwise initializer for SpecFile public init(filePath: Path, jsonDictionary: JSONDictionary, basePath: Path = "", relativePath: Path = "", subSpecs: [SpecFile] = []) { self.basePath = basePath self.relativePath = relativePath self.jsonDictionary = jsonDictionary self.subSpecs = subSpecs self.filePath = filePath } private init(include: Include, basePath: Path, relativePath: Path, cachedSpecFiles: inout [Path: SpecFile], variables: [String: String]) throws { let basePath = include.relativePaths ? (basePath + relativePath) : basePath let relativePath = include.relativePaths ? include.path.parent() : Path() try self.init(filePath: include.path, basePath: basePath, cachedSpecFiles: &cachedSpecFiles, variables: variables, relativePath: relativePath) } private init(filePath: Path, basePath: Path, cachedSpecFiles: inout [Path: SpecFile], variables: [String: String], relativePath: Path = "") throws { let path = basePath + filePath if let specFile = cachedSpecFiles[path] { self = specFile return } let jsonDictionary = try SpecFile.loadDictionary(path: path).expand(variables: variables) let includes = Include.parse(json: jsonDictionary["include"]) let subSpecs: [SpecFile] = try includes .filter(\.enable) .map { include in return try SpecFile(include: include, basePath: basePath, relativePath: relativePath, cachedSpecFiles: &cachedSpecFiles, variables: variables) } self.init(filePath: filePath, jsonDictionary: jsonDictionary, basePath: basePath, relativePath: relativePath, subSpecs: subSpecs) cachedSpecFiles[path] = self } static func loadDictionary(path: Path) throws -> JSONDictionary { // Depending on the extension we will either load the file as YAML or JSON if path.extension?.lowercased() == "json" { let data: Data = try path.read() let jsonData = try JSONSerialization.jsonObject(with: data, options: .allowFragments) guard let jsonDictionary = jsonData as? [String: Any] else { fatalError("Invalid JSON at path \(path)") } return jsonDictionary } else { return try loadYamlDictionary(path: path) } } public func resolvedDictionary() -> JSONDictionary { resolvedDictionaryWithUniqueTargets() } private func resolvedDictionaryWithUniqueTargets() -> JSONDictionary { var cachedSpecFiles: [Path: SpecFile] = [:] let resolvedSpec = resolvingPaths(cachedSpecFiles: &cachedSpecFiles) var mergedSpecPaths = Set() return resolvedSpec.mergedDictionary(set: &mergedSpecPaths) } private func mergedDictionary(set mergedSpecPaths: inout Set) -> JSONDictionary { let path = basePath + filePath guard mergedSpecPaths.insert(path).inserted else { return [:] } return jsonDictionary.merged(onto: subSpecs .map { $0.mergedDictionary(set: &mergedSpecPaths) } .reduce([:]) { $1.merged(onto: $0) }) } private func resolvingPaths(cachedSpecFiles: inout [Path: SpecFile], relativeTo basePath: Path = Path()) -> SpecFile { let path = basePath + filePath if let cachedSpecFile = cachedSpecFiles[path] { return cachedSpecFile } let relativePath = (basePath + self.relativePath).normalize() guard relativePath != Path() else { return self } let jsonDictionary = Project.pathProperties.resolvingPaths(in: self.jsonDictionary, relativeTo: relativePath) let specFile = SpecFile( filePath: filePath, jsonDictionary: jsonDictionary, basePath: self.basePath, relativePath: self.relativePath, subSpecs: subSpecs.map { $0.resolvingPaths(cachedSpecFiles: &cachedSpecFiles, relativeTo: relativePath) } ) cachedSpecFiles[path] = specFile return specFile } } extension Dictionary where Key == String, Value: Any { func merged(onto other: [Key: Value]) -> [Key: Value] { var merged = other for (key, value) in self { if key.hasSuffix(":REPLACE") { let newKey = key[key.startIndex.. JSONDictionary { var expanded: JSONDictionary = self if !variables.isEmpty { for (key, value) in self { let newKey = expand(variables: variables, in: key) if newKey != key { expanded.removeValue(forKey: key) } expanded[newKey] = expand(variables: variables, in: value) } } return expanded } private func expand(variables: [String: String], in value: Any) -> Any { switch value { case let dictionary as JSONDictionary: return dictionary.expand(variables: variables) case let string as String: return expand(variables: variables, in: string) case let array as [JSONDictionary]: return array.map { $0.expand(variables: variables) } case let array as [String]: return array.map { self.expand(variables: variables, in: $0) } case let anyArray as [Any]: return anyArray.map { self.expand(variables: variables, in: $0) } default: return value } } private func expand(variables: [String: String], in string: String) -> String { var result = string var index = result.startIndex while index < result.endIndex { let substring = result[index...] if substring.count < 4 { // We need at least 4 characters: ${x} index = result.endIndex } else if substring[index] == "$" && substring[substring.index(index, offsetBy: 1)] == "{" && substring[substring.index(index, offsetBy: 2)] != "}" { // This is the start of a variable expansion... let variableStart = index if let variableEnd = substring.firstIndex(of: "}") { // ...with an end let nameStart = result.index(variableStart, offsetBy: 2) // Skipping ${ let nameEnd = result.index(variableEnd, offsetBy: -1) // Removing trailing } let name = result[nameStart...nameEnd] if let value = variables[String(name)] { result.replaceSubrange(variableStart...variableEnd, with: value) index = result.index(index, offsetBy: value.count) } else { // Skip this whole variable for which we don't have a value index = result.index(after: variableEnd) } } else { // Malformed variable, skip the whole string index = result.endIndex } } else { // Move on to the next $ and start again or finish early index = result[result.index(after: index)...].firstIndex(of: "$") ?? result.endIndex } } return result } } ================================================ FILE: Sources/ProjectSpec/SpecLoader.swift ================================================ import Foundation import JSONUtilities import PathKit import XcodeProj import Yams import Version public class SpecLoader { var project: Project! public private(set) var projectDictionary: [String: Any]? let version: Version public init(version: Version) { self.version = version } public func loadProject(path: Path, projectRoot: Path? = nil, variables: [String: String] = [:]) throws -> Project { let projectRoot = projectRoot?.absolute() let spec = try SpecFile(path: path, projectRoot: projectRoot, variables: variables) let resolvedDictionary = spec.resolvedDictionary() let project = try Project(basePath: projectRoot ?? spec.basePath, jsonDictionary: resolvedDictionary) self.project = project projectDictionary = resolvedDictionary return project } public func validateProjectDictionaryWarnings() throws { try projectDictionary?.validateWarnings() } public func generateCacheFile() throws -> CacheFile? { guard let projectDictionary = projectDictionary, let project = project else { return nil } return try CacheFile( version: version, projectDictionary: projectDictionary, project: project ) } } private extension Dictionary where Key == String, Value: Any { func validateWarnings() throws { let errors: [SpecValidationError.ValidationError] = [] if !errors.isEmpty { throw SpecValidationError(errors: errors) } } func hasValueContaining(_ needle: String) -> Bool { values.contains { value in switch value { case let dictionary as JSONDictionary: return dictionary.hasValueContaining(needle) case let string as String: return string.contains(needle) case let array as [JSONDictionary]: return array.contains { $0.hasValueContaining(needle) } case let array as [String]: return array.contains { $0.contains(needle) } default: return false } } } } ================================================ FILE: Sources/ProjectSpec/SpecOptions.swift ================================================ import Foundation import JSONUtilities import Version public struct SpecOptions: Equatable { public static let settingPresetsDefault = SettingPresets.all public static let createIntermediateGroupsDefault = false public static let transitivelyLinkDependenciesDefault = false public static let groupSortPositionDefault = GroupSortPosition.bottom public static let generateEmptyDirectoriesDefault = false public static let findCarthageFrameworksDefault = false public static let useBaseInternationalizationDefault = true public static let schemePathPrefixDefault = "../../" public var minimumXcodeGenVersion: Version? public var carthageBuildPath: String? public var carthageExecutablePath: String? public var createIntermediateGroups: Bool public var bundleIdPrefix: String? public var settingPresets: SettingPresets public var disabledValidations: [ValidationType] public var developmentLanguage: String? public var usesTabs: Bool? public var tabWidth: UInt? public var indentWidth: UInt? public var xcodeVersion: String? public var projectFormat: String? public var deploymentTarget: DeploymentTarget public var defaultConfig: String? public var transitivelyLinkDependencies: Bool public var groupSortPosition: GroupSortPosition public var groupOrdering: [GroupOrdering] public var fileTypes: [String: FileType] public var generateEmptyDirectories: Bool public var findCarthageFrameworks: Bool public var localPackagesGroup: String? public var preGenCommand: String? public var postGenCommand: String? public var useBaseInternationalization: Bool public var schemePathPrefix: String public var defaultSourceDirectoryType: SourceType? public enum ValidationType: String { case missingConfigs case missingConfigFiles case missingTestPlans } public enum SettingPresets: String { case all case none case project case targets public var applyTarget: Bool { switch self { case .all, .targets: return true default: return false } } public var applyProject: Bool { switch self { case .all, .project: return true default: return false } } } /// Where groups are sorted in relation to other files public enum GroupSortPosition: String { /// groups are at the top case top /// groups are at the bottom case bottom /// groups are sorted with the rest of the files case none } public init( minimumXcodeGenVersion: Version? = nil, carthageBuildPath: String? = nil, carthageExecutablePath: String? = nil, createIntermediateGroups: Bool = createIntermediateGroupsDefault, bundleIdPrefix: String? = nil, settingPresets: SettingPresets = settingPresetsDefault, developmentLanguage: String? = nil, indentWidth: UInt? = nil, tabWidth: UInt? = nil, usesTabs: Bool? = nil, xcodeVersion: String? = nil, projectFormat: String? = nil, deploymentTarget: DeploymentTarget = .init(), disabledValidations: [ValidationType] = [], defaultConfig: String? = nil, transitivelyLinkDependencies: Bool = transitivelyLinkDependenciesDefault, groupSortPosition: GroupSortPosition = groupSortPositionDefault, groupOrdering: [GroupOrdering] = [], fileTypes: [String: FileType] = [:], generateEmptyDirectories: Bool = generateEmptyDirectoriesDefault, findCarthageFrameworks: Bool = findCarthageFrameworksDefault, localPackagesGroup: String? = nil, preGenCommand: String? = nil, postGenCommand: String? = nil, useBaseInternationalization: Bool = useBaseInternationalizationDefault, schemePathPrefix: String = schemePathPrefixDefault, defaultSourceDirectoryType: SourceType? = nil ) { self.minimumXcodeGenVersion = minimumXcodeGenVersion self.carthageBuildPath = carthageBuildPath self.carthageExecutablePath = carthageExecutablePath self.createIntermediateGroups = createIntermediateGroups self.bundleIdPrefix = bundleIdPrefix self.settingPresets = settingPresets self.developmentLanguage = developmentLanguage self.tabWidth = tabWidth self.indentWidth = indentWidth self.usesTabs = usesTabs self.xcodeVersion = xcodeVersion self.projectFormat = projectFormat self.deploymentTarget = deploymentTarget self.disabledValidations = disabledValidations self.defaultConfig = defaultConfig self.transitivelyLinkDependencies = transitivelyLinkDependencies self.groupSortPosition = groupSortPosition self.groupOrdering = groupOrdering self.fileTypes = fileTypes self.generateEmptyDirectories = generateEmptyDirectories self.findCarthageFrameworks = findCarthageFrameworks self.localPackagesGroup = localPackagesGroup self.preGenCommand = preGenCommand self.postGenCommand = postGenCommand self.useBaseInternationalization = useBaseInternationalization self.schemePathPrefix = schemePathPrefix self.defaultSourceDirectoryType = defaultSourceDirectoryType } } extension SpecOptions: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { if let string: String = jsonDictionary.json(atKeyPath: "minimumXcodeGenVersion") { minimumXcodeGenVersion = try Version.parse(string) } carthageBuildPath = jsonDictionary.json(atKeyPath: "carthageBuildPath") carthageExecutablePath = jsonDictionary.json(atKeyPath: "carthageExecutablePath") bundleIdPrefix = jsonDictionary.json(atKeyPath: "bundleIdPrefix") settingPresets = jsonDictionary.json(atKeyPath: "settingPresets") ?? SpecOptions.settingPresetsDefault createIntermediateGroups = jsonDictionary.json(atKeyPath: "createIntermediateGroups") ?? SpecOptions.createIntermediateGroupsDefault developmentLanguage = jsonDictionary.json(atKeyPath: "developmentLanguage") usesTabs = jsonDictionary.json(atKeyPath: "usesTabs") xcodeVersion = jsonDictionary.json(atKeyPath: "xcodeVersion") projectFormat = jsonDictionary.json(atKeyPath: "projectFormat") indentWidth = (jsonDictionary.json(atKeyPath: "indentWidth") as Int?).flatMap(UInt.init) tabWidth = (jsonDictionary.json(atKeyPath: "tabWidth") as Int?).flatMap(UInt.init) deploymentTarget = jsonDictionary.json(atKeyPath: "deploymentTarget") ?? DeploymentTarget() disabledValidations = jsonDictionary.json(atKeyPath: "disabledValidations") ?? [] defaultConfig = jsonDictionary.json(atKeyPath: "defaultConfig") transitivelyLinkDependencies = jsonDictionary.json(atKeyPath: "transitivelyLinkDependencies") ?? SpecOptions.transitivelyLinkDependenciesDefault groupSortPosition = jsonDictionary.json(atKeyPath: "groupSortPosition") ?? SpecOptions.groupSortPositionDefault groupOrdering = jsonDictionary.json(atKeyPath: "groupOrdering") ?? [] generateEmptyDirectories = jsonDictionary.json(atKeyPath: "generateEmptyDirectories") ?? SpecOptions.generateEmptyDirectoriesDefault findCarthageFrameworks = jsonDictionary.json(atKeyPath: "findCarthageFrameworks") ?? SpecOptions.findCarthageFrameworksDefault localPackagesGroup = jsonDictionary.json(atKeyPath: "localPackagesGroup") preGenCommand = jsonDictionary.json(atKeyPath: "preGenCommand") postGenCommand = jsonDictionary.json(atKeyPath: "postGenCommand") useBaseInternationalization = jsonDictionary.json(atKeyPath: "useBaseInternationalization") ?? SpecOptions.useBaseInternationalizationDefault schemePathPrefix = jsonDictionary.json(atKeyPath: "schemePathPrefix") ?? SpecOptions.schemePathPrefixDefault defaultSourceDirectoryType = jsonDictionary.json(atKeyPath: "defaultSourceDirectoryType") if jsonDictionary["fileTypes"] != nil { fileTypes = try jsonDictionary.json(atKeyPath: "fileTypes") } else { fileTypes = [:] } } } extension SpecOptions: JSONEncodable { public func toJSONValue() -> Any { var dict: [String: Any?] = [ "deploymentTarget": deploymentTarget.toJSONValue(), "transitivelyLinkDependencies": transitivelyLinkDependencies, "groupSortPosition": groupSortPosition.rawValue, "disabledValidations": disabledValidations.map { $0.rawValue }, "minimumXcodeGenVersion": minimumXcodeGenVersion?.description, "carthageBuildPath": carthageBuildPath, "carthageExecutablePath": carthageExecutablePath, "bundleIdPrefix": bundleIdPrefix, "developmentLanguage": developmentLanguage, "usesTabs": usesTabs, "xcodeVersion": xcodeVersion, "projectFormat": projectFormat, "indentWidth": indentWidth.flatMap { Int($0) }, "tabWidth": tabWidth.flatMap { Int($0) }, "defaultConfig": defaultConfig, "localPackagesGroup": localPackagesGroup, "preGenCommand": preGenCommand, "postGenCommand": postGenCommand, "fileTypes": fileTypes.mapValues { $0.toJSONValue() } ] if settingPresets != SpecOptions.settingPresetsDefault { dict["settingPresets"] = settingPresets.rawValue } if createIntermediateGroups != SpecOptions.createIntermediateGroupsDefault { dict["createIntermediateGroups"] = createIntermediateGroups } if generateEmptyDirectories != SpecOptions.generateEmptyDirectoriesDefault { dict["generateEmptyDirectories"] = generateEmptyDirectories } if findCarthageFrameworks != SpecOptions.findCarthageFrameworksDefault { dict["findCarthageFrameworks"] = findCarthageFrameworks } if useBaseInternationalization != SpecOptions.useBaseInternationalizationDefault { dict["useBaseInternationalization"] = useBaseInternationalization } if schemePathPrefix != SpecOptions.schemePathPrefixDefault { dict["schemePathPrefix"] = schemePathPrefix } return dict } } extension SpecOptions: PathContainer { static var pathProperties: [PathProperty] { [ .string("carthageBuildPath"), ] } } ================================================ FILE: Sources/ProjectSpec/SpecParsingError.swift ================================================ import Foundation public enum SpecParsingError: Error, CustomStringConvertible { case unknownTargetType(String) case unknownTargetPlatform(String) case invalidDependency([String: Any]) case unknownPackageRequirement([String: Any]) case invalidSourceBuildPhase(String) case invalidTargetReference(String) case invalidTargetPlatformAsArray case invalidVersion(String) case unknownBreakpointType(String) case unknownBreakpointScope(String) case unknownBreakpointStopOnStyle(String) case unknownBreakpointActionType(String) case unknownBreakpointActionConveyanceType(String) case unknownBreakpointActionSoundName(String) case invalidConfigsMappingFormat(keys: Set) public var description: String { switch self { case let .unknownTargetType(type): return "Unknown Target type: \(type)" case let .unknownTargetPlatform(platform): return "Unknown Target platform: \(platform)" case let .invalidDependency(dependency): return "Unknown Target dependency: \(dependency)" case let .invalidSourceBuildPhase(error): return "Invalid Source Build Phase: \(error)" case let .invalidTargetReference(targetReference): return "Invalid Target Reference Syntax: \(targetReference)" case .invalidTargetPlatformAsArray: return "Invalid Target platform: Array not allowed with supported destinations" case let .invalidVersion(version): return "Invalid version: \(version)" case let .unknownPackageRequirement(package): return "Unknown package requirement: \(package)" case let .unknownBreakpointType(type): return "Unknown Breakpoint type: \(type)" case let .unknownBreakpointScope(scope): return "Unknown Breakpoint scope: \(scope)" case let .unknownBreakpointStopOnStyle(stopOnStyle): return "Unknown Breakpoint stopOnStyle: \(stopOnStyle)" case let .unknownBreakpointActionType(type): return "Unknown Breakpoint Action type: \(type)" case let .unknownBreakpointActionConveyanceType(type): return "Unknown Breakpoint Action conveyance type: \(type)" case let .unknownBreakpointActionSoundName(name): return "Unknown Breakpoint Action sound name: \(name)" case let .invalidConfigsMappingFormat(keys): return "Invalid format: The value for \"\(keys.sorted().joined(separator: ", "))\" in `configs` must be mapping format" } } } ================================================ FILE: Sources/ProjectSpec/SpecValidation.swift ================================================ import Foundation import JSONUtilities import PathKit import Version extension Project { public func validate() throws { var errors: [SpecValidationError.ValidationError] = [] func validateSettings(_ settings: Settings) -> [SpecValidationError.ValidationError] { var errors: [SpecValidationError.ValidationError] = [] for group in settings.groups { if let settings = settingGroups[group] { errors += validateSettings(settings) } else { errors.append(.invalidSettingsGroup(group)) } } for config in settings.configSettings.keys { if !configs.contains(where: { $0.name.lowercased().contains(config.lowercased()) }), !options.disabledValidations.contains(.missingConfigs) { errors.append(.invalidBuildSettingConfig(config)) } } if settings.buildSettings.count == configs.count { var allConfigs = true outerLoop: for buildSetting in settings.buildSettings.keys { var isConfig = false for config in configs { if config.name.lowercased().contains(buildSetting.lowercased()) { isConfig = true break } } if !isConfig { allConfigs = false break outerLoop } } if allConfigs { errors.append(.invalidPerConfigSettings) } } return errors } errors += validateSettings(settings) for fileGroup in fileGroups { if !(basePath + fileGroup).exists { errors.append(.invalidFileGroup(fileGroup)) } } for (name, package) in packages { if case let .local(path, _, _) = package, !(basePath + Path(path).normalize()).exists { errors.append(.invalidLocalPackage(name)) } } for (config, configFile) in configFiles { if !options.disabledValidations.contains(.missingConfigFiles) && !(basePath + configFile).exists { errors.append(.invalidConfigFile(configFile: configFile, config: config)) } if !options.disabledValidations.contains(.missingConfigs) && getConfig(config) == nil { errors.append(.invalidConfigFileConfig(config)) } } if let configName = options.defaultConfig { if !configs.contains(where: { $0.name == configName }) { errors.append(.missingDefaultConfig(configName: configName)) } } for settings in settingGroups.values { errors += validateSettings(settings) } for target in projectTargets { for (config, configFile) in target.configFiles { let configPath = basePath + configFile if !options.disabledValidations.contains(.missingConfigFiles) && !configPath.exists { errors.append(.invalidTargetConfigFile(target: target.name, configFile: configPath.string, config: config)) } if !options.disabledValidations.contains(.missingConfigs) && getConfig(config) == nil { errors.append(.invalidConfigFileConfig(config)) } } if let scheme = target.scheme { for configVariant in scheme.configVariants { if configs.first(including: configVariant, for: .debug) == nil { errors.append(.invalidTargetSchemeConfigVariant( target: target.name, configVariant: configVariant, configType: .debug )) } if configs.first(including: configVariant, for: .release) == nil { errors.append(.invalidTargetSchemeConfigVariant( target: target.name, configVariant: configVariant, configType: .release )) } } if scheme.configVariants.isEmpty { if !configs.contains(where: { $0.type == .debug }) { errors.append(.missingConfigForTargetScheme(target: target.name, configType: .debug)) } if !configs.contains(where: { $0.type == .release }) { errors.append(.missingConfigForTargetScheme(target: target.name, configType: .release)) } } for testTarget in scheme.testTargets { if getTarget(testTarget.name) == nil { // For test case of local Swift Package if case .package(let name) = testTarget.targetReference.location, getPackage(name) != nil { continue } errors.append(.invalidTargetSchemeTest(target: target.name, testTarget: testTarget.name)) } } if !options.disabledValidations.contains(.missingTestPlans) { let invalidTestPlans: [TestPlan] = scheme.testPlans.filter { !(basePath + $0.path).exists } errors.append(contentsOf: invalidTestPlans.map{ .invalidTestPlan($0) }) } } for script in target.buildScripts { if case let .path(pathString) = script.script { let scriptPath = basePath + pathString if !scriptPath.exists { errors.append(.invalidBuildScriptPath(target: target.name, name: script.name, path: scriptPath.string)) } } } errors += validateSettings(target.settings) for buildToolPlugin in target.buildToolPlugins { if packages[buildToolPlugin.package] == nil { errors.append(.invalidPluginPackageReference(plugin: buildToolPlugin.plugin, package: buildToolPlugin.package)) } } } for target in aggregateTargets { for dependency in target.targets { if getProjectTarget(dependency) == nil { errors.append(.invalidTargetDependency(target: target.name, dependency: dependency)) } } } for target in targets { var uniqueDependencies = Set() for dependency in target.dependencies { let dependencyValidationErrors = try validate(dependency, in: target) errors.append(contentsOf: dependencyValidationErrors) if uniqueDependencies.contains(dependency) { errors.append(.duplicateDependencies(target: target.name, dependencyReference: dependency.reference)) } else { uniqueDependencies.insert(dependency) } } for source in target.sources { if source.path.isEmpty { errors.append(.emptySourcePath(target: target.name)) continue } let sourcePath = basePath + source.path if !source.optional && !sourcePath.exists { errors.append(.invalidTargetSource(target: target.name, source: sourcePath.string)) } } if target.supportedDestinations != nil, target.platform == .watchOS { errors.append(.unexpectedTargetPlatformForSupportedDestinations(target: target.name, platform: target.platform)) } if let supportedDestinations = target.supportedDestinations, target.type.isApp, supportedDestinations.contains(.watchOS) { errors.append(.containsWatchOSDestinationForMultiplatformApp(target: target.name)) } if target.supportedDestinations?.contains(.macOS) == true, target.supportedDestinations?.contains(.macCatalyst) == true { errors.append(.multipleMacPlatformsInSupportedDestinations(target: target.name)) } if target.supportedDestinations?.contains(.macCatalyst) == true, target.platform != .iOS, target.platform != .auto { errors.append(.invalidTargetPlatformForSupportedDestinations(target: target.name)) } if target.platform != .auto, target.platform != .watchOS, let supportedDestination = SupportedDestination(rawValue: target.platform.rawValue), target.supportedDestinations?.contains(supportedDestination) == false { errors.append(.missingTargetPlatformInSupportedDestinations(target: target.name, platform: target.platform)) } } for projectReference in projectReferences { if !(basePath + projectReference.path).exists { errors.append(.invalidProjectReferencePath(projectReference)) } } for scheme in schemes { errors.append( contentsOf: scheme.build.targets.compactMap { validationError(for: $0.target, in: scheme, action: "build") } ) if let action = scheme.run, let config = action.config, getConfig(config) == nil { errors.append(.invalidSchemeConfig(scheme: scheme.name, config: config)) } if !options.disabledValidations.contains(.missingTestPlans) { let invalidTestPlans: [TestPlan] = scheme.test?.testPlans.filter { !(basePath + $0.path).exists } ?? [] errors.append(contentsOf: invalidTestPlans.map{ .invalidTestPlan($0) }) } let defaultPlanCount = scheme.test?.testPlans.filter { $0.defaultPlan }.count ?? 0 if (defaultPlanCount > 1) { errors.append(.multipleDefaultTestPlans) } if let action = scheme.test, let config = action.config, getConfig(config) == nil { errors.append(.invalidSchemeConfig(scheme: scheme.name, config: config)) } errors.append( contentsOf: scheme.test?.targets.compactMap { validationError(for: $0.targetReference, in: scheme, action: "test") } ?? [] ) errors.append( contentsOf: scheme.test?.coverageTargets.compactMap { validationError(for: $0, in: scheme, action: "test") } ?? [] ) if let action = scheme.profile, let config = action.config, getConfig(config) == nil { errors.append(.invalidSchemeConfig(scheme: scheme.name, config: config)) } if let action = scheme.analyze, let config = action.config, getConfig(config) == nil { errors.append(.invalidSchemeConfig(scheme: scheme.name, config: config)) } if let action = scheme.archive, let config = action.config, getConfig(config) == nil { errors.append(.invalidSchemeConfig(scheme: scheme.name, config: config)) } } if !errors.isEmpty { throw SpecValidationError(errors: errors) } } public func validateMinimumXcodeGenVersion(_ xcodeGenVersion: Version) throws { if let minimumXcodeGenVersion = options.minimumXcodeGenVersion, xcodeGenVersion < minimumXcodeGenVersion { throw SpecValidationError(errors: [SpecValidationError.ValidationError.invalidXcodeGenVersion(minimumVersion: minimumXcodeGenVersion, version: xcodeGenVersion)]) } } // Returns error if the given dependency from target is invalid. private func validate(_ dependency: Dependency, in target: Target) throws -> [SpecValidationError.ValidationError] { var errors: [SpecValidationError.ValidationError] = [] switch dependency.type { case .target: let dependencyTargetReference = try TargetReference(dependency.reference) switch dependencyTargetReference.location { case .local: if getProjectTarget(dependency.reference) == nil { errors.append(.invalidTargetDependency(target: target.name, dependency: dependency.reference)) } case .project(let dependencyProjectName): if getProjectReference(dependencyProjectName) == nil { errors.append(.invalidTargetDependency(target: target.name, dependency: dependency.reference)) } } case .sdk: let path = Path(dependency.reference) if !dependency.reference.contains("/") { switch path.extension { case "framework"?, "tbd"?, "dylib"?: break default: errors.append(.invalidSDKDependency(target: target.name, dependency: dependency.reference)) } } case .package: if packages[dependency.reference] == nil { errors.append(.invalidSwiftPackage(name: dependency.reference, target: target.name)) } default: break } return errors } /// Returns a descriptive error if the given target reference was invalid otherwise `nil`. private func validationError(for targetReference: TargetReference, in scheme: Scheme, action: String) -> SpecValidationError.ValidationError? { switch targetReference.location { case .local where getProjectTarget(targetReference.name) == nil: return .invalidSchemeTarget(scheme: scheme.name, target: targetReference.name, action: action) case .project(let project) where getProjectReference(project) == nil: return .invalidProjectReference(scheme: scheme.name, reference: project) case .local, .project: return nil } } /// Returns a descriptive error if the given target reference was invalid otherwise `nil`. private func validationError(for testableTargetReference: TestableTargetReference, in scheme: Scheme, action: String) -> SpecValidationError.ValidationError? { switch testableTargetReference.location { case .local where getProjectTarget(testableTargetReference.name) == nil: return .invalidSchemeTarget(scheme: scheme.name, target: testableTargetReference.name, action: action) case .project(let project) where getProjectReference(project) == nil: return .invalidProjectReference(scheme: scheme.name, reference: project) case .package(let package) where getPackage(package) == nil: return .invalidLocalPackage(package) case .local, .project, .package: return nil } } } ================================================ FILE: Sources/ProjectSpec/SpecValidationError.swift ================================================ import Foundation import Version public struct SpecValidationError: Error, CustomStringConvertible { public var errors: [ValidationError] public init(errors: [ValidationError]) { self.errors = errors } public enum ValidationError: Hashable, Error, CustomStringConvertible { case invalidXcodeGenVersion(minimumVersion: Version, version: Version) case invalidSDKDependency(target: String, dependency: String) case invalidTargetDependency(target: String, dependency: String) case invalidTargetSource(target: String, source: String) case invalidTargetConfigFile(target: String, configFile: String, config: String) case invalidTargetSchemeConfigVariant(target: String, configVariant: String, configType: ConfigType) case invalidTargetSchemeTest(target: String, testTarget: String) case invalidTargetPlatformForSupportedDestinations(target: String) case unexpectedTargetPlatformForSupportedDestinations(target: String, platform: Platform) case containsWatchOSDestinationForMultiplatformApp(target: String) case multipleMacPlatformsInSupportedDestinations(target: String) case missingTargetPlatformInSupportedDestinations(target: String, platform: Platform) case invalidSchemeTarget(scheme: String, target: String, action: String) case invalidSchemeConfig(scheme: String, config: String) case invalidSwiftPackage(name: String, target: String) case invalidPackageDependencyReference(name: String) case invalidLocalPackage(String) case invalidConfigFile(configFile: String, config: String) case invalidBuildSettingConfig(String) case invalidSettingsGroup(String) case invalidBuildScriptPath(target: String, name: String?, path: String) case invalidFileGroup(String) case invalidConfigFileConfig(String) case missingConfigForTargetScheme(target: String, configType: ConfigType) case missingDefaultConfig(configName: String) case invalidPerConfigSettings case invalidProjectReference(scheme: String, reference: String) case invalidProjectReferencePath(ProjectReference) case invalidTestPlan(TestPlan) case multipleDefaultTestPlans case duplicateDependencies(target: String, dependencyReference: String) case invalidPluginPackageReference(plugin: String, package: String) case emptySourcePath(target: String) public var description: String { switch self { case let .invalidXcodeGenVersion(minimumVersion, version): return "XcodeGen version is \(version), but minimum required version specified as \(minimumVersion)" case let .invalidSDKDependency(target, dependency): return "Target \(target.quoted) has invalid sdk dependency: \(dependency.quoted). It must be a full path or have the following extensions: .framework, .dylib, .tbd" case let .invalidTargetDependency(target, dependency): return "Target \(target.quoted) has invalid dependency: \(dependency.quoted)" case let .invalidTargetConfigFile(target, configFile, config): return "Target \(target.quoted) has invalid config file path \(configFile.quoted) for config \(config.quoted)" case let .invalidTargetSource(target, source): return "Target \(target.quoted) has a missing source directory \(source.quoted)" case let .invalidTargetSchemeConfigVariant(target, configVariant, configType): return "Target \(target.quoted) has an invalid scheme config variant which requires a config that has a \(configType.rawValue.quoted) type and contains the name \(configVariant.quoted)" case let .invalidTargetSchemeTest(target, test): return "Target \(target.quoted) scheme has invalid test \(test.quoted)" case let .invalidTargetPlatformForSupportedDestinations(target): return "Target \(target.quoted) has supported destinations that require a target platform iOS or auto" case let .unexpectedTargetPlatformForSupportedDestinations(target, platform): return "Target \(target.quoted) has platform \(platform.rawValue.quoted) that does not expect supported destinations" case let .multipleMacPlatformsInSupportedDestinations(target): return "Target \(target.quoted) has multiple definitions of mac platforms in supported destinations" case let .missingTargetPlatformInSupportedDestinations(target, platform): return "Target \(target.quoted) has platform \(platform.rawValue.quoted) that is missing in supported destinations" case let .containsWatchOSDestinationForMultiplatformApp(target): return "Multiplatform app \(target.quoted) cannot contain watchOS in \"supportedDestinations\". Create a separate target using \"platform\" for watchOS apps" case let .invalidConfigFile(configFile, config): return "Invalid config file \(configFile.quoted) for config \(config.quoted)" case let .invalidSchemeTarget(scheme, target, action): return "Scheme \(scheme.quoted) has invalid \(action) target \(target.quoted)" case let .invalidSchemeConfig(scheme, config): return "Scheme \(scheme.quoted) has invalid build configuration \(config.quoted)" case let .invalidBuildSettingConfig(config): return "Build setting has invalid build configuration \(config.quoted)" case let .invalidSettingsGroup(group): return "Invalid settings group \(group.quoted)" case let .invalidBuildScriptPath(target, name, path): return "Target \(target.quoted) has a script \(name != nil ? "\(name!.quoted) which has a " : "")path that doesn't exist \(path.quoted)" case let .invalidFileGroup(group): return "Invalid file group \(group.quoted)" case let .invalidConfigFileConfig(config): return "Config file has invalid config \(config.quoted)" case let .invalidSwiftPackage(name, target): return "Target \(target.quoted) has an invalid package dependency \(name.quoted)" case let .invalidLocalPackage(path): return "Invalid local package \(path.quoted)" case let .invalidPackageDependencyReference(name): return "Package reference \(name) must be specified as package dependency, not target" case let .missingConfigForTargetScheme(target, configType): return "Target \(target.quoted) is missing a config of type \(configType.rawValue) to generate its scheme" case let .missingDefaultConfig(name): return "Default configuration \(name) doesn't exist" case .invalidPerConfigSettings: return "Settings that are for a specific config must go in \"configs\". \"base\" can be used for common settings" case let .invalidProjectReference(scheme, project): return "Scheme \(scheme.quoted) has invalid project reference \(project.quoted)" case let .invalidProjectReferencePath(reference): return "Project reference \(reference.name) has a project file path that doesn't exist \"\(reference.path)\"" case let .invalidTestPlan(testPlan): return "Test plan path \"\(testPlan.path)\" doesn't exist" case .multipleDefaultTestPlans: return "Your test plans contain more than one default test plan" case let .duplicateDependencies(target, dependencyReference): return "Target \(target.quoted) has the dependency \(dependencyReference.quoted) multiple times" case let .invalidPluginPackageReference(plugin, package): return "Plugin \(plugin) has invalid package reference \(package)" case let .emptySourcePath(target): return "Target \(target.quoted) has an empty source path entry" } } } public var description: String { let title: String if errors.count == 1 { title = "Spec validation error: " } else { title = "\(errors.count) Spec validations errors:\n\t- " } return "\(title)" + errors.map { $0.description }.joined(separator: "\n\t- ") } } ================================================ FILE: Sources/ProjectSpec/SupportedDestination.swift ================================================ import Foundation public enum SupportedDestination: String, CaseIterable { case iOS case tvOS case macOS case macCatalyst case watchOS case visionOS } extension SupportedDestination { public var string: String { switch self { case .iOS: return "ios" case .tvOS: return "tvos" case .macOS: return "macos" case .macCatalyst: return "maccatalyst" case .watchOS: return "watchos" case .visionOS: return "xros" } } // This is used to: // 1. Get the first one and apply SettingPresets 'Platforms' and 'Product_Platform' if the platform is 'auto' // 2. Sort, loop and merge together SettingPresets 'SupportedDestinations' public var priority: Int { switch self { case .iOS: return 0 case .tvOS: return 1 case .watchOS: return 2 case .visionOS: return 3 case .macOS: return 4 case .macCatalyst: return 5 } } } ================================================ FILE: Sources/ProjectSpec/SwiftPackage.swift ================================================ import Foundation import XcodeProj import JSONUtilities import Version public enum SwiftPackage: Equatable { public typealias VersionRequirement = XCRemoteSwiftPackageReference.VersionRequirement static let githubPrefix = "https://github.com/" case remote(url: String, versionRequirement: VersionRequirement) case local(path: String, group: String?, excludeFromProject: Bool) public var isLocal: Bool { if case .local = self { return true } return false } } extension SwiftPackage: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { if let path: String = jsonDictionary.json(atKeyPath: "path") { let customLocation: String? = jsonDictionary.json(atKeyPath: "group") let excludeFromProject: Bool = jsonDictionary.json(atKeyPath: "excludeFromProject") ?? false self = .local(path: path, group: customLocation, excludeFromProject: excludeFromProject) } else { let versionRequirement: VersionRequirement = try VersionRequirement(jsonDictionary: jsonDictionary) try Self.validateVersion(versionRequirement: versionRequirement) let url: String if jsonDictionary["github"] != nil { let github: String = try jsonDictionary.json(atKeyPath: "github") url = "\(Self.githubPrefix)\(github)" } else { url = try jsonDictionary.json(atKeyPath: "url") } self = .remote(url: url, versionRequirement: versionRequirement) } } private static func validateVersion(versionRequirement: VersionRequirement) throws { switch versionRequirement { case .upToNextMajorVersion(let version): try _ = Version.parse(version) case .upToNextMinorVersion(let version): try _ = Version.parse(version) case .range(let from, let to): try _ = Version.parse(from) try _ = Version.parse(to) case .exact(let version): try _ = Version.parse(version) default: break } } } extension SwiftPackage: JSONEncodable { public func toJSONValue() -> Any { var dictionary: JSONDictionary = [:] switch self { case .remote(let url, let versionRequirement): if url.hasPrefix(Self.githubPrefix) { dictionary["github"] = url.replacingOccurrences(of: Self.githubPrefix, with: "") } else { dictionary["url"] = url } switch versionRequirement { case .upToNextMajorVersion(let version): dictionary["majorVersion"] = version case .upToNextMinorVersion(let version): dictionary["minorVersion"] = version case .range(let from, let to): dictionary["minVersion"] = from dictionary["maxVersion"] = to case .exact(let version): dictionary["exactVersion"] = version case .branch(let branch): dictionary["branch"] = branch case .revision(let revision): dictionary["revision"] = revision } return dictionary case let .local(path, group, excludeFromProject): dictionary["path"] = path dictionary["group"] = group dictionary["excludeFromProject"] = excludeFromProject } return dictionary } } extension SwiftPackage.VersionRequirement: JSONUtilities.JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { func json(atKeyPath keyPath: String) -> String? { if jsonDictionary[keyPath] != nil { do { let value: String = try jsonDictionary.json(atKeyPath: .init(rawValue: keyPath)) return value } catch { do { let value: Double = try jsonDictionary.json(atKeyPath: .init(rawValue: keyPath)) return String(value) } catch { return nil } } } return nil } if let exactVersion = json(atKeyPath: "exactVersion") { self = .exact(exactVersion) } else if let version = json(atKeyPath: "version") { self = .exact(version) } else if let revision = json(atKeyPath: "revision") { self = .revision(revision) } else if let branch = json(atKeyPath: "branch") { self = .branch(branch) } else if let minVersion = json(atKeyPath: "minVersion"), let maxVersion = json(atKeyPath: "maxVersion") { self = .range(from: minVersion, to: maxVersion) } else if let minorVersion = json(atKeyPath: "minorVersion") { self = .upToNextMinorVersion(minorVersion) } else if let majorVersion = json(atKeyPath: "majorVersion") { self = .upToNextMajorVersion(majorVersion) } else if let from = json(atKeyPath: "from") { self = .upToNextMajorVersion(from) } else { throw SpecParsingError.unknownPackageRequirement(jsonDictionary) } } } extension SwiftPackage: PathContainer { static var pathProperties: [PathProperty] { [ .dictionary([ .string("path"), ]), ] } } ================================================ FILE: Sources/ProjectSpec/Target.swift ================================================ import Foundation import JSONUtilities import XcodeProj import Version public struct LegacyTarget: Equatable { public static let passSettingsDefault = false public var toolPath: String public var arguments: String? public var passSettings: Bool public var workingDirectory: String? public init( toolPath: String, passSettings: Bool = passSettingsDefault, arguments: String? = nil, workingDirectory: String? = nil ) { self.toolPath = toolPath self.arguments = arguments self.passSettings = passSettings self.workingDirectory = workingDirectory } } extension LegacyTarget: PathContainer { static var pathProperties: [PathProperty] { [ .string("workingDirectory"), ] } } public struct Target: ProjectTarget { public var name: String public var type: PBXProductType public var platform: Platform public var supportedDestinations: [SupportedDestination]? public var settings: Settings public var sources: [TargetSource] public var dependencies: [Dependency] public var info: Plist? public var entitlements: Plist? public var transitivelyLinkDependencies: Bool? public var directlyEmbedCarthageDependencies: Bool? public var requiresObjCLinking: Bool? public var preBuildScripts: [BuildScript] public var buildToolPlugins: [BuildToolPlugin] public var postCompileScripts: [BuildScript] public var postBuildScripts: [BuildScript] public var buildRules: [BuildRule] public var configFiles: [String: String] public var scheme: TargetScheme? public var legacy: LegacyTarget? public var deploymentTarget: Version? public var attributes: [String: Any] public var productName: String public var onlyCopyFilesOnInstall: Bool public var putResourcesBeforeSourcesBuildPhase: Bool public var isLegacy: Bool { legacy != nil } public var filename: String { var filename = productName if let fileExtension = type.fileExtension { filename += ".\(fileExtension)" } if type == .staticLibrary { filename = "lib\(filename)" } return filename } public init( name: String, type: PBXProductType, platform: Platform, supportedDestinations: [SupportedDestination]? = nil, productName: String? = nil, deploymentTarget: Version? = nil, settings: Settings = .empty, configFiles: [String: String] = [:], sources: [TargetSource] = [], dependencies: [Dependency] = [], info: Plist? = nil, entitlements: Plist? = nil, transitivelyLinkDependencies: Bool? = nil, directlyEmbedCarthageDependencies: Bool? = nil, requiresObjCLinking: Bool? = nil, preBuildScripts: [BuildScript] = [], buildToolPlugins: [BuildToolPlugin] = [], postCompileScripts: [BuildScript] = [], postBuildScripts: [BuildScript] = [], buildRules: [BuildRule] = [], scheme: TargetScheme? = nil, legacy: LegacyTarget? = nil, attributes: [String: Any] = [:], onlyCopyFilesOnInstall: Bool = false, putResourcesBeforeSourcesBuildPhase: Bool = false ) { self.name = name self.type = type self.platform = platform self.supportedDestinations = supportedDestinations self.deploymentTarget = deploymentTarget self.productName = productName ?? name self.settings = settings self.configFiles = configFiles self.sources = sources self.dependencies = dependencies self.info = info self.entitlements = entitlements self.transitivelyLinkDependencies = transitivelyLinkDependencies self.directlyEmbedCarthageDependencies = directlyEmbedCarthageDependencies self.requiresObjCLinking = requiresObjCLinking self.preBuildScripts = preBuildScripts self.buildToolPlugins = buildToolPlugins self.postCompileScripts = postCompileScripts self.postBuildScripts = postBuildScripts self.buildRules = buildRules self.scheme = scheme self.legacy = legacy self.attributes = attributes self.onlyCopyFilesOnInstall = onlyCopyFilesOnInstall self.putResourcesBeforeSourcesBuildPhase = putResourcesBeforeSourcesBuildPhase } } extension Target: CustomStringConvertible { public var description: String { "\(name): \(platform.rawValue) \(type)" } } extension Target: PathContainer { static var pathProperties: [PathProperty] { [ .dictionary([ .string("sources"), .object("sources", TargetSource.pathProperties), .string("configFiles"), .object("dependencies", Dependency.pathProperties), .object("info", Plist.pathProperties), .object("entitlements", Plist.pathProperties), .object("preBuildScripts", BuildScript.pathProperties), .object("prebuildScripts", BuildScript.pathProperties), .object("postCompileScripts", BuildScript.pathProperties), .object("postBuildScripts", BuildScript.pathProperties), .object("legacy", LegacyTarget.pathProperties), .object("scheme", TargetScheme.pathProperties), ]), ] } } extension Target { static func resolveMultiplatformTargets(jsonDictionary: JSONDictionary) -> JSONDictionary { guard let targetsDictionary: [String: JSONDictionary] = jsonDictionary["targets"] as? [String: JSONDictionary] else { return jsonDictionary } var crossPlatformTargets: [String: JSONDictionary] = [:] for (targetName, target) in targetsDictionary { if let platforms = target["platform"] as? [String] { for platform in platforms { var platformTarget = target /// This value is set to help us to check, in Target init, that there are no conflicts in the definition of the platforms. We want to ensure that the user didn't define, at the same time, /// the new Xcode 14 supported destinations and the XcodeGen generation of Multiple Platform Targets (when you define the platform field as an array). platformTarget["isMultiPlatformTarget"] = true platformTarget = platformTarget.expand(variables: ["platform": platform]) platformTarget["platform"] = platform let platformSuffix = platformTarget["platformSuffix"] as? String ?? "_\(platform)" let platformPrefix = platformTarget["platformPrefix"] as? String ?? "" let newTargetName = platformPrefix + targetName + platformSuffix var settings = platformTarget["settings"] as? JSONDictionary ?? [:] if settings["configs"] != nil || settings["groups"] != nil || settings["base"] != nil { var base = settings["base"] as? JSONDictionary ?? [:] if base["PRODUCT_NAME"] == nil { base["PRODUCT_NAME"] = targetName } settings["base"] = base } else { if settings["PRODUCT_NAME"] == nil { settings["PRODUCT_NAME"] = targetName } } platformTarget["productName"] = targetName platformTarget["settings"] = settings if let deploymentTargets = target["deploymentTarget"] as? [String: Any] { platformTarget["deploymentTarget"] = deploymentTargets[platform] } crossPlatformTargets[newTargetName] = platformTarget } } else { crossPlatformTargets[targetName] = target } } var merged = jsonDictionary merged["targets"] = crossPlatformTargets return merged } } extension Target: Equatable { public static func == (lhs: Target, rhs: Target) -> Bool { lhs.name == rhs.name && lhs.type == rhs.type && lhs.platform == rhs.platform && lhs.deploymentTarget == rhs.deploymentTarget && lhs.transitivelyLinkDependencies == rhs.transitivelyLinkDependencies && lhs.requiresObjCLinking == rhs.requiresObjCLinking && lhs.directlyEmbedCarthageDependencies == rhs.directlyEmbedCarthageDependencies && lhs.settings == rhs.settings && lhs.configFiles == rhs.configFiles && lhs.sources == rhs.sources && lhs.info == rhs.info && lhs.entitlements == rhs.entitlements && lhs.dependencies == rhs.dependencies && lhs.preBuildScripts == rhs.preBuildScripts && lhs.buildToolPlugins == rhs.buildToolPlugins && lhs.postCompileScripts == rhs.postCompileScripts && lhs.postBuildScripts == rhs.postBuildScripts && lhs.buildRules == rhs.buildRules && lhs.scheme == rhs.scheme && lhs.legacy == rhs.legacy && NSDictionary(dictionary: lhs.attributes).isEqual(to: rhs.attributes) } } extension LegacyTarget: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { toolPath = try jsonDictionary.json(atKeyPath: "toolPath") arguments = jsonDictionary.json(atKeyPath: "arguments") passSettings = jsonDictionary.json(atKeyPath: "passSettings") ?? LegacyTarget.passSettingsDefault workingDirectory = jsonDictionary.json(atKeyPath: "workingDirectory") } } extension LegacyTarget: JSONEncodable { public func toJSONValue() -> Any { var dict: [String: Any?] = [ "toolPath": toolPath, "arguments": arguments, "workingDirectory": workingDirectory, ] if passSettings != LegacyTarget.passSettingsDefault { dict["passSettings"] = passSettings } return dict } } extension Target: NamedJSONDictionaryConvertible { public init(name: String, jsonDictionary: JSONDictionary) throws { let resolvedName: String = jsonDictionary.json(atKeyPath: "name") ?? name self.name = resolvedName productName = jsonDictionary.json(atKeyPath: "productName") ?? resolvedName let typeString: String = jsonDictionary.json(atKeyPath: "type") ?? "" if let type = PBXProductType(string: typeString) { self.type = type } else { throw SpecParsingError.unknownTargetType(typeString) } if let supportedDestinations: [SupportedDestination] = jsonDictionary.json(atKeyPath: "supportedDestinations") { self.supportedDestinations = supportedDestinations } let isResolved = jsonDictionary.json(atKeyPath: "isMultiPlatformTarget") ?? false if isResolved, supportedDestinations != nil { throw SpecParsingError.invalidTargetPlatformAsArray } var platformString: String = jsonDictionary.json(atKeyPath: "platform") ?? "" // platform defaults to 'auto' if it is empty and we are using supported destinations if supportedDestinations != nil, platformString.isEmpty { platformString = Platform.auto.rawValue } // we add 'iOS' in supported destinations if it contains only 'macCatalyst' if supportedDestinations?.contains(.macCatalyst) == true, supportedDestinations?.contains(.iOS) == false { supportedDestinations?.append(.iOS) } if let platform = Platform(rawValue: platformString) { self.platform = platform } else { throw SpecParsingError.unknownTargetPlatform(platformString) } if let string: String = jsonDictionary.json(atKeyPath: "deploymentTarget") { deploymentTarget = try Version.parse(string) } else if let double: Double = jsonDictionary.json(atKeyPath: "deploymentTarget") { deploymentTarget = try Version.parse(String(double)) } else { deploymentTarget = nil } settings = try BuildSettingsParser(jsonDictionary: jsonDictionary).parse() configFiles = jsonDictionary.json(atKeyPath: "configFiles") ?? [:] if let source: String = jsonDictionary.json(atKeyPath: "sources") { sources = [TargetSource(path: source)] } else if let array = jsonDictionary["sources"] as? [Any] { sources = try array.compactMap { source in if let string = source as? String { return TargetSource(path: string) } else if let dictionary = source as? [String: Any] { return try TargetSource(jsonDictionary: dictionary) } else { return nil } } } else { sources = [] } if jsonDictionary["dependencies"] == nil { dependencies = [] } else { let dependencies: [Dependency] = try jsonDictionary.json(atKeyPath: "dependencies", invalidItemBehaviour: .fail) self.dependencies = dependencies.filter { [platform] dependency -> Bool in // If unspecified, all platforms are supported guard let platforms = dependency.platforms else { return true } return platforms.contains(platform) } } if jsonDictionary["buildToolPlugins"] == nil { buildToolPlugins = [] } else { self.buildToolPlugins = try jsonDictionary.json(atKeyPath: "buildToolPlugins", invalidItemBehaviour: .fail) } if jsonDictionary["info"] != nil { info = try jsonDictionary.json(atKeyPath: "info") as Plist } if jsonDictionary["entitlements"] != nil { entitlements = try jsonDictionary.json(atKeyPath: "entitlements") as Plist } transitivelyLinkDependencies = jsonDictionary.json(atKeyPath: "transitivelyLinkDependencies") directlyEmbedCarthageDependencies = jsonDictionary.json(atKeyPath: "directlyEmbedCarthageDependencies") requiresObjCLinking = jsonDictionary.json(atKeyPath: "requiresObjCLinking") preBuildScripts = jsonDictionary.json(atKeyPath: "preBuildScripts") ?? jsonDictionary.json(atKeyPath: "prebuildScripts") ?? [] postCompileScripts = jsonDictionary.json(atKeyPath: "postCompileScripts") ?? [] postBuildScripts = jsonDictionary.json(atKeyPath: "postBuildScripts") ?? jsonDictionary.json(atKeyPath: "postbuildScripts") ?? [] buildRules = jsonDictionary.json(atKeyPath: "buildRules") ?? [] scheme = jsonDictionary.json(atKeyPath: "scheme") legacy = jsonDictionary.json(atKeyPath: "legacy") attributes = jsonDictionary.json(atKeyPath: "attributes") ?? [:] onlyCopyFilesOnInstall = jsonDictionary.json(atKeyPath: "onlyCopyFilesOnInstall") ?? false putResourcesBeforeSourcesBuildPhase = jsonDictionary.json(atKeyPath: "putResourcesBeforeSourcesBuildPhase") ?? false } } extension Target: JSONEncodable { public func toJSONValue() -> Any { var dict: [String: Any?] = [ "type": type.name, "platform": platform.rawValue, "supportedDestinations": supportedDestinations?.map { $0.rawValue }, "settings": settings.toJSONValue(), "configFiles": configFiles, "attributes": attributes, "sources": sources.map { $0.toJSONValue() }, "dependencies": dependencies.map { $0.toJSONValue() }, "postCompileScripts": postCompileScripts.map { $0.toJSONValue() }, "prebuildScripts": preBuildScripts.map { $0.toJSONValue() }, "buildToolPlugins": buildToolPlugins.map { $0.toJSONValue() }, "postbuildScripts": postBuildScripts.map { $0.toJSONValue() }, "buildRules": buildRules.map { $0.toJSONValue() }, "deploymentTarget": deploymentTarget?.deploymentTarget, "info": info?.toJSONValue(), "entitlements": entitlements?.toJSONValue(), "transitivelyLinkDependencies": transitivelyLinkDependencies, "directlyEmbedCarthageDependencies": directlyEmbedCarthageDependencies, "requiresObjCLinking": requiresObjCLinking, "scheme": scheme?.toJSONValue(), "legacy": legacy?.toJSONValue(), ] if productName != name { dict["productName"] = productName } if onlyCopyFilesOnInstall { dict["onlyCopyFilesOnInstall"] = true } if putResourcesBeforeSourcesBuildPhase { dict["putResourcesBeforeSourcesBuildPhase"] = true } return dict } } ================================================ FILE: Sources/ProjectSpec/TargetReference.swift ================================================ import Foundation import JSONUtilities public struct TargetReference: Hashable { public var name: String public var location: Location public enum Location: Hashable { case local case project(String) } public init(name: String, location: Location) { self.name = name self.location = location } } extension TargetReference { public init(_ string: String) throws { let paths = string.split(separator: "/") switch paths.count { case 2: location = .project(String(paths[0])) name = String(paths[1]) case 1: location = .local name = String(paths[0]) default: throw SpecParsingError.invalidTargetReference(string) } } public static func local(_ name: String) -> TargetReference { TargetReference(name: name, location: .local) } } extension TargetReference: ExpressibleByStringLiteral { public init(stringLiteral value: String) { try! self.init(value) } } extension TargetReference: CustomStringConvertible { public var reference: String { switch location { case .local: return name case .project(let root): return "\(root)/\(name)" } } public var description: String { reference } } ================================================ FILE: Sources/ProjectSpec/TargetScheme.swift ================================================ import Foundation import JSONUtilities import XcodeProj public struct TargetScheme: Equatable { public static let gatherCoverageDataDefault = false public static let disableMainThreadCheckerDefault = false public static let stopOnEveryMainThreadCheckerIssueDefault = false public static let disableThreadPerformanceCheckerDefault = false public static let buildImplicitDependenciesDefault = true public var testTargets: [Scheme.Test.TestTarget] public var configVariants: [String] public var gatherCoverageData: Bool public var coverageTargets: [TestableTargetReference] public var storeKitConfiguration: String? public var language: String? public var region: String? public var disableMainThreadChecker: Bool public var stopOnEveryMainThreadCheckerIssue: Bool public var disableThreadPerformanceChecker: Bool public var buildImplicitDependencies: Bool public var commandLineArguments: [String: Bool] public var environmentVariables: [XCScheme.EnvironmentVariable] public var preActions: [Scheme.ExecutionAction] public var postActions: [Scheme.ExecutionAction] public var management: Scheme.Management? public var testPlans: [TestPlan] public init( testTargets: [Scheme.Test.TestTarget] = [], testPlans: [TestPlan] = [], configVariants: [String] = [], gatherCoverageData: Bool = gatherCoverageDataDefault, coverageTargets: [TestableTargetReference] = [], storeKitConfiguration: String? = nil, language: String? = nil, region: String? = nil, disableMainThreadChecker: Bool = disableMainThreadCheckerDefault, stopOnEveryMainThreadCheckerIssue: Bool = stopOnEveryMainThreadCheckerIssueDefault, disableThreadPerformanceChecker: Bool = disableThreadPerformanceCheckerDefault, buildImplicitDependencies: Bool = buildImplicitDependenciesDefault, commandLineArguments: [String: Bool] = [:], environmentVariables: [XCScheme.EnvironmentVariable] = [], preActions: [Scheme.ExecutionAction] = [], postActions: [Scheme.ExecutionAction] = [], management: Scheme.Management? = nil ) { self.testTargets = testTargets self.testPlans = testPlans self.configVariants = configVariants self.gatherCoverageData = gatherCoverageData self.coverageTargets = coverageTargets self.storeKitConfiguration = storeKitConfiguration self.language = language self.region = region self.disableMainThreadChecker = disableMainThreadChecker self.stopOnEveryMainThreadCheckerIssue = stopOnEveryMainThreadCheckerIssue self.disableThreadPerformanceChecker = disableThreadPerformanceChecker self.buildImplicitDependencies = buildImplicitDependencies self.commandLineArguments = commandLineArguments self.environmentVariables = environmentVariables self.preActions = preActions self.postActions = postActions self.postActions = postActions self.management = management } } extension TargetScheme: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { if let targets = jsonDictionary["testTargets"] as? [Any] { testTargets = try targets.compactMap { target in if let string = target as? String { return .init(targetReference: try TestableTargetReference(string)) } else if let dictionary = target as? JSONDictionary, let target: Scheme.Test.TestTarget = try? .init(jsonDictionary: dictionary) { return target } else { return nil } } } else { testTargets = [] } if let targets = jsonDictionary["coverageTargets"] as? [Any] { coverageTargets = try targets.compactMap { target in if let string = target as? String { return try TestableTargetReference(string) } else if let dictionary = target as? JSONDictionary, let target: TestableTargetReference = try? .init(jsonDictionary: dictionary) { return target } else { return nil } } } else { coverageTargets = [] } testPlans = try (jsonDictionary.json(atKeyPath: "testPlans") ?? []).map { try TestPlan(jsonDictionary: $0) } configVariants = jsonDictionary.json(atKeyPath: "configVariants") ?? [] gatherCoverageData = jsonDictionary.json(atKeyPath: "gatherCoverageData") ?? TargetScheme.gatherCoverageDataDefault storeKitConfiguration = jsonDictionary.json(atKeyPath: "storeKitConfiguration") language = jsonDictionary.json(atKeyPath: "language") region = jsonDictionary.json(atKeyPath: "region") disableMainThreadChecker = jsonDictionary.json(atKeyPath: "disableMainThreadChecker") ?? TargetScheme.disableMainThreadCheckerDefault stopOnEveryMainThreadCheckerIssue = jsonDictionary.json(atKeyPath: "stopOnEveryMainThreadCheckerIssue") ?? TargetScheme.stopOnEveryMainThreadCheckerIssueDefault disableThreadPerformanceChecker = jsonDictionary.json(atKeyPath: "disableThreadPerformanceChecker") ?? TargetScheme.disableThreadPerformanceCheckerDefault buildImplicitDependencies = jsonDictionary.json(atKeyPath: "buildImplicitDependencies") ?? TargetScheme.buildImplicitDependenciesDefault commandLineArguments = jsonDictionary.json(atKeyPath: "commandLineArguments") ?? [:] environmentVariables = try XCScheme.EnvironmentVariable.parseAll(jsonDictionary: jsonDictionary) preActions = jsonDictionary.json(atKeyPath: "preActions") ?? [] postActions = jsonDictionary.json(atKeyPath: "postActions") ?? [] management = jsonDictionary.json(atKeyPath: "management") } } extension TargetScheme: JSONEncodable { public func toJSONValue() -> Any { var dict: [String: Any] = [ "configVariants": configVariants, "coverageTargets": coverageTargets.map { $0.reference }, "commandLineArguments": commandLineArguments, "testTargets": testTargets.map { $0.toJSONValue() }, "testPlans": testPlans.map { $0.toJSONValue() }, "environmentVariables": environmentVariables.map { $0.toJSONValue() }, "preActions": preActions.map { $0.toJSONValue() }, "postActions": postActions.map { $0.toJSONValue() }, ] if gatherCoverageData != TargetScheme.gatherCoverageDataDefault { dict["gatherCoverageData"] = gatherCoverageData } if let storeKitConfiguration = storeKitConfiguration { dict["storeKitConfiguration"] = storeKitConfiguration } if disableMainThreadChecker != TargetScheme.disableMainThreadCheckerDefault { dict["disableMainThreadChecker"] = disableMainThreadChecker } if stopOnEveryMainThreadCheckerIssue != TargetScheme.stopOnEveryMainThreadCheckerIssueDefault { dict["stopOnEveryMainThreadCheckerIssue"] = stopOnEveryMainThreadCheckerIssue } if disableThreadPerformanceChecker != TargetScheme.disableThreadPerformanceCheckerDefault { dict["disableThreadPerformanceChecker"] = disableThreadPerformanceChecker } if buildImplicitDependencies != TargetScheme.buildImplicitDependenciesDefault { dict["buildImplicitDependencies"] = buildImplicitDependencies } if let language = language { dict["language"] = language } if let region = region { dict["region"] = region } if let management = management { dict["management"] = management.toJSONValue() } return dict } } extension TargetScheme: PathContainer { static var pathProperties: [PathProperty] { [ .object("testPlans", TestPlan.pathProperties), ] } } ================================================ FILE: Sources/ProjectSpec/TargetSource.swift ================================================ import Foundation import JSONUtilities import PathKit public struct TargetSource: Equatable { public static let optionalDefault = false public var path: String { didSet { path = (path as NSString).standardizingPath } } public var name: String? public var group: String? public var compilerFlags: [String] public var excludes: [String] public var includes: [String] public var explicitFolders: [String] public var type: SourceType? public var optional: Bool public var buildPhase: BuildPhaseSpec? public var headerVisibility: HeaderVisibility? public var createIntermediateGroups: Bool? public var attributes: [String] public var resourceTags: [String] public var inferDestinationFiltersByPath: Bool? public var destinationFilters: [SupportedDestination]? public enum HeaderVisibility: String { case `public` case `private` case project public var settingName: String { switch self { case .public: return "Public" case .private: return "Private" case .project: return "Project" } } } public init( path: String, name: String? = nil, group: String? = nil, compilerFlags: [String] = [], excludes: [String] = [], includes: [String] = [], explicitFolders: [String] = [], type: SourceType? = nil, optional: Bool = optionalDefault, buildPhase: BuildPhaseSpec? = nil, headerVisibility: HeaderVisibility? = nil, createIntermediateGroups: Bool? = nil, attributes: [String] = [], resourceTags: [String] = [], inferDestinationFiltersByPath: Bool? = nil, destinationFilters: [SupportedDestination]? = nil ) { self.path = (path as NSString).standardizingPath self.name = name self.group = group self.compilerFlags = compilerFlags self.excludes = excludes self.includes = includes self.explicitFolders = explicitFolders self.type = type self.optional = optional self.buildPhase = buildPhase self.headerVisibility = headerVisibility self.createIntermediateGroups = createIntermediateGroups self.attributes = attributes self.resourceTags = resourceTags self.inferDestinationFiltersByPath = inferDestinationFiltersByPath self.destinationFilters = destinationFilters } } extension TargetSource: ExpressibleByStringLiteral { public init(stringLiteral value: String) { self = TargetSource(path: value) } public init(extendedGraphemeClusterLiteral value: String) { self = TargetSource(path: value) } public init(unicodeScalarLiteral value: String) { self = TargetSource(path: value) } } extension TargetSource: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { path = try jsonDictionary.json(atKeyPath: "path") path = (path as NSString).standardizingPath // Done in two steps as the compiler can't figure out the types otherwise name = jsonDictionary.json(atKeyPath: "name") group = jsonDictionary.json(atKeyPath: "group") let maybeCompilerFlagsString: String? = jsonDictionary.json(atKeyPath: "compilerFlags") let maybeCompilerFlagsArray: [String]? = jsonDictionary.json(atKeyPath: "compilerFlags") compilerFlags = maybeCompilerFlagsArray ?? maybeCompilerFlagsString.map { $0.split(separator: " ").map { String($0) } } ?? [] headerVisibility = jsonDictionary.json(atKeyPath: "headerVisibility") excludes = jsonDictionary.json(atKeyPath: "excludes") ?? [] includes = jsonDictionary.json(atKeyPath: "includes") ?? [] explicitFolders = jsonDictionary.json(atKeyPath: "explicitFolders") ?? [] type = jsonDictionary.json(atKeyPath: "type") optional = jsonDictionary.json(atKeyPath: "optional") ?? TargetSource.optionalDefault if let string: String = jsonDictionary.json(atKeyPath: "buildPhase") { buildPhase = try BuildPhaseSpec(string: string) } else if let dict: JSONDictionary = jsonDictionary.json(atKeyPath: "buildPhase") { buildPhase = try BuildPhaseSpec(jsonDictionary: dict) } createIntermediateGroups = jsonDictionary.json(atKeyPath: "createIntermediateGroups") attributes = jsonDictionary.json(atKeyPath: "attributes") ?? [] resourceTags = jsonDictionary.json(atKeyPath: "resourceTags") ?? [] inferDestinationFiltersByPath = jsonDictionary.json(atKeyPath: "inferDestinationFiltersByPath") if let destinationFilters: [SupportedDestination] = jsonDictionary.json(atKeyPath: "destinationFilters") { self.destinationFilters = destinationFilters } } } extension TargetSource: JSONEncodable { public func toJSONValue() -> Any { var dict: [String: Any?] = [ "compilerFlags": compilerFlags, "excludes": excludes, "includes": includes, "explicitFolders": explicitFolders, "name": name, "group": group, "headerVisibility": headerVisibility?.rawValue, "type": type?.rawValue, "buildPhase": buildPhase?.toJSONValue(), "createIntermediateGroups": createIntermediateGroups, "resourceTags": resourceTags, "path": path, "inferDestinationFiltersByPath": inferDestinationFiltersByPath, "destinationFilters": destinationFilters?.map { $0.rawValue }, ] if optional != TargetSource.optionalDefault { dict["optional"] = optional } return dict } } extension TargetSource: PathContainer { static var pathProperties: [PathProperty] { [ .string("path"), ] } } ================================================ FILE: Sources/ProjectSpec/Template.swift ================================================ import Foundation import JSONUtilities struct TemplateStructure { let baseKey: String let templatesKey: String let nameToReplace: String } extension Target { static func resolveTargetTemplates(jsonDictionary: JSONDictionary) -> JSONDictionary { resolveTemplates(jsonDictionary: jsonDictionary, templateStructure: TemplateStructure(baseKey: "targets", templatesKey: "targetTemplates", nameToReplace: "target_name")) } } extension Scheme { static func resolveSchemeTemplates(jsonDictionary: JSONDictionary) -> JSONDictionary { resolveTemplates(jsonDictionary: jsonDictionary, templateStructure: TemplateStructure(baseKey: "schemes", templatesKey: "schemeTemplates", nameToReplace: "scheme_name")) } } private func resolveTemplates(jsonDictionary: JSONDictionary, templateStructure: TemplateStructure) -> JSONDictionary { guard var baseDictionary: [String: JSONDictionary] = jsonDictionary[templateStructure.baseKey] as? [String: JSONDictionary] else { return jsonDictionary } let templatesDictionary: [String: JSONDictionary] = jsonDictionary[templateStructure.templatesKey] as? [String: JSONDictionary] ?? [:] // Recursively collects all nested template names of a given dictionary. func collectTemplates(of jsonDictionary: JSONDictionary, into allTemplates: inout [String], insertAt insertionIndex: inout Int) { guard let templates = jsonDictionary["templates"] as? [String] else { return } for template in templates where !allTemplates.contains(template) { guard let templateDictionary = templatesDictionary[template] else { continue } allTemplates.insert(template, at: insertionIndex) collectTemplates(of: templateDictionary, into: &allTemplates, insertAt: &insertionIndex) insertionIndex += 1 } } for (referenceName, var reference) in baseDictionary { var templates: [String] = [] var index: Int = 0 collectTemplates(of: reference, into: &templates, insertAt: &index) if !templates.isEmpty { var mergedDictionary: JSONDictionary = [:] for template in templates { if let templateDictionary = templatesDictionary[template] { mergedDictionary = templateDictionary.merged(onto: mergedDictionary) } } reference = reference.merged(onto: mergedDictionary) reference = reference.expand(variables: [templateStructure.nameToReplace: referenceName]) if let templateAttributes = reference["templateAttributes"] as? [String: String] { reference = reference.expand(variables: templateAttributes) } } baseDictionary[referenceName] = reference } var jsonDictionary = jsonDictionary jsonDictionary[templateStructure.baseKey] = baseDictionary return jsonDictionary } ================================================ FILE: Sources/ProjectSpec/TestPlan.swift ================================================ import Foundation import JSONUtilities public struct TestPlan: Hashable { public var path: String public var defaultPlan: Bool public init(path: String, defaultPlan: Bool = false) { self.defaultPlan = defaultPlan self.path = path } } extension TestPlan: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { path = try jsonDictionary.json(atKeyPath: "path") defaultPlan = jsonDictionary.json(atKeyPath: "defaultPlan") ?? false } } extension TestPlan: JSONEncodable { public func toJSONValue() -> Any { [ "path": path, "defaultPlan": defaultPlan, ] as [String : Any] } } extension TestPlan: PathContainer { static var pathProperties: [PathProperty] { [ .string("path"), ] } } ================================================ FILE: Sources/ProjectSpec/TestTargeReference.swift ================================================ import Foundation import JSONUtilities public struct TestableTargetReference: Hashable { public var name: String public var location: Location public var targetReference: TargetReference { switch location { case .local: return TargetReference(name: name, location: .local) case .project(let projectName): return TargetReference(name: name, location: .project(projectName)) case .package: fatalError("Package target is only available for testable") } } public enum Location: Hashable { case local case project(String) case package(String) } public init(name: String, location: Location) { self.name = name self.location = location } } extension TestableTargetReference { public init(_ string: String) throws { let paths = string.split(separator: "/") switch paths.count { case 2: location = .project(String(paths[0])) name = String(paths[1]) case 1: location = .local name = String(paths[0]) default: throw SpecParsingError.invalidTargetReference(string) } } public static func local(_ name: String) -> TestableTargetReference { TestableTargetReference(name: name, location: .local) } public static func project(_ name: String) -> TestableTargetReference { let paths = name.split(separator: "/") return TestableTargetReference(name: String(paths[1]), location: .project(String(paths[0]))) } public static func package(_ name: String) -> TestableTargetReference { let paths = name.split(separator: "/") return TestableTargetReference(name: String(paths[1]), location: .package(String(paths[0]))) } } extension TestableTargetReference: ExpressibleByStringLiteral { public init(stringLiteral value: String) { try! self.init(value) } } extension TestableTargetReference: CustomStringConvertible { public var reference: String { switch location { case .local: return name case .project(let root), .package(let root): return "\(root)/\(name)" } } public var description: String { reference } } extension TestableTargetReference: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { if let project: String = jsonDictionary.json(atKeyPath: "project") { let paths = project.split(separator: "/") name = String(paths[1]) location = .project(String(paths[0])) } else if let project: String = jsonDictionary.json(atKeyPath: "package") { let paths = project.split(separator: "/") name = String(paths[1]) location = .package(String(paths[0])) } else { name = try jsonDictionary.json(atKeyPath: "local") location = .local } } } extension TestableTargetReference: JSONEncodable { public func toJSONValue() -> Any { var dictionary: JSONDictionary = [:] switch self.location { case .package(let packageName): dictionary["package"] = "\(packageName)/\(name)" case .project(let projectName): dictionary["project"] = "\(projectName)/\(name)" case .local: dictionary["local"] = name } return dictionary } } ================================================ FILE: Sources/ProjectSpec/VersionExtensions.swift ================================================ // // File.swift // // // Created by Yonas Kolb on 7/2/20. // import Foundation import Version extension Version: Swift.ExpressibleByStringLiteral { public static func parse(_ string: String) throws -> Version { if let version = Version(tolerant: string) { return version } else { throw SpecParsingError.invalidVersion(string) } } public static func parse(_ double: Double) throws -> Version { return try Version.parse(String(double)) } public init(stringLiteral value: String) { self.init(tolerant: value)! } } ================================================ FILE: Sources/ProjectSpec/XCProjExtensions.swift ================================================ import Foundation import PathKit import XcodeProj extension PBXProductType { init?(string: String) { if let type = PBXProductType(rawValue: string) { self = type } else if let type = PBXProductType(rawValue: "com.apple.product-type.\(string)") { self = type } else { return nil } } public var isFramework: Bool { self == .framework || self == .staticFramework } public var isLibrary: Bool { self == .staticLibrary || self == .dynamicLibrary } public var isExtension: Bool { fileExtension == "appex" } public var isSystemExtension: Bool { fileExtension == "dext" || fileExtension == "systemextension" } public var isApp: Bool { fileExtension == "app" } public var isTest: Bool { fileExtension == "xctest" } public var isExecutable: Bool { isApp || isExtension || isSystemExtension || isTest || self == .commandLineTool } public var name: String { rawValue.replacingOccurrences(of: "com.apple.product-type.", with: "") } public var canSkipCompileSourcesBuildPhase: Bool { switch self { case .bundle, .watch2App, .stickerPack, .messagesApplication: // Bundles, watch apps, sticker packs and simple messages applications without sources should not include a // compile sources build phase. Doing so can cause Xcode to produce an error on build. return true default: return false } } /// Function to determine when a dependendency should be embedded into the target public func shouldEmbed(_ dependencyTarget: Target) -> Bool { switch dependencyTarget.defaultLinkage { case .static: // Static dependencies should never embed return false case .dynamic, .none: if isApp { // If target is an app, all dependencies should be embed (unless they're static) return true } else if isTest, [.framework, .bundle].contains(dependencyTarget.type) { // If target is test, some dependencies should be embed (depending on their type) return true } else { // If none of the above, do not embed the dependency return false } } } } extension Platform { public var emoji: String { switch self { case .auto: return "🤖" case .iOS: return "📱" case .watchOS: return "⌚️" case .tvOS: return "📺" case .macOS: return "🖥" case .visionOS: return "🕶️" } } } extension ProjectTarget { public var shouldExecuteOnLaunch: Bool { // This is different from `type.isExecutable`, because we don't want to "run" a test type.isApp || type.isExtension || type.isSystemExtension || type == .commandLineTool } } extension XCScheme.CommandLineArguments { // Dictionary is a mapping from argument name and if it is enabled by default public convenience init(_ dict: [String: Bool]) { let args = dict.map { tuple in XCScheme.CommandLineArguments.CommandLineArgument(name: tuple.key, enabled: tuple.value) }.sorted { $0.name < $1.name } self.init(arguments: args) } } extension BreakpointExtensionID { init(string: String) throws { if let id = BreakpointExtensionID(rawValue: "Xcode.Breakpoint.\(string)Breakpoint") { self = id } else if let id = BreakpointExtensionID(rawValue: string) { self = id } else { throw SpecParsingError.unknownBreakpointType(string) } } } extension BreakpointActionExtensionID { init(string: String) throws { if let type = BreakpointActionExtensionID(rawValue: "Xcode.BreakpointAction.\(string)") { self = type } else if let type = BreakpointActionExtensionID(rawValue: string) { self = type } else { throw SpecParsingError.unknownBreakpointActionType(string) } } } ================================================ FILE: Sources/ProjectSpec/Yaml.swift ================================================ import Foundation import PathKit import Yams public func loadYamlDictionary(path: Path) throws -> [String: Any] { let string: String = try path.read() if string == "" { return [:] } let resolver = Resolver.default .removing(.null) // remove rule so that empty quotes are treated as empty strings guard let yaml = try Yams.load(yaml: string, resolver) else { return [:] } return yaml as? [String: Any] ?? [:] } public func dumpYamlDictionary(_ dictionary: [String: Any], path: Path) throws { let uncluttered = (dictionary as [String: Any?]).removingEmptyArraysDictionariesAndNils() let string: String = try Yams.dump(object: uncluttered) try path.write(string) } ================================================ FILE: Sources/TestSupport/TestHelpers.swift ================================================ import Foundation import PathKit import Spectre import XcodeProj import XCTest public let fixturePath = Path(#file).parent().parent().parent() + "Tests/Fixtures" public func doThrowing(file: String = #file, line: Int = #line, _ closure: () throws -> T) throws -> T { do { return try closure() } catch { throw failure(String(describing: error), file: file, line: line) } } public func unwrap(_ value: T?, file: String = #file, line: Int = #line) throws -> T { if let value = value { return value } else { throw failure("Expected non-nil value of \(T.self)", file: file, line: line) } } public func expectError(_ expectedError: T, function: String = #function, file: String = #file, line: Int = #line, _ closure: () throws -> Void) throws where T: CustomStringConvertible { do { try closure() } catch let error as T { try expect(error.description, file: file, line: line, function: function) == expectedError.description return } catch { throw failure("Supposed to fail with \"\(expectedError)\"", function: function, file: file, line: line) } throw failure("Supposed to fail with \"\(expectedError)\"", function: function, file: file, line: line) } struct ExpectationFailure: FailureType { let file: String let line: Int let function: String let reason: String init(reason: String, file: String, line: Int, function: String) { self.reason = reason self.file = file self.line = line self.function = function } } open class ArrayExpectation: ExpectationType { public typealias ValueType = [T] public let expression: () throws -> ValueType? let file: String let line: Int let function: String open var to: ArrayExpectation { self } init(file: String, line: Int, function: String, expression: @escaping () throws -> ValueType?) { self.file = file self.line = line self.function = function self.expression = expression } open func failure(_ reason: String) -> FailureType { ExpectationFailure(reason: reason, file: file, line: line, function: function) } } public func expect(_ expression: @autoclosure @escaping () throws -> [T]?, file: String = #file, line: Int = #line, function: String = #function) -> ArrayExpectation { ArrayExpectation(file: file, line: line, function: function, expression: expression) } extension ArrayExpectation { public func contains(_ predicate: (T) throws -> Bool) throws { let value = try expression() if let value = value { if try !value.contains(where: predicate) { throw failure("value does not contain item: \(value)") } } } } extension ArrayExpectation where T: Named { public func contains(name: String) throws { let value = try expression() if let value = value { if !value.contains(where: { $0.name == name }) { throw failure("Array does not contain item with name \(name)") } } } } public protocol Named { var name: String { get } } extension XCBuildConfiguration: Named {} extension PBXNativeTarget: Named {} extension XCScheme: Named {} extension XCTestCase { public func describe(_ name: StaticString = #function, _ test: (ContextType) -> Void) { var name = String(describing: name) if name.hasPrefix("test") { name = String(name.suffix(name.count - "test".count)) name = name.replacingOccurrences(of: "_", with: " ") } if name.hasSuffix("()") { name = String(name.prefix(name.count - 2)) } describe(name, test) } } public func skipIfNecessary() throws { #if os(Linux) && swift(<6.0.2) // https://github.com/swiftlang/swift-foundation/pull/1002 throw XCTSkip("Skipping test on Linux until PropertyListDecoder issues are fixed.") #endif } ================================================ FILE: Sources/XcodeGen/main.swift ================================================ import Foundation import ProjectSpec import XcodeGenCLI import Version let version = Version("2.45.3") let cli = XcodeGenCLI(version: version) cli.execute() ================================================ FILE: Sources/XcodeGenCLI/Arguments.swift ================================================ import Foundation import PathKit import SwiftCLI extension Path: SwiftCLI.ConvertibleFromString { public init?(input: String) { self.init(input) } } ================================================ FILE: Sources/XcodeGenCLI/Commands/CacheCommand.swift ================================================ import Foundation import PathKit import ProjectSpec import SwiftCLI import XcodeGenKit import XcodeProj import Version class CacheCommand: ProjectCommand { @Key("--cache-path", description: "Where the cache file will be loaded from and save to. Defaults to ~/.xcodegen/cache/{SPEC_PATH_HASH}") var cacheFilePath: Path? init(version: Version) { super.init(version: version, name: "cache", shortDescription: "Write the project cache") } override func execute(specLoader: SpecLoader, projectSpecPath: Path, project: Project) throws { let cacheFilePath = self.cacheFilePath ?? Path("~/.xcodegen/cache/\(projectSpecPath.absolute().string.md5)").absolute() var cacheFile: CacheFile? // generate cache do { cacheFile = try specLoader.generateCacheFile() } catch { throw GenerationError.projectSpecParsingError(error) } // write cache if let cacheFile = cacheFile { do { try cacheFilePath.parent().mkpath() try cacheFilePath.write(cacheFile.string) success("Wrote cache to \(cacheFilePath)") } catch { info("Failed to write cache: \(error.localizedDescription)") } } } } ================================================ FILE: Sources/XcodeGenCLI/Commands/DumpCommand.swift ================================================ import Foundation import SwiftCLI import PathKit import ProjectSpec import Yams import Version import XcodeGenKit class DumpCommand: ProjectCommand { @Key("--type", "-t", description: "The type of dump to output. Either \(DumpType.allCases.map { "\"\($0.rawValue)\"" }.joined(separator: ", ")). Defaults to \(DumpType.defaultValue.rawValue). The \"parsed\" types parse the project into swift and then back again.") private var dumpType: DumpType? @Key("--file", "-f", description: "The path of a file to write to. If not supplied will output to stdout") private var file: Path? init(version: Version) { super.init(version: version, name: "dump", shortDescription: "Dumps the resolved project spec to stdout or a file") } override func execute(specLoader: SpecLoader, projectSpecPath: Path, project: Project) throws { let type = dumpType ?? .defaultValue let output: String switch type { case .swiftDump: var string = "" dump(project, to: &string) output = string case .json: let data = try JSONSerialization.data(withJSONObject: specLoader.projectDictionary!, options: .prettyPrinted) output = String(data: data, encoding: .utf8)! case .yaml: output = try Yams.dump(object: specLoader.projectDictionary!) case .parsedJSON: let data = try JSONSerialization.data(withJSONObject: project.toJSONDictionary(), options: .prettyPrinted) output = String(data: data, encoding: .utf8)! case .parsedYaml: output = try Yams.dump(object: project.toJSONDictionary()) case .summary: output = project.debugDescription } if let file = file { try file.parent().mkpath() try file.write(output) } else { success(output) } } } private enum DumpType: String, ConvertibleFromString, CaseIterable { case swiftDump = "swift-dump" case json case yaml case parsedJSON = "parsed-json" case parsedYaml = "parsed-yaml" case summary static var defaultValue: DumpType { .yaml } } ================================================ FILE: Sources/XcodeGenCLI/Commands/GenerateCommand.swift ================================================ import Foundation import PathKit import ProjectSpec import SwiftCLI import XcodeGenKit import XcodeProj import Version class GenerateCommand: ProjectCommand { @Flag("-c", "--use-cache", description: "Use a cache for the xcodegen spec. This will prevent unnecessarily generating the project if nothing has changed") var useCache: Bool @Key("--cache-path", description: "Where the cache file will be loaded from and save to. Defaults to ~/.xcodegen/cache/{SPEC_PATH_HASH}") var cacheFilePath: Path? @Key("-p", "--project", description: "The path to the directory where the project should be generated. Defaults to the directory the spec is in. The filename is defined in the project spec") var projectDirectory: Path? @Flag("--only-plists", description: "Generate only plist files") var onlyPlists: Bool init(version: Version) { super.init(version: version, name: "generate", shortDescription: "Generate an Xcode project from a spec") } override func execute(specLoader: SpecLoader, projectSpecPath: Path, project: Project) throws { let projectDirectory = self.projectDirectory?.absolute() ?? projectSpecPath.parent() // validate project dictionary do { try specLoader.validateProjectDictionaryWarnings() } catch { warning("\(error)") } let projectPath = projectDirectory + "\(project.name).xcodeproj" // run pre gen command before we use the cache as the scripts may change it if let command = project.options.preGenCommand { try Task.run(bash: command, directory: projectDirectory.absolute().string) } let cacheFilePath = self.cacheFilePath ?? Path("~/.xcodegen/cache/\(projectSpecPath.absolute().string.md5)").absolute() var cacheFile: CacheFile? // generate cache if useCache || self.cacheFilePath != nil { do { cacheFile = try specLoader.generateCacheFile() } catch { throw GenerationError.projectSpecParsingError(error) } } let projectExists = XcodeProj.pbxprojPath(projectPath).exists // check cache if let cacheFile = cacheFile, projectExists, cacheFilePath.exists { do { let existingCacheFile: String = try cacheFilePath.read() if cacheFile.string == existingCacheFile { info("Project \(project.name) has not changed since cache was written") return } } catch { info("Couldn't load cache at \(cacheFile)") } } // validate project do { try project.validateMinimumXcodeGenVersion(version) try project.validate() } catch let error as SpecValidationError { throw GenerationError.validationError(error) } // generate plists info("⚙️ Generating plists...") let fileWriter = FileWriter(project: project) do { try fileWriter.writePlists() if onlyPlists { return } } catch { throw GenerationError.writingError(error) } // generate project info("⚙️ Generating project...") let xcodeProject: XcodeProj do { let projectGenerator = ProjectGenerator(project: project) guard let userName = ProcessInfo.processInfo.environment["USER"] else { throw GenerationError.missingUsername } xcodeProject = try projectGenerator.generateXcodeProject(in: projectDirectory, userName: userName) } catch { throw GenerationError.generationError(error) } // write project info("⚙️ Writing project...") do { try fileWriter.writeXcodeProject(xcodeProject, to: projectPath) success("Created project at \(projectPath)") } catch { throw GenerationError.writingError(error) } // write cache if let cacheFile = cacheFile { do { try cacheFilePath.parent().mkpath() try cacheFilePath.write(cacheFile.string) } catch { info("Failed to write cache: \(error.localizedDescription)") } } // run post gen command if let command = project.options.postGenCommand { try Task.run(bash: command, directory: projectDirectory.absolute().string) } } } ================================================ FILE: Sources/XcodeGenCLI/Commands/ProjectCommand.swift ================================================ import Foundation import SwiftCLI import ProjectSpec import XcodeGenKit import PathKit import XcodeGenCore import Version class ProjectCommand: Command { let version: Version let name: String let shortDescription: String @Flag("-q", "--quiet", description: "Suppress all informational and success output") var quiet: Bool @Key("-s", "--spec", description: "The path to the project spec file. Defaults to project.yml. (It is also possible to link to multiple spec files by comma separating them. Note that all other flags will be the same.)") var spec: String? @Key("-r", "--project-root", description: "The path to the project root directory. Defaults to the directory containing the project spec.") var projectRoot: Path? @Flag("-n", "--no-env", description: "Disable environment variable expansions") var disableEnvExpansion: Bool init(version: Version, name: String, shortDescription: String) { self.version = version self.name = name self.shortDescription = shortDescription } func execute() throws { var projectSpecs: [Path] = [] if let spec = spec { projectSpecs = spec.components(separatedBy: ",").map { Path($0).absolute() } } else { projectSpecs = [ Path("project.yml").absolute() ] } for projectSpecPath in projectSpecs { if !projectSpecPath.exists { throw GenerationError.missingProjectSpec(projectSpecPath) } let specLoader = SpecLoader(version: version) let project: Project let variables: [String: String] = disableEnvExpansion ? [:] : ProcessInfo.processInfo.environment do { project = try specLoader.loadProject(path: projectSpecPath, projectRoot: projectRoot, variables: variables) } catch { throw GenerationError.projectSpecParsingError(error) } try execute(specLoader: specLoader, projectSpecPath: projectSpecPath, project: project) } } func execute(specLoader: SpecLoader, projectSpecPath: Path, project: Project) throws {} func info(_ string: String) { if !quiet { stdout.print(string) } } func warning(_ string: String) { if !quiet { stdout.print(string.yellow) } } func success(_ string: String) { if !quiet { stdout.print(string.green) } } } ================================================ FILE: Sources/XcodeGenCLI/GenerationError.swift ================================================ import Foundation import PathKit import ProjectSpec import Rainbow import SwiftCLI enum GenerationError: Error, CustomStringConvertible, ProcessError { case missingProjectSpec(Path) case projectSpecParsingError(Error) case cacheGenerationError(Error) case validationError(SpecValidationError) case generationError(Error) case missingUsername case writingError(Error) var description: String { switch self { case let .missingProjectSpec(path): return "No project spec found at \(path.absolute())" case let .projectSpecParsingError(error): return "Parsing project spec failed: \(error)" case let .cacheGenerationError(error): return "Couldn't generate cache file: \(error)" case let .validationError(error): return error.description case let .generationError(error): return String(describing: error) case .missingUsername: return "Couldn't find current username" case let .writingError(error): return String(describing: error) } } var message: String? { description.red } var exitStatus: Int32 { 1 } } ================================================ FILE: Sources/XcodeGenCLI/XcodeGenCLI.swift ================================================ import Foundation import ProjectSpec import SwiftCLI import Version public class XcodeGenCLI { let cli: CLI public init(version: Version) { let generateCommand = GenerateCommand(version: version) cli = CLI( name: "xcodegen", version: version.description, description: "Generates Xcode projects", commands: [ generateCommand, CacheCommand(version: version), DumpCommand(version: version), ] ) cli.parser.routeBehavior = .searchWithFallback(generateCommand) } public func execute(arguments: [String]? = nil) { let status: Int32 if let arguments = arguments { status = cli.go(with: arguments) } else { status = cli.go() } exit(status) } } ================================================ FILE: Sources/XcodeGenCore/ArrayExtensions.swift ================================================ import Foundation public extension Array { func parallelMap(transform: (Element) -> T) -> [T] { var result = ContiguousArray(repeating: nil, count: count) return result.withUnsafeMutableBufferPointer { buffer in DispatchQueue.concurrentPerform(iterations: buffer.count) { idx in buffer[idx] = transform(self[idx]) } return buffer.map { $0! } } } } /// Holds a sorted array, created from specified sequence /// This structure is needed for the cases, when some part of application requires array to be sorted, but don't trust any inputs :) public struct SortedArray { public let value: Array public init(_ value: S) where S.Element == T { self.value = value.sorted() } } public extension SortedArray { /// Returns the first index in which an element of the collection satisfies the given predicate. /// The collection assumed to be sorted. If collection is not have sorted values the result is undefined. /// /// The idea is to get first index of a function for which the given predicate evaluates to true. /// /// let values = [1,2,3,4,5] /// let idx = values.firstIndexAssumingSorted(where: { $0 > 3 }) /// /// // false, false, false, true, true /// // ^ /// // therefore idx == 3 /// /// - Parameter predicate: A closure that takes an element as its argument /// and returns a Boolean value that indicates whether the passed element /// represents a match. /// /// - Returns: The index of the first element for which `predicate` returns /// `true`. If no elements in the collection satisfy the given predicate, /// returns `nil`. /// /// - Complexity: O(log(*n*)), where *n* is the length of the collection. @inlinable func firstIndex(where predicate: (T) throws -> Bool) rethrows -> Int? { // Predicate should divide a collection to two pairs of values // "bad" values for which predicate returns `false`` // "good" values for which predicate return `true` // false false false false false true true true // ^ // The idea is to get _first_ index which for which the predicate returns `true` let lastIndex = value.count // The index that represents where bad values start var badIndex = -1 // The index that represents where good values start var goodIndex = lastIndex var midIndex = (badIndex + goodIndex) / 2 while badIndex + 1 < goodIndex { if try predicate(value[midIndex]) { goodIndex = midIndex } else { badIndex = midIndex } midIndex = (badIndex + goodIndex) / 2 } // We're out of bounds, no good items in array if midIndex == lastIndex || goodIndex == lastIndex { return nil } return goodIndex } } ================================================ FILE: Sources/XcodeGenCore/Atomic.swift ================================================ // // Atomic.swift // // // Created by Vladislav Lisianskii on 23.02.2022. // import Foundation @propertyWrapper public final class Atomic { private var value: Value private let queue = DispatchQueue( label: "com.xcodegencore.atomic.\(UUID().uuidString)", qos: .utility, attributes: .concurrent, autoreleaseFrequency: .inherit, target: .global() ) public init(wrappedValue: Value) { self.value = wrappedValue } public var wrappedValue: Value { get { queue.sync { value } } set { queue.async(flags: .barrier) { [weak self] in self?.value = newValue } } } /// Allows us to get the actual `Atomic` instance with the $ /// prefix. public var projectedValue: Atomic { return self } /// Modifies the protected value using `closure`. public func with( _ closure: (inout Value) throws -> R ) rethrows -> R { try queue.sync(flags: .barrier) { try closure(&value) } } } ================================================ FILE: Sources/XcodeGenCore/Glob.swift ================================================ // // Created by Eric Firestone on 3/22/16. // Copyright © 2016 Square, Inc. All rights reserved. // Released under the Apache v2 License. // // Adapted from https://gist.github.com/blakemerryman/76312e1cbf8aec248167 // Adapted from https://gist.github.com/efirestone/ce01ae109e08772647eb061b3bb387c3 import Foundation public let GlobBehaviorBashV3 = Glob.Behavior( supportsGlobstar: false, includesFilesFromRootOfGlobstar: false, includesDirectoriesInResults: true, includesFilesInResultsIfTrailingSlash: false ) public let GlobBehaviorBashV4 = Glob.Behavior( supportsGlobstar: true, // Matches Bash v4 with "shopt -s globstar" option includesFilesFromRootOfGlobstar: true, includesDirectoriesInResults: true, includesFilesInResultsIfTrailingSlash: false ) public let GlobBehaviorGradle = Glob.Behavior( supportsGlobstar: true, includesFilesFromRootOfGlobstar: true, includesDirectoriesInResults: false, includesFilesInResultsIfTrailingSlash: true ) /** Finds files on the file system using pattern matching. */ public class Glob: Collection { /** * Different glob implementations have different behaviors, so the behavior of this * implementation is customizable. */ public struct Behavior { // If true then a globstar ("**") causes matching to be done recursively in subdirectories. // If false then "**" is treated the same as "*" let supportsGlobstar: Bool // If true the results from the directory where the globstar is declared will be included as well. // For example, with the pattern "dir/**/*.ext" the fie "dir/file.ext" would be included if this // property is true, and would be omitted if it's false. let includesFilesFromRootOfGlobstar: Bool // If false then the results will not include directory entries. This does not affect recursion depth. let includesDirectoriesInResults: Bool // If false and the last characters of the pattern are "**/" then only directories are returned in the results. let includesFilesInResultsIfTrailingSlash: Bool } public static var defaultBehavior = GlobBehaviorBashV4 public static let defaultBlacklistedDirectories = ["node_modules", "Pods"] @Atomic private var isDirectoryCache = [String: Bool]() public let behavior: Behavior public let blacklistedDirectories: [String] var paths = [String]() public var startIndex: Int { paths.startIndex } public var endIndex: Int { paths.endIndex } /// Initialize a glob /// /// - Parameters: /// - pattern: The pattern to use when building the list of matching directories. /// - behavior: See individual descriptions on `Glob.Behavior` values. /// - blacklistedDirectories: An array of directories to ignore at the root level of the project. public init(pattern: String, behavior: Behavior = Glob.defaultBehavior, blacklistedDirectories: [String] = defaultBlacklistedDirectories) { self.behavior = behavior self.blacklistedDirectories = blacklistedDirectories var adjustedPattern = pattern let hasTrailingGlobstarSlash = pattern.hasSuffix("**/") var includeFiles = !hasTrailingGlobstarSlash if behavior.includesFilesInResultsIfTrailingSlash { includeFiles = true if hasTrailingGlobstarSlash { // Grab the files too. adjustedPattern += "*" } } let patterns = behavior.supportsGlobstar ? expandGlobstar(pattern: adjustedPattern) : [adjustedPattern] #if os(macOS) paths = patterns.parallelMap { paths(usingPattern: $0, includeFiles: includeFiles) }.flatMap { $0 } #else // Parallel invocations of Glob on Linux seems to be causing unexpected crashes paths = patterns.map { paths(usingPattern: $0, includeFiles: includeFiles) }.flatMap { $0 } #endif paths = Array(Set(paths)).sorted { lhs, rhs in lhs.compare(rhs) != ComparisonResult.orderedDescending } clearCaches() } // MARK: Subscript Support public subscript(i: Int) -> String { paths[i] } // MARK: Protocol of IndexableBase public func index(after i: Int) -> Int { i + 1 } // MARK: Private private var globalFlags = GLOB_TILDE | GLOB_BRACE | GLOB_MARK private func executeGlob(pattern: UnsafePointer, gt: UnsafeMutablePointer) -> Bool { glob(pattern, globalFlags, nil, gt) == 0 } private func expandGlobstar(pattern: String) -> [String] { guard pattern.contains("**") else { return [pattern] } var results = [String]() var parts = pattern.components(separatedBy: "**") let firstPart = parts.removeFirst() var lastPart = parts.joined(separator: "**") var directories: [URL] if FileManager.default.fileExists(atPath: firstPart) { do { directories = try exploreDirectories(path: firstPart) } catch { directories = [] print("Error parsing file system item: \(error)") } } else { directories = [] } if behavior.includesFilesFromRootOfGlobstar { // Check the base directory for the glob star as well. directories.insert(URL(fileURLWithPath: firstPart), at: 0) // Include the globstar root directory ("dir/") in a pattern like "dir/**" or "dir/**/" if lastPart.isEmpty { results.append(firstPart) } } if lastPart.isEmpty { lastPart = "*" } for directory in directories { let partiallyResolvedPattern = directory.appendingPathComponent(lastPart) let standardizedPattern = (partiallyResolvedPattern.relativePath as NSString).standardizingPath results.append(contentsOf: expandGlobstar(pattern: standardizedPattern)) } return results } private func exploreDirectories(path: String) throws -> [URL] { try FileManager.default.contentsOfDirectory(atPath: path) .compactMap { subpath -> [URL]? in if blacklistedDirectories.contains(subpath) { return nil } let firstLevel = URL(fileURLWithPath: path).appendingPathComponent(subpath, isDirectory: true) guard isDirectory(path: firstLevel.path) else { return nil } var subDirs: [URL] = try FileManager.default.subpathsOfDirectory(atPath: firstLevel.path) .compactMap { subpath -> URL? in let full = firstLevel.appendingPathComponent(subpath, isDirectory: true) return isDirectory(path: full.path) ? full : nil } subDirs.append(firstLevel) return subDirs } .joined() .array() } private func isDirectory(path: String) -> Bool { if let isDirectory = isDirectoryCache[path] { return isDirectory } var isDirectoryBool = ObjCBool(false) let isDirectory = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectoryBool) && isDirectoryBool.boolValue $isDirectoryCache.with { isDirectoryCache in isDirectoryCache[path] = isDirectory } return isDirectory } private func clearCaches() { $isDirectoryCache.with { isDirectoryCache in isDirectoryCache.removeAll() } } private func paths(usingPattern pattern: String, includeFiles: Bool) -> [String] { var gt = glob_t() defer { globfree(>) } if executeGlob(pattern: pattern, gt: >) { return populateFiles(gt: gt, includeFiles: includeFiles) } return [] } private func populateFiles(gt: glob_t, includeFiles: Bool) -> [String] { var paths = [String]() let includeDirectories = behavior.includesDirectoriesInResults #if os(macOS) let matches = Int(gt.gl_matchc) #else let matches = Int(gt.gl_pathc) #endif for i in 0.. [Element] { Array(self) } } ================================================ FILE: Sources/XcodeGenCore/MD5.swift ================================================ // To date, adding CommonCrypto to a Swift framework is problematic. See: // http://stackoverflow.com/questions/25248598/importing-commoncrypto-in-a-swift-framework // We're using a subset and modified version of CryptoSwift as an alternative. // The following is an altered source version that only includes MD5. The original software can be found at: // https://github.com/krzyzanowskim/CryptoSwift // This is the original copyright notice: /* Copyright (C) 2014 Marcin Krzyżanowski This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. - This notice may not be removed or altered from any source or binary distribution. */ import Foundation extension String { public var md5: String { if let data = data(using: .utf8, allowLossyConversion: true) { let message = data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) -> [UInt8] in Array(bytes) } let MD5Calculator = MD5(message) let MD5Data = MD5Calculator.calculate() var MD5String = String() for c in MD5Data { MD5String += String(format: "%02x", c) } return MD5String } else { return self } } } /** array of bytes, little-endian representation */ func arrayOfBytes(_ value: T, length: Int? = nil) -> [UInt8] { let totalBytes = length ?? (MemoryLayout.size * 8) let valuePointer = UnsafeMutablePointer.allocate(capacity: 1) valuePointer.pointee = value let bytes = valuePointer.withMemoryRebound(to: UInt8.self, capacity: totalBytes) { (bytesPointer) -> [UInt8] in var bytes = [UInt8](repeating: 0, count: totalBytes) for j in 0...size, totalBytes) { bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee } return bytes } #if swift(>=4.1) valuePointer.deinitialize(count: 1) valuePointer.deallocate() #else valuePointer.deinitialize() valuePointer.deallocate(capacity: 1) #endif return bytes } extension Int { /** Array of bytes with optional padding (little-endian) */ func bytes(_ totalBytes: Int = MemoryLayout.size) -> [UInt8] { arrayOfBytes(self, length: totalBytes) } } extension NSMutableData { /** Convenient way to append bytes */ func appendBytes(_ arrayOfBytes: [UInt8]) { append(arrayOfBytes, length: arrayOfBytes.count) } } protocol HashProtocol { var message: [UInt8] { get } /** Common part for hash calculation. Prepare header data. */ func prepare(_ len: Int) -> [UInt8] } extension HashProtocol { func prepare(_ len: Int) -> [UInt8] { var tmpMessage = message // Step 1. Append Padding Bits tmpMessage.append(0x80) // append one bit (UInt8 with one bit) to message // append "0" bit until message length in bits ≡ 448 (mod 512) var msgLength = tmpMessage.count var counter = 0 while msgLength % len != (len - 8) { counter += 1 msgLength += 1 } tmpMessage += [UInt8](repeating: 0, count: counter) return tmpMessage } } func toUInt32Array(_ slice: ArraySlice) -> [UInt32] { var result = [UInt32]() result.reserveCapacity(16) for idx in stride(from: slice.startIndex, to: slice.endIndex, by: MemoryLayout.size) { let d0 = UInt32(slice[idx.advanced(by: 3)]) << 24 let d1 = UInt32(slice[idx.advanced(by: 2)]) << 16 let d2 = UInt32(slice[idx.advanced(by: 1)]) << 8 let d3 = UInt32(slice[idx]) let val: UInt32 = d0 | d1 | d2 | d3 result.append(val) } return result } struct BytesIterator: IteratorProtocol { let chunkSize: Int let data: [UInt8] init(chunkSize: Int, data: [UInt8]) { self.chunkSize = chunkSize self.data = data } var offset = 0 mutating func next() -> ArraySlice? { let end = min(chunkSize, data.count - offset) let result = data[offset.. 0 ? result : nil } } struct BytesSequence: Sequence { let chunkSize: Int let data: [UInt8] func makeIterator() -> BytesIterator { BytesIterator(chunkSize: chunkSize, data: data) } } func rotateLeft(_ value: UInt32, bits: UInt32) -> UInt32 { ((value << bits) & 0xFFFF_FFFF) | (value >> (32 - bits)) } class MD5: HashProtocol { static let size = 16 // 128 / 8 let message: [UInt8] init(_ message: [UInt8]) { self.message = message } /** specifies the per-round shift amounts */ private let shifts: [UInt32] = [ 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, ] /** binary integer part of the sines of integers (Radians) */ private let sines: [UInt32] = [ 0xD76A_A478, 0xE8C7_B756, 0x2420_70DB, 0xC1BD_CEEE, 0xF57C_0FAF, 0x4787_C62A, 0xA830_4613, 0xFD46_9501, 0x6980_98D8, 0x8B44_F7AF, 0xFFFF_5BB1, 0x895C_D7BE, 0x6B90_1122, 0xFD98_7193, 0xA679_438E, 0x49B4_0821, 0xF61E_2562, 0xC040_B340, 0x265E_5A51, 0xE9B6_C7AA, 0xD62F_105D, 0x0244_1453, 0xD8A1_E681, 0xE7D3_FBC8, 0x21E1_CDE6, 0xC337_07D6, 0xF4D5_0D87, 0x455A_14ED, 0xA9E3_E905, 0xFCEF_A3F8, 0x676F_02D9, 0x8D2A_4C8A, 0xFFFA_3942, 0x8771_F681, 0x6D9D_6122, 0xFDE5_380C, 0xA4BE_EA44, 0x4BDE_CFA9, 0xF6BB_4B60, 0xBEBF_BC70, 0x289B_7EC6, 0xEAA1_27FA, 0xD4EF_3085, 0x4881D05, 0xD9D4_D039, 0xE6DB_99E5, 0x1FA2_7CF8, 0xC4AC_5665, 0xF429_2244, 0x432A_FF97, 0xAB94_23A7, 0xFC93_A039, 0x655B_59C3, 0x8F0C_CC92, 0xFFEF_F47D, 0x8584_5DD1, 0x6FA8_7E4F, 0xFE2C_E6E0, 0xA301_4314, 0x4E08_11A1, 0xF753_7E82, 0xBD3A_F235, 0x2AD7_D2BB, 0xEB86_D391, ] private let hashes: [UInt32] = [0x6745_2301, 0xEFCD_AB89, 0x98BA_DCFE, 0x1032_5476] func calculate() -> [UInt8] { var tmpMessage = prepare(64) tmpMessage.reserveCapacity(tmpMessage.count + 4) // hash values var hh = hashes // Step 2. Append Length a 64-bit representation of lengthInBits let lengthInBits = (message.count * 8) let lengthBytes = lengthInBits.bytes(64 / 8) tmpMessage += lengthBytes.reversed() // Process the message in successive 512-bit chunks: let chunkSizeBytes = 512 / 8 // 64 for chunk in BytesSequence(chunkSize: chunkSizeBytes, data: tmpMessage) { // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15 let M = toUInt32Array(chunk) assert(M.count == 16, "Invalid array") // Initialize hash value for this chunk: var A: UInt32 = hh[0] var B: UInt32 = hh[1] var C: UInt32 = hh[2] var D: UInt32 = hh[3] var dTemp: UInt32 = 0 // Main loop for j in 0..> 8) & 0xFF) let r3 = UInt8((itemLE >> 16) & 0xFF) let r4 = UInt8((itemLE >> 24) & 0xFF) result += [r1, r2, r3, r4] } return result } } // swiftlint:enable all ================================================ FILE: Sources/XcodeGenCore/PathExtensions.swift ================================================ import Foundation import PathKit extension Path { /// Returns a Path without any inner parent directory references. /// /// Similar to `NSString.standardizingPath`, but works with relative paths. /// /// ### Examples /// - `a/b/../c` simplifies to `a/c` /// - `../a/b` simplifies to `../a/b` /// - `a/../../c` simplifies to `../c` public func simplifyingParentDirectoryReferences() -> Path { if !string.contains("..") { // Skip simplifying if its already simple var string = self.string while string.hasSuffix(Path.separator) { // Remove all trailing path separators string.removeLast() } return Path(String(string)) } return normalize().components.reduce(Path(), +) } /// Returns the relative path necessary to go from `base` to `self`. /// /// Both paths must be absolute or relative paths. /// - throws: Throws an error when the path types do not match, or when `base` has so many parent path components /// that it refers to an unknown parent directory. public func relativePath(from base: Path) throws -> Path { enum PathArgumentError: Error { /// Can't back out of an unknown parent directory case unknownParentDirectory /// It's impossible to determine the path between an absolute and a relative path case unmatchedAbsolutePath } func pathComponents(for path: ArraySlice, relativeTo base: ArraySlice, memo: [String]) throws -> [String] { switch (base.first, path.first) { // Base case: Paths are equivalent case (.none, .none): return memo // No path to backtrack from case (.none, .some(let rhs)): guard rhs != "." else { // Skip . instead of appending it return try pathComponents(for: path.dropFirst(), relativeTo: base, memo: memo) } return try pathComponents(for: path.dropFirst(), relativeTo: base, memo: memo + [rhs]) // Both sides have a common parent case (.some(let lhs), .some(let rhs)) where memo.isEmpty && lhs == rhs: return try pathComponents(for: path.dropFirst(), relativeTo: base.dropFirst(), memo: memo) // `base` has a path to back out of case (.some(let lhs), _): guard lhs != ".." else { throw PathArgumentError.unknownParentDirectory } guard lhs != "." else { // Skip . instead of resolving it to .. return try pathComponents(for: path, relativeTo: base.dropFirst(), memo: memo) } return try pathComponents(for: path, relativeTo: base.dropFirst(), memo: memo + [".."]) } } guard isAbsolute && base.isAbsolute || !isAbsolute && !base.isAbsolute else { throw PathArgumentError.unmatchedAbsolutePath } return Path(components: try pathComponents(for: ArraySlice(simplifyingParentDirectoryReferences().components), relativeTo: ArraySlice(base.simplifyingParentDirectoryReferences().components), memo: [])) } /// Returns whether `self` is a strict parent of `child`. /// /// Both paths must be asbolute or relative paths. public func isParent(of child: Path) throws -> Bool { let relativePath = try child.relativePath(from: self) return relativePath.components.allSatisfy { $0 != ".." } } } ================================================ FILE: Sources/XcodeGenCore/StringDiff.swift ================================================ import Foundation // https://gist.github.com/kristopherjohnson/543687c763cd6e524c91 /// Find first differing character between two strings /// /// :param: s1 First String /// :param: s2 Second String /// /// :returns: .DifferenceAtIndex(i) or .NoDifference public func firstDifferenceBetweenStrings(_ s1: String, _ s2: String) -> FirstDifferenceResult { let len1 = s1.count let len2 = s2.count let lenMin = min(len1, len2) for i in 0.. String { let firstDifferenceResult = firstDifferenceBetweenStrings(s1, s2) func diffString(at index: Int, _ s1: String, _ s2: String) -> String { let markerArrow = "\u{2b06}" // "⬆" let ellipsis = "\u{2026}" // "…" /// Given a string and a range, return a string representing that substring. /// /// If the range starts at a position other than 0, an ellipsis /// will be included at the beginning. /// /// If the range ends before the actual end of the string, /// an ellipsis is added at the end. func windowSubstring(_ s: String, _ range: NSRange) -> String { let validRange = NSMakeRange(range.location, min(range.length, s.count - range.location)) let substring = (s as NSString).substring(with: validRange) let prefix = range.location > 0 ? ellipsis : "" let suffix = (s.count - range.location > range.length) ? ellipsis : "" return "\(prefix)\(substring)\(suffix)" } // Show this many characters before and after the first difference let windowLength = previewPrefixLength + 1 + previewSuffixLength let windowIndex = max(index - previewPrefixLength, 0) let windowRange = NSMakeRange(windowIndex, windowLength) let sub1 = windowSubstring(s1, windowRange) let sub2 = windowSubstring(s2, windowRange) let markerPosition = min(previewSuffixLength, index) + (windowIndex > 0 ? 1 : 0) let markerPrefix = String(repeating: " ", count: markerPosition) let markerLine = "\(markerPrefix)\(markerArrow)" return "Difference at index \(index):\n\(sub1)\n\(sub2)\n\(markerLine)" } switch firstDifferenceResult { case .NoDifference: return "No difference" case let .DifferenceAtIndex(index): return diffString(at: index, s1, s2) } } /// Result type for firstDifferenceBetweenStrings() public enum FirstDifferenceResult { /// Strings are identical case NoDifference /// Strings differ at the specified index. /// /// This could mean that characters at the specified index are different, /// or that one string is longer than the other case DifferenceAtIndex(Int) } ================================================ FILE: Sources/XcodeGenKit/BreakpointGenerator.swift ================================================ import Foundation import ProjectSpec import XcodeProj public class BreakpointGenerator { let project: Project public init(project: Project) { self.project = project } func generateBreakpointList() throws -> XCBreakpointList? { let breakpoints = project.breakpoints guard !breakpoints.isEmpty else { return nil } return XCBreakpointList(type: "4", version: "2.0", breakpoints: try breakpoints.map({ try generateBreakpointProxy($0) })) } private func generateBreakpointProxy(_ breakpoint: Breakpoint) throws -> XCBreakpointList.BreakpointProxy { let breakpointExtensionID: BreakpointExtensionID var filePath: String? var line: String? var column: String? var scope: String? var stopOnStyle: String? var symbol: String? var module: String? switch breakpoint.type { case let .file(path, lineNumber, columnNumber): breakpointExtensionID = .file filePath = path line = String(lineNumber) column = columnNumber.map(String.init) case let .exception(exception): breakpointExtensionID = .exception scope = exception.scope.rawValue stopOnStyle = exception.stopOnStyle.rawValue case .swiftError: breakpointExtensionID = .swiftError case .openGLError: breakpointExtensionID = .openGLError case let .symbolic(symbolName, moduleName): breakpointExtensionID = .symbolic symbol = symbolName module = moduleName case .ideConstraintError: breakpointExtensionID = .ideConstraintError case .ideTestFailure: breakpointExtensionID = .ideTestFailure case .runtimeIssue: breakpointExtensionID = .runtimeIssue } let xcbreakpoint = XCBreakpointList.BreakpointProxy.BreakpointContent( enabled: breakpoint.enabled, ignoreCount: String(breakpoint.ignoreCount), continueAfterRunningActions: breakpoint.continueAfterRunningActions, filePath: filePath, startingColumn: column, endingColumn: column, startingLine: line, endingLine: line, symbol: symbol, module: module, scope: scope, stopOnStyle: stopOnStyle, condition: breakpoint.condition, actions: try breakpoint.actions.map { try generateBreakpointActionProxy($0) } ) return XCBreakpointList.BreakpointProxy( breakpointExtensionID: breakpointExtensionID, breakpointContent: xcbreakpoint ) } private func generateBreakpointActionProxy(_ breakpointAction: Breakpoint.Action) throws -> XCBreakpointList.BreakpointProxy.BreakpointContent.BreakpointActionProxy { let actionExtensionID: BreakpointActionExtensionID var consoleCommand: String? var message: String? var conveyanceType: String? var command: String? var arguments: String? var waitUntilDone: Bool? var script: String? var soundName: String? switch breakpointAction { case let .debuggerCommand(command): actionExtensionID = .debuggerCommand consoleCommand = command case let .log(log): actionExtensionID = .log message = log.message conveyanceType = log.conveyanceType.rawValue case let .shellCommand(commandPath, commandArguments, waitUntilCommandDone): actionExtensionID = .shellCommand command = commandPath arguments = commandArguments waitUntilDone = waitUntilCommandDone case .graphicsTrace: actionExtensionID = .graphicsTrace case let .appleScript(appleScript): actionExtensionID = .appleScript script = appleScript case let .sound(sound): actionExtensionID = .sound soundName = sound.rawValue } let xcaction = XCBreakpointList.BreakpointProxy.BreakpointContent.BreakpointActionProxy.ActionContent( consoleCommand: consoleCommand, message: message, conveyanceType: conveyanceType, command: command, arguments: arguments, waitUntilDone: waitUntilDone, script: script, soundName: soundName ) return XCBreakpointList.BreakpointProxy.BreakpointContent.BreakpointActionProxy( actionExtensionID: actionExtensionID, actionContent: xcaction ) } } ================================================ FILE: Sources/XcodeGenKit/CarthageDependencyResolver.swift ================================================ // // CarthageDependencyResolver.swift // XcodeGenKit // // Created by Rogerio de Paula Assis on 2/4/19. // import Foundation import ProjectSpec import PathKit public struct ResolvedCarthageDependency: Equatable, Hashable { let dependency: Dependency let isFromTopLevelTarget: Bool } public class CarthageDependencyResolver { static func getBuildPath(_ project: Project) -> String { return project.options.carthageBuildPath ?? "Carthage/Build" } /// Carthage's base build path as specified by the /// project's `SpecOptions`, or `Carthage/Build` by default var buildPath: String { return CarthageDependencyResolver.getBuildPath(project) } /// Carthage's executable path as specified by the /// project's `SpecOptions`, or `carthage` by default var executable: String { project.options.carthageExecutablePath ?? "carthage" } private let project: Project let versionLoader: CarthageVersionLoader init(project: Project) { self.project = project versionLoader = CarthageVersionLoader(buildPath: project.basePath + CarthageDependencyResolver.getBuildPath(project)) } /// Carthage's build path for the given platform func buildPath(for platform: Platform, linkType: Dependency.CarthageLinkType) -> String { switch linkType { case .static: return "\(buildPath)/\(platform.carthageName)/Static" case .dynamic: return "\(buildPath)/\(platform.carthageName)" } } /// Fetches all carthage dependencies for a given target func dependencies(for topLevelTarget: Target) -> [ResolvedCarthageDependency] { // this is used to resolve cyclical target dependencies var visitedTargets: Set = [] var frameworks: Set = [] var isTopLevelTarget = true var queue: [ProjectTarget] = [topLevelTarget] while !queue.isEmpty { // projectTarget is not the top level target after the first loop ends defer { isTopLevelTarget = false } let projectTarget = queue.removeFirst() if visitedTargets.contains(projectTarget.name) { continue } if let target = projectTarget as? Target { for dependency in target.dependencies { if case (false, false) = (dependency.link, dependency.embed ?? topLevelTarget.shouldEmbedCarthageDependencies) { continue } guard !frameworks.contains(where: { $0.dependency == dependency }) else { continue } switch dependency.type { case .carthage(let findFrameworks, _): let findFrameworks = findFrameworks ?? project.options.findCarthageFrameworks if findFrameworks { relatedDependencies(for: dependency, in: target.platform) .filter { dependency in !frameworks.contains(where: { $0.dependency == dependency }) } .forEach { frameworks.insert(.init( dependency: $0, isFromTopLevelTarget: isTopLevelTarget )) } } else { frameworks.insert(.init( dependency: dependency, isFromTopLevelTarget: isTopLevelTarget )) } case .target: if let projectTarget = project.getProjectTarget(dependency.reference) { if let dependencyTarget = projectTarget as? Target { if topLevelTarget.platform == dependencyTarget.platform { queue.append(projectTarget) } } else { queue.append(projectTarget) } } default: break } } } else if let aggregateTarget = projectTarget as? AggregateTarget { for dependencyName in aggregateTarget.targets { if let projectTarget = project.getProjectTarget(dependencyName) { queue.append(projectTarget) } } } visitedTargets.update(with: projectTarget.name) } return frameworks.sorted(by: { $0.dependency.reference < $1.dependency.reference }) } /// Reads the .version file generated for a given Carthage dependency /// and returns a list of its related dependencies including self func relatedDependencies(for dependency: Dependency, in platform: Platform) -> [Dependency] { guard case .carthage = dependency.type, let versionFile = try? versionLoader.getVersionFile(for: dependency.reference) else { // No .version file or we've been unable to parse // so fail gracefully by returning the main dependency return [dependency] } return versionFile.frameworks(for: platform) .map { Dependency( type: dependency.type, reference: $0, embed: dependency.embed, codeSign: dependency.codeSign, link: dependency.link, implicit: dependency.implicit, weakLink: dependency.weakLink ) } .sorted(by: { $0.reference < $1.reference }) } } extension Platform { public var carthageName: String { switch self { case .auto: // This is a dummy value return "auto" case .iOS: return "iOS" case .tvOS: return "tvOS" case .watchOS: return "watchOS" case .macOS: return "Mac" case .visionOS: // This is a dummy value because Carthage doesn't support visionOS. return "visionOS" } } } ================================================ FILE: Sources/XcodeGenKit/CarthageVersionLoader.swift ================================================ // // CarthageVersionLoader.swift // XcodeGenKit // // Created by Yonas Kolb on 24/3/19. // import Foundation import PathKit import ProjectSpec class Mutex { var value: T var semaphore: DispatchSemaphore = DispatchSemaphore(value: 1) init(_ value: T) { self.value = value } func get(closure: (inout T) throws -> (U)) rethrows -> U { semaphore.wait() defer { semaphore.signal() } return try closure(&value) } func get(closure: (inout T) -> Void) { semaphore.wait() closure(&value) semaphore.signal() } } // Note: this class can be accessed on multiple threads. It must therefore stay thread-safe. class CarthageVersionLoader { private let buildPath: Path private var cachedFilesMutex: Mutex<[String: CarthageVersionFile]> = Mutex([:]) init(buildPath: Path) { self.buildPath = buildPath } func getVersionFile(for dependency: String) throws -> CarthageVersionFile { return try cachedFilesMutex.get { cachedFiles in if let versionFile = cachedFiles[dependency] { return versionFile } let filePath = buildPath + ".\(dependency).version" let data = try filePath.read() let carthageVersionFile = try JSONDecoder().decode(CarthageVersionFile.self, from: data) cachedFiles[dependency] = carthageVersionFile return carthageVersionFile } } } struct CarthageVersionFile: Decodable { private struct Reference: Decodable, Equatable { public let name: String public let hash: String } private let data: [Platform: [String]] internal init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: Platform.self) data = try Platform.allCases.reduce(into: [:]) { data, platform in let references = try container.decodeIfPresent([Reference].self, forKey: platform) ?? [] let frameworks = Set(references.map { $0.name }).sorted() data[platform] = frameworks } } } extension Platform: Swift.CodingKey { public var stringValue: String { carthageName } } extension CarthageVersionFile { func frameworks(for platform: Platform) -> [String] { data[platform] ?? [] } } ================================================ FILE: Sources/XcodeGenKit/FileWriter.swift ================================================ import Foundation import PathKit import ProjectSpec import XcodeProj public class FileWriter { let project: Project public init(project: Project) { self.project = project } public func writeXcodeProject(_ xcodeProject: XcodeProj, to projectPath: Path? = nil) throws { let projectPath = projectPath ?? project.defaultProjectPath let tempPath = try Path.processUniqueTemporary() + "XcodeGen" try? tempPath.delete() if projectPath.exists { try projectPath.copy(tempPath) } try xcodeProject.write(path: tempPath, override: true) try? projectPath.delete() try tempPath.copy(projectPath) try? tempPath.delete() } public func writePlists() throws { let infoPlistGenerator = InfoPlistGenerator() for target in project.targets { // write Info.plist if let plist = target.info { let properties = infoPlistGenerator.generateProperties(for: target).merged(plist.properties) try writePlist(properties, path: plist.path) } // write entitlements if let plist = target.entitlements { try writePlist(plist.properties, path: plist.path) } } } private func writePlist(_ plist: [String: Any], path: String) throws { let path = project.basePath + path if path.exists, let data: Data = try? path.read(), let existingPlist = (try? PropertyListSerialization.propertyList(from: data, format: nil)) as? [String: Any], NSDictionary(dictionary: plist).isEqual(to: existingPlist) { // file is the same return } let data = try PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0) try? path.delete() try path.parent().mkpath() try path.write(data) } } ================================================ FILE: Sources/XcodeGenKit/InfoPlistGenerator.swift ================================================ import Foundation import PathKit import ProjectSpec public class InfoPlistGenerator { /** Default info plist attributes taken from: /Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/Project Templates/Base/Base_DefinitionsInfoPlist.xctemplate/TemplateInfo.plist */ private func generateDefaultInfoPlist(for target: Target) -> [String: Any] { var dictionary: [String: Any] = [:] dictionary["CFBundleIdentifier"] = "$(PRODUCT_BUNDLE_IDENTIFIER)" dictionary["CFBundleInfoDictionaryVersion"] = "6.0" dictionary["CFBundleName"] = "$(PRODUCT_NAME)" dictionary["CFBundleDevelopmentRegion"] = "$(DEVELOPMENT_LANGUAGE)" dictionary["CFBundleShortVersionString"] = "1.0" dictionary["CFBundleVersion"] = "1" // Bundles should not contain any CFBundleExecutable otherwise they will be rejected when uploading. if target.type != .bundle { dictionary["CFBundleExecutable"] = "$(EXECUTABLE_NAME)" } return dictionary } public func generateProperties(for target: Target) -> [String: Any] { var targetInfoPlist = generateDefaultInfoPlist(for: target) switch target.type { case .uiTestBundle, .unitTestBundle: targetInfoPlist["CFBundlePackageType"] = "BNDL" case .application, .watch2App: targetInfoPlist["CFBundlePackageType"] = "APPL" case .framework: targetInfoPlist["CFBundlePackageType"] = "FMWK" case .bundle: targetInfoPlist["CFBundlePackageType"] = "BNDL" case .xpcService, .appExtension: targetInfoPlist["CFBundlePackageType"] = "XPC!" default: break } return targetInfoPlist } } ================================================ FILE: Sources/XcodeGenKit/PBXProjGenerator.swift ================================================ import Foundation import PathKit import ProjectSpec import XcodeProj import Yams import Version public class PBXProjGenerator { let project: Project let pbxProj: PBXProj let projectDirectory: Path? let carthageResolver: CarthageDependencyResolver public static let copyFilesActionMask: UInt = 8 var sourceGenerator: SourceGenerator! var targetObjects: [String: PBXTarget] = [:] var targetAggregateObjects: [String: PBXAggregateTarget] = [:] var targetFileReferences: [String: PBXFileReference] = [:] var sdkFileReferences: [String: PBXFileReference] = [:] var packageReferences: [String: XCRemoteSwiftPackageReference] = [:] var localPackageReferences: [String: XCLocalSwiftPackageReference] = [:] var carthageFrameworksByPlatform: [String: Set] = [:] var frameworkFiles: [PBXFileElement] = [] var bundleFiles: [PBXFileElement] = [] var generated = false private var projects: [ProjectReference: PBXProj] = [:] public init(project: Project, projectDirectory: Path? = nil) { self.project = project carthageResolver = CarthageDependencyResolver(project: project) pbxProj = PBXProj(rootObject: nil, objectVersion: project.objectVersion) self.projectDirectory = projectDirectory sourceGenerator = SourceGenerator(project: project, pbxProj: pbxProj, projectDirectory: projectDirectory) } @discardableResult func addObject(_ object: T, context: String? = nil) -> T { pbxProj.add(object: object) object.context = context return object } public func generate() throws -> PBXProj { if generated { fatalError("Cannot use PBXProjGenerator to generate more than once") } generated = true for group in project.fileGroups { try sourceGenerator.getFileGroups(path: group) } let buildConfigs: [XCBuildConfiguration] = project.configs.map { config in let buildSettings = project.getProjectBuildSettings(config: config) var baseConfiguration: PBXFileReference? if let configPath = project.configFiles[config.name], let fileReference = sourceGenerator.getContainedFileReference(path: project.basePath + configPath) as? PBXFileReference { baseConfiguration = fileReference } let buildConfig = addObject( XCBuildConfiguration( name: config.name, buildSettings: buildSettings ) ) buildConfig.baseConfiguration = baseConfiguration return buildConfig } let configName = project.options.defaultConfig ?? buildConfigs.first?.name ?? "" let buildConfigList = addObject( XCConfigurationList( buildConfigurations: buildConfigs, defaultConfigurationName: configName ) ) var derivedGroups: [PBXGroup] = [] let mainGroup = addObject( PBXGroup( children: [], sourceTree: .group, usesTabs: project.options.usesTabs, indentWidth: project.options.indentWidth, tabWidth: project.options.tabWidth ) ) let developmentRegion = project.options.developmentLanguage ?? "en" let pbxProject = addObject( PBXProject( name: project.name, buildConfigurationList: buildConfigList, compatibilityVersion: project.compatibilityVersion, preferredProjectObjectVersion: project.preferredProjectObjectVersion.map { Int($0) }, minimizedProjectReferenceProxies: project.minimizedProjectReferenceProxies, mainGroup: mainGroup, developmentRegion: developmentRegion ) ) pbxProj.rootObject = pbxProject for target in project.targets { let targetObject: PBXTarget if target.isLegacy { targetObject = PBXLegacyTarget( name: target.name, buildToolPath: target.legacy?.toolPath, buildArgumentsString: target.legacy?.arguments, passBuildSettingsInEnvironment: target.legacy?.passSettings ?? false, buildWorkingDirectory: target.legacy?.workingDirectory, buildPhases: [] ) } else { targetObject = PBXNativeTarget(name: target.name, buildPhases: []) } targetObjects[target.name] = addObject(targetObject) var explicitFileType: String? var lastKnownFileType: String? let fileType = Xcode.fileType(path: Path(target.filename), productType: target.type) if target.platform == .macOS || target.platform == .watchOS || target.type == .framework || target.type == .extensionKitExtension { explicitFileType = fileType } else { lastKnownFileType = fileType } if !target.isLegacy { let fileReference = addObject( PBXFileReference( sourceTree: .buildProductsDir, explicitFileType: explicitFileType, lastKnownFileType: lastKnownFileType, path: target.filename, includeInIndex: false ), context: target.name ) targetFileReferences[target.name] = fileReference } } for target in project.aggregateTargets { let aggregateTarget = addObject( PBXAggregateTarget( name: target.name, productName: target.name ) ) targetAggregateObjects[target.name] = aggregateTarget } for (name, package) in project.packages { switch package { case let .remote(url, versionRequirement): let packageReference = XCRemoteSwiftPackageReference(repositoryURL: url, versionRequirement: versionRequirement) packageReferences[name] = packageReference addObject(packageReference) case let .local(path, group, excludeFromProject): let packageReference = XCLocalSwiftPackageReference(relativePath: path) localPackageReferences[name] = packageReference if !excludeFromProject { addObject(packageReference) try sourceGenerator.createLocalPackage(path: Path(path), group: group.map { Path($0) }) } } } let productGroup = addObject( PBXGroup( children: targetFileReferences.valueArray, sourceTree: .group, name: "Products" ) ) derivedGroups.append(productGroup) pbxProject.productsGroup = productGroup let sortedProjectReferences = project.projectReferences.sorted { $0.name < $1.name } let subprojectFileReferences: [PBXFileReference] = sortedProjectReferences.map { projectReference in let projectPath = Path(projectReference.path) return addObject( PBXFileReference( sourceTree: .group, name: projectReference.name, lastKnownFileType: Xcode.fileType(path: projectPath), path: projectPath.normalize().string ) ) } if subprojectFileReferences.count > 0 { let subprojectsGroups = addObject( PBXGroup( children: subprojectFileReferences, sourceTree: .group, name: "Projects" ) ) derivedGroups.append(subprojectsGroups) let subprojects: [[String: PBXFileElement]] = subprojectFileReferences.map { projectReference in let group = addObject( PBXGroup( children: [], sourceTree: .group, name: "Products" ) ) return [ "ProductGroup": group, "ProjectRef": projectReference, ] } pbxProject.projects = subprojects } try project.targets.forEach(generateTarget) try project.aggregateTargets.forEach(generateAggregateTarget) if !carthageFrameworksByPlatform.isEmpty { var platforms: [PBXGroup] = [] for (platform, files) in carthageFrameworksByPlatform { let platformGroup: PBXGroup = addObject( PBXGroup( children: Array(files), sourceTree: .group, path: platform ) ) platforms.append(platformGroup) } let carthageGroup = addObject( PBXGroup( children: platforms, sourceTree: .group, name: "Carthage", path: carthageResolver.buildPath ) ) frameworkFiles.append(carthageGroup) } if !frameworkFiles.isEmpty { let group = addObject( PBXGroup( children: frameworkFiles, sourceTree: .group, name: "Frameworks" ) ) derivedGroups.append(group) } if !bundleFiles.isEmpty { let group = addObject( PBXGroup( children: bundleFiles, sourceTree: .group, name: "Bundles" ) ) derivedGroups.append(group) } mainGroup.children = Array(sourceGenerator.rootGroups) sortGroups(group: mainGroup) setupGroupOrdering(group: mainGroup) // add derived groups at the end derivedGroups.forEach(sortGroups) mainGroup.children += derivedGroups .sorted(by: PBXFileElement.sortByNamePath) .map { $0 } let assetTags = Set(project.targets .map { target in target.sources.map { $0.resourceTags }.flatMap { $0 } }.flatMap { $0 } ).sorted() var projectAttributes: [String: ProjectAttribute] = [ "BuildIndependentTargetsInParallel": "YES" ] for (key, value) in project.attributes { projectAttributes[key] = ProjectAttribute(any: value) } // Set default LastUpgradeCheck if user did not specify a valid string value let lastUpgradeKey = "LastUpgradeCheck" if !(project.attributes[lastUpgradeKey] is String) { projectAttributes[lastUpgradeKey] = .string(project.xcodeVersion) } if !assetTags.isEmpty { projectAttributes["knownAssetTags"] = .array(assetTags) } var knownRegions = Set(sourceGenerator.knownRegions) knownRegions.insert(developmentRegion) if project.options.useBaseInternationalization { knownRegions.insert("Base") } pbxProject.knownRegions = knownRegions.sorted() pbxProject.remotePackages = packageReferences.sorted { $0.key < $1.key }.map { $1 } pbxProject.localPackages = localPackageReferences.sorted { $0.key < $1.key }.map { $1 } let allTargets: [PBXTarget] = targetObjects.valueArray + targetAggregateObjects.valueArray pbxProject.targets = allTargets .sorted { $0.name < $1.name } pbxProject.attributes = projectAttributes pbxProject.targetAttributes = generateTargetAttributes() return pbxProj } func generateAggregateTarget(_ target: AggregateTarget) throws { let aggregateTarget = targetAggregateObjects[target.name]! let configs: [XCBuildConfiguration] = project.configs.map { config in let buildSettings = project.getBuildSettings(settings: target.settings, config: config) var baseConfiguration: PBXFileReference? if let configPath = target.configFiles[config.name] { baseConfiguration = sourceGenerator.getContainedFileReference(path: project.basePath + configPath) as? PBXFileReference } let buildConfig = XCBuildConfiguration( name: config.name, baseConfiguration: baseConfiguration, buildSettings: buildSettings ) return addObject(buildConfig) } var dependencies = target.targets.map { generateTargetDependency(from: target.name, to: $0, platform: nil, platforms: nil) } let defaultConfigurationName = project.options.defaultConfig ?? project.configs.first?.name ?? "" let buildConfigList = addObject(XCConfigurationList( buildConfigurations: configs, defaultConfigurationName: defaultConfigurationName )) var buildPhases: [PBXBuildPhase] = [] buildPhases += try target.buildScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) } let packagePluginDependencies = makePackagePluginDependency(for: target) dependencies.append(contentsOf: packagePluginDependencies) aggregateTarget.buildPhases = buildPhases aggregateTarget.buildConfigurationList = buildConfigList aggregateTarget.dependencies = dependencies } func generateTargetDependency(from: String, to target: String, platform: String?, platforms: [String]?) -> PBXTargetDependency { guard let targetObject = targetObjects[target] ?? targetAggregateObjects[target] else { fatalError("Target dependency not found: from ( \(from) ) to ( \(target) )") } let targetProxy = addObject( PBXContainerItemProxy( containerPortal: .project(pbxProj.rootObject!), remoteGlobalID: .object(targetObject), proxyType: .nativeTarget, remoteInfo: target ) ) let targetDependency = addObject( PBXTargetDependency( platformFilter: platform, platformFilters: platforms, target: targetObject, targetProxy: targetProxy ) ) return targetDependency } func generateExternalTargetDependency(from: String, to target: String, in project: String, platform: Platform) throws -> (PBXTargetDependency, Target, PBXReferenceProxy) { guard let projectReference = self.project.getProjectReference(project) else { fatalError("project '\(project)' not found") } let pbxProj = try getPBXProj(from: projectReference) guard let targetObject = pbxProj.targets(named: target).first else { fatalError("target '\(target)' not found in project '\(project)'") } let projectFileReferenceIndex = self.pbxProj.rootObject! .projects .map { $0["ProjectRef"] as? PBXFileReference } .firstIndex { $0?.path == Path(projectReference.path).normalize().string } guard let index = projectFileReferenceIndex, let projectFileReference = self.pbxProj.rootObject?.projects[index]["ProjectRef"] as? PBXFileReference, let productsGroup = self.pbxProj.rootObject?.projects[index]["ProductGroup"] as? PBXGroup else { fatalError("Missing subproject file reference") } let targetProxy = addObject( PBXContainerItemProxy( containerPortal: .fileReference(projectFileReference), remoteGlobalID: .object(targetObject), proxyType: .nativeTarget, remoteInfo: target ) ) let productProxy = PBXContainerItemProxy( containerPortal: .fileReference(projectFileReference), remoteGlobalID: targetObject.product.flatMap(PBXContainerItemProxy.RemoteGlobalID.object), proxyType: .reference, remoteInfo: target ) var path = targetObject.productNameWithExtension() if targetObject.productType == .staticLibrary, let tmpPath = path, !tmpPath.hasPrefix("lib") { path = "lib\(tmpPath)" } let productReferenceProxyFileType = targetObject.productNameWithExtension() .flatMap { Xcode.fileType(path: Path($0)) } let existingValue = self.pbxProj.referenceProxies.first { referenceProxy in referenceProxy.path == path && referenceProxy.remote == productProxy && referenceProxy.sourceTree == .buildProductsDir && referenceProxy.fileType == productReferenceProxyFileType } let productReferenceProxy: PBXReferenceProxy if let existingValue = existingValue { productReferenceProxy = existingValue } else { addObject(productProxy) productReferenceProxy = addObject( PBXReferenceProxy( fileType: productReferenceProxyFileType, path: path, remote: productProxy, sourceTree: .buildProductsDir ) ) productsGroup.children.append(productReferenceProxy) } let targetDependency = addObject( PBXTargetDependency( name: targetObject.name, targetProxy: targetProxy ) ) guard let buildConfigurations = targetObject.buildConfigurationList?.buildConfigurations, let defaultConfigurationName = targetObject.buildConfigurationList?.defaultConfigurationName, let defaultConfiguration = buildConfigurations.first(where: { $0.name == defaultConfigurationName }) ?? buildConfigurations.first else { fatalError("Missing target info") } let productType: PBXProductType = targetObject.productType ?? .none let buildSettings = defaultConfiguration.buildSettings let settings = Settings(buildSettings: buildSettings, configSettings: [:], groups: []) let deploymentTargetString = buildSettings[platform.deploymentTargetSetting]?.stringValue let deploymentTarget = deploymentTargetString == nil ? nil : try Version.parse(deploymentTargetString!) let requiresObjCLinking = buildSettings["OTHER_LDFLAGS"]?.stringValue?.contains("-ObjC") ?? (productType == .staticLibrary) let dependencyTarget = Target( name: targetObject.name, type: productType, platform: platform, productName: targetObject.productName, deploymentTarget: deploymentTarget, settings: settings, requiresObjCLinking: requiresObjCLinking ) return (targetDependency, dependencyTarget, productReferenceProxy) } func generateBuildScript(targetName: String, buildScript: BuildScript) throws -> PBXShellScriptBuildPhase { let shellScript: String switch buildScript.script { case let .path(path): shellScript = try (project.basePath + path).read() case let .script(script): shellScript = script } let shellScriptPhase = PBXShellScriptBuildPhase( name: buildScript.name ?? "Run Script", inputPaths: buildScript.inputFiles, outputPaths: buildScript.outputFiles, inputFileListPaths: buildScript.inputFileLists, outputFileListPaths: buildScript.outputFileLists, shellPath: buildScript.shell ?? "/bin/sh", shellScript: shellScript, runOnlyForDeploymentPostprocessing: buildScript.runOnlyWhenInstalling, showEnvVarsInLog: buildScript.showEnvVars, alwaysOutOfDate: !buildScript.basedOnDependencyAnalysis, dependencyFile: buildScript.discoveredDependencyFile ) return addObject(shellScriptPhase) } func generateCopyFiles(targetName: String, copyFiles: BuildPhaseSpec.CopyFilesSettings, buildPhaseFiles: [PBXBuildFile]) -> PBXCopyFilesBuildPhase { let copyFilesBuildPhase = PBXCopyFilesBuildPhase( dstPath: copyFiles.subpath, dstSubfolderSpec: copyFiles.destination.destination, files: buildPhaseFiles ) return addObject(copyFilesBuildPhase) } func generateTargetAttributes() -> [PBXTarget: [String: ProjectAttribute]] { var targetAttributes: [PBXTarget: [String: ProjectAttribute]] = [:] let testTargets = pbxProj.nativeTargets.filter { $0.productType == .uiTestBundle || $0.productType == .unitTestBundle } for testTarget in testTargets { // look up TEST_TARGET_NAME build setting func testTargetName(_ target: PBXTarget) -> String? { guard let buildConfigurations = target.buildConfigurationList?.buildConfigurations else { return nil } return buildConfigurations .compactMap { $0.buildSettings["TEST_TARGET_NAME"]?.stringValue } .first } guard let name = testTargetName(testTarget) else { continue } guard let target = self.pbxProj.targets(named: name).first else { continue } targetAttributes[testTarget, default: [:]].merge(["TestTargetID": .targetReference(target)]) } func generateTargetAttributes(_ target: ProjectTarget, pbxTarget: PBXTarget) { if !target.attributes.isEmpty { targetAttributes[pbxTarget, default: [:]].merge(target.attributes.mapValues { ProjectAttribute(any: $0) }) } func getSingleBuildSetting(_ setting: String) -> String? { let settings = project.configs.compactMap { project.getCombinedBuildSetting(setting, target: target, config: $0)?.stringValue } guard settings.count == project.configs.count, let firstSetting = settings.first, settings.filter({ $0 == firstSetting }).count == settings.count else { return nil } return firstSetting } func setTargetAttribute(attribute: String, buildSetting: String) { if let setting = getSingleBuildSetting(buildSetting) { targetAttributes[pbxTarget, default: [:]].merge([attribute: .string(setting)]) } } setTargetAttribute(attribute: "ProvisioningStyle", buildSetting: "CODE_SIGN_STYLE") setTargetAttribute(attribute: "DevelopmentTeam", buildSetting: "DEVELOPMENT_TEAM") } for target in project.aggregateTargets { guard let pbxTarget = targetAggregateObjects[target.name] else { continue } generateTargetAttributes(target, pbxTarget: pbxTarget) } for target in project.targets { guard let pbxTarget = targetObjects[target.name] else { continue } generateTargetAttributes(target, pbxTarget: pbxTarget) } return targetAttributes } func sortGroups(group: PBXGroup) { // sort children let children = group.children .sorted { child1, child2 in let sortOrder1 = child1.getSortOrder(groupSortPosition: project.options.groupSortPosition) let sortOrder2 = child2.getSortOrder(groupSortPosition: project.options.groupSortPosition) if sortOrder1 != sortOrder2 { return sortOrder1 < sortOrder2 } else { if (child1.name, child1.path) != (child2.name, child2.path) { return PBXFileElement.sortByNamePath(child1, child2) } else { return child1.context ?? "" < child2.context ?? "" } } } group.children = children.filter { $0 != group } // sort sub groups let childGroups = group.children.compactMap { $0 as? PBXGroup } childGroups.forEach(sortGroups) } public func setupGroupOrdering(group: PBXGroup) { let groupOrdering = project.options.groupOrdering.first { groupOrdering in let groupName = group.nameOrPath if groupName == groupOrdering.pattern { return true } if let regex = groupOrdering.regex { return regex.isMatch(to: groupName) } return false } if let order = groupOrdering?.order { let files = group.children.filter { !$0.isGroupOrFolder } var groups = group.children.filter { $0.isGroupOrFolder } var filteredGroups = [PBXFileElement]() for groupName in order { guard let group = groups.first(where: { $0.nameOrPath == groupName }) else { continue } filteredGroups.append(group) groups.removeAll { $0 == group } } filteredGroups += groups switch project.options.groupSortPosition { case .top: group.children = filteredGroups + files case .bottom: group.children = files + filteredGroups default: break } } // sort sub groups let childGroups = group.children.compactMap { $0 as? PBXGroup } childGroups.forEach(setupGroupOrdering) } func getPBXProj(from reference: ProjectReference) throws -> PBXProj { if let cachedProject = projects[reference] { return cachedProject } let pbxproj = try XcodeProj(pathString: (project.basePath + Path(reference.path).normalize()).string).pbxproj projects[reference] = pbxproj return pbxproj } func generateTarget(_ target: Target) throws { let carthageDependencies = carthageResolver.dependencies(for: target) let infoPlistFiles: [Config: String] = getInfoPlists(for: target) let sourceFileBuildPhaseOverrideSequence: [(Path, BuildPhaseSpec)] = Set(infoPlistFiles.values).map({ (project.basePath + $0, .none) }) let sourceFileBuildPhaseOverrides = Dictionary(uniqueKeysWithValues: sourceFileBuildPhaseOverrideSequence) let sourceFiles = try sourceGenerator.getAllSourceFiles(targetType: target.type, sources: target.sources, buildPhases: sourceFileBuildPhaseOverrides) .sorted { $0.path.lastComponent < $1.path.lastComponent } var anyDependencyRequiresObjCLinking = false var dependencies: [PBXTargetDependency] = [] var targetFrameworkBuildFiles: [PBXBuildFile] = [] var frameworkBuildPaths = Set() var customCopyDependenciesReferences: [PBXBuildFile] = [] var copyFilesBuildPhasesFiles: [BuildPhaseSpec.CopyFilesSettings: [PBXBuildFile]] = [:] var copyFrameworksReferences: [PBXBuildFile] = [] var copyResourcesReferences: [PBXBuildFile] = [] var copyBundlesReferences: [PBXBuildFile] = [] var copyWatchReferences: [PBXBuildFile] = [] var packageDependencies: [XCSwiftPackageProductDependency] = [] var extensions: [PBXBuildFile] = [] var extensionKitExtensions: [PBXBuildFile] = [] var systemExtensions: [PBXBuildFile] = [] var appClips: [PBXBuildFile] = [] var carthageFrameworksToEmbed: [String] = [] var buildFileCopyPhases: [PBXBuildFile: BuildPhaseSpec.CopyFilesSettings] = [:] let targetDependencies = (target.transitivelyLinkDependencies ?? project.options.transitivelyLinkDependencies) ? getAllDependenciesPlusTransitiveNeedingEmbedding(target: target) : target.dependencies let targetSupportsDirectEmbed = !(target.platform.requiresSimulatorStripping && (target.type.isApp || target.type == .watch2Extension)) let directlyEmbedCarthage = target.directlyEmbedCarthageDependencies ?? targetSupportsDirectEmbed func getEmbedSettings(dependency: Dependency, codeSign: Bool) -> [String: BuildFileSetting] { var embedAttributes: [String] = [] if codeSign { embedAttributes.append("CodeSignOnCopy") } if dependency.removeHeaders { embedAttributes.append("RemoveHeadersOnCopy") } return ["ATTRIBUTES": .array(embedAttributes)] } func getDependencyFrameworkSettings(dependency: Dependency) -> [String: BuildFileSetting]? { var linkingAttributes: [String] = [] if dependency.weakLink { linkingAttributes.append("Weak") } return !linkingAttributes.isEmpty ? ["ATTRIBUTES": .array(linkingAttributes)] : nil } func processTargetDependency(_ dependency: Dependency, dependencyTarget: Target, embedFileReference: PBXFileElement?, platform: String?, platforms: [String]?) { let dependencyLinkage = dependencyTarget.defaultLinkage let link = dependency.link ?? ((dependencyLinkage == .dynamic && target.type != .staticLibrary) || (dependencyLinkage == .static && target.type.isExecutable)) if link, let dependencyFile = embedFileReference { let pbxBuildFile = PBXBuildFile(file: dependencyFile, settings: getDependencyFrameworkSettings(dependency: dependency)) pbxBuildFile.platformFilter = platform pbxBuildFile.platformFilters = platforms let buildFile = addObject(pbxBuildFile) targetFrameworkBuildFiles.append(buildFile) if !anyDependencyRequiresObjCLinking && dependencyTarget.requiresObjCLinking ?? (dependencyTarget.type == .staticLibrary) { anyDependencyRequiresObjCLinking = true } } let embed = dependency.embed ?? target.type.shouldEmbed(dependencyTarget) if embed { let pbxBuildFile = PBXBuildFile( file: embedFileReference, settings: getEmbedSettings(dependency: dependency, codeSign: dependency.codeSign ?? !dependencyTarget.type.isExecutable) ) pbxBuildFile.platformFilter = platform pbxBuildFile.platformFilters = platforms let embedFile = addObject(pbxBuildFile) if let copyPhase = dependency.copyPhase { // custom copy takes precedence buildFileCopyPhases[embedFile] = copyPhase customCopyDependenciesReferences.append(embedFile) } else if dependencyTarget.type.isExtension { if dependencyTarget.type == .extensionKitExtension { // embed extension kit extension extensionKitExtensions.append(embedFile) } else { // embed app extension extensions.append(embedFile) } } else if dependencyTarget.type.isSystemExtension { // embed system extension systemExtensions.append(embedFile) } else if dependencyTarget.type == .onDemandInstallCapableApplication { // embed app clip appClips.append(embedFile) } else if dependencyTarget.type.isFramework { copyFrameworksReferences.append(embedFile) } else if dependencyTarget.type.isApp && dependencyTarget.platform == .watchOS { copyWatchReferences.append(embedFile) } else if dependencyTarget.type == .xpcService { copyFilesBuildPhasesFiles[.xpcServices, default: []].append(embedFile) } else { copyResourcesReferences.append(embedFile) } } } for dependency in targetDependencies { let embed = dependency.embed ?? target.shouldEmbedDependencies let platform = makePlatformFilter(for: dependency.platformFilter) let platforms = makeDestinationFilters(for: dependency.destinationFilters) switch dependency.type { case .target: let dependencyTargetReference = try TargetReference(dependency.reference) switch dependencyTargetReference.location { case .local: let dependencyTargetName = dependency.reference let targetDependency = generateTargetDependency(from: target.name, to: dependencyTargetName, platform: platform, platforms: platforms) dependencies.append(targetDependency) guard let dependencyTarget = project.getTarget(dependencyTargetName) else { continue } processTargetDependency(dependency, dependencyTarget: dependencyTarget, embedFileReference: targetFileReferences[dependencyTarget.name], platform: platform, platforms: platforms) case .project(let dependencyProjectName): let dependencyTargetName = dependencyTargetReference.name let (targetDependency, dependencyTarget, dependencyProductProxy) = try generateExternalTargetDependency(from: target.name, to: dependencyTargetName, in: dependencyProjectName, platform: target.platform) dependencies.append(targetDependency) processTargetDependency(dependency, dependencyTarget: dependencyTarget, embedFileReference: dependencyProductProxy, platform: platform, platforms: platforms) } case .framework: if !dependency.implicit { let buildPath = Path(dependency.reference).parent().string.quoted frameworkBuildPaths.insert(buildPath) } let fileReference: PBXFileElement if dependency.implicit { fileReference = sourceGenerator.getFileReference( path: Path(dependency.reference), inPath: project.basePath, sourceTree: .buildProductsDir ) } else { fileReference = sourceGenerator.getFileReference( path: Path(dependency.reference), inPath: project.basePath ) } if dependency.link ?? (target.type != .staticLibrary) { let pbxBuildFile = PBXBuildFile(file: fileReference, settings: getDependencyFrameworkSettings(dependency: dependency)) pbxBuildFile.platformFilter = platform pbxBuildFile.platformFilters = platforms let buildFile = addObject(pbxBuildFile) targetFrameworkBuildFiles.append(buildFile) } if !frameworkFiles.contains(fileReference) { frameworkFiles.append(fileReference) } if embed { let pbxBuildFile = PBXBuildFile(file: fileReference, settings: getEmbedSettings(dependency: dependency, codeSign: dependency.codeSign ?? true)) pbxBuildFile.platformFilter = platform pbxBuildFile.platformFilters = platforms let embedFile = addObject(pbxBuildFile) if let copyPhase = dependency.copyPhase { buildFileCopyPhases[embedFile] = copyPhase customCopyDependenciesReferences.append(embedFile) } else { copyFrameworksReferences.append(embedFile) } } case .sdk(let root): var dependencyPath = Path(dependency.reference) if !dependency.reference.contains("/") { switch dependencyPath.extension ?? "" { case "framework": dependencyPath = Path("System/Library/Frameworks") + dependencyPath case "tbd": dependencyPath = Path("usr/lib") + dependencyPath case "dylib": dependencyPath = Path("usr/lib") + dependencyPath default: break } } let fileReference: PBXFileReference if let existingFileReferences = sdkFileReferences[dependency.reference] { fileReference = existingFileReferences } else { let sourceTree: PBXSourceTree if let root = root { sourceTree = .custom(root) } else { sourceTree = .sdkRoot } fileReference = addObject( PBXFileReference( sourceTree: sourceTree, name: dependencyPath.lastComponent, lastKnownFileType: Xcode.fileType(path: dependencyPath), path: dependencyPath.string ) ) sdkFileReferences[dependency.reference] = fileReference frameworkFiles.append(fileReference) } let pbxBuildFile = PBXBuildFile( file: fileReference, settings: getDependencyFrameworkSettings(dependency: dependency) ) pbxBuildFile.platformFilter = platform pbxBuildFile.platformFilters = platforms let buildFile = addObject(pbxBuildFile) targetFrameworkBuildFiles.append(buildFile) if dependency.embed == true { let pbxBuildFile = PBXBuildFile(file: fileReference, settings: getEmbedSettings(dependency: dependency, codeSign: dependency.codeSign ?? true)) pbxBuildFile.platformFilter = platform pbxBuildFile.platformFilters = platforms let embedFile = addObject(pbxBuildFile) if let copyPhase = dependency.copyPhase { buildFileCopyPhases[embedFile] = copyPhase customCopyDependenciesReferences.append(embedFile) } else { copyFrameworksReferences.append(embedFile) } } case .carthage(let findFrameworks, let linkType): let findFrameworks = findFrameworks ?? project.options.findCarthageFrameworks let allDependencies = findFrameworks ? carthageResolver.relatedDependencies(for: dependency, in: target.platform) : [dependency] allDependencies.forEach { dependency in let platformPath = Path(carthageResolver.buildPath(for: target.platform, linkType: linkType)) var frameworkPath = platformPath + dependency.reference if frameworkPath.extension == nil { frameworkPath = Path(frameworkPath.string + ".framework") } let fileReference = self.sourceGenerator.getFileReference(path: frameworkPath, inPath: platformPath) self.carthageFrameworksByPlatform[target.platform.carthageName, default: []].insert(fileReference) let isStaticLibrary = target.type == .staticLibrary let isCarthageStaticLink = dependency.carthageLinkType == .static if dependency.link ?? (!isStaticLibrary && !isCarthageStaticLink) { let pbxBuildFile = PBXBuildFile(file: fileReference, settings: getDependencyFrameworkSettings(dependency: dependency)) pbxBuildFile.platformFilter = platform pbxBuildFile.platformFilters = platforms let buildFile = addObject(pbxBuildFile) targetFrameworkBuildFiles.append(buildFile) } } // Embedding handled by iterating over `carthageDependencies` below case .package(let products): let packageReference = packageReferences[dependency.reference] // If package's reference is none and there is no specified package in localPackages, // then ignore the package specified as dependency. if packageReference == nil, localPackageReferences[dependency.reference] == nil { continue } func addPackageProductDependency(named productName: String) { let packageDependency = addObject( XCSwiftPackageProductDependency(productName: productName, package: packageReference) ) // Add package dependency if linking is true. if dependency.link ?? true { packageDependencies.append(packageDependency) } let link = dependency.link ?? (target.type != .staticLibrary) if link { let file = PBXBuildFile(product: packageDependency, settings: getDependencyFrameworkSettings(dependency: dependency)) file.platformFilter = platform file.platformFilters = platforms let buildFile = addObject(file) targetFrameworkBuildFiles.append(buildFile) } else { let targetDependency = addObject( PBXTargetDependency(platformFilter: platform, platformFilters: platforms, product: packageDependency) ) dependencies.append(targetDependency) } if dependency.embed == true { let pbxBuildFile = PBXBuildFile(product: packageDependency, settings: getEmbedSettings(dependency: dependency, codeSign: dependency.codeSign ?? true)) pbxBuildFile.platformFilter = platform pbxBuildFile.platformFilters = platforms let embedFile = addObject(pbxBuildFile) if let copyPhase = dependency.copyPhase { buildFileCopyPhases[embedFile] = copyPhase customCopyDependenciesReferences.append(embedFile) } else { copyFrameworksReferences.append(embedFile) } } } if !products.isEmpty { for product in products { addPackageProductDependency(named: product) } } else { addPackageProductDependency(named: dependency.reference) } case .bundle: // Static and dynamic libraries can't copy resources guard target.type != .staticLibrary && target.type != .dynamicLibrary else { break } let fileReference = sourceGenerator.getFileReference( path: Path(dependency.reference), inPath: project.basePath, sourceTree: .buildProductsDir ) let pbxBuildFile = PBXBuildFile( file: fileReference, settings: embed ? getEmbedSettings(dependency: dependency, codeSign: dependency.codeSign ?? true) : nil ) pbxBuildFile.platformFilter = platform pbxBuildFile.platformFilters = platforms let buildFile = addObject(pbxBuildFile) copyBundlesReferences.append(buildFile) if !bundleFiles.contains(fileReference) { bundleFiles.append(fileReference) } } } for carthageDependency in carthageDependencies { let dependency = carthageDependency.dependency let isFromTopLevelTarget = carthageDependency.isFromTopLevelTarget let embed = dependency.embed ?? target.shouldEmbedCarthageDependencies let platformPath = Path(carthageResolver.buildPath(for: target.platform, linkType: dependency.carthageLinkType ?? .default)) var frameworkPath = platformPath + dependency.reference if frameworkPath.extension == nil { frameworkPath = Path(frameworkPath.string + ".framework") } let fileReference = sourceGenerator.getFileReference(path: frameworkPath, inPath: platformPath) if dependency.carthageLinkType == .static { guard isFromTopLevelTarget else { continue } // ignore transitive dependencies if static let linkFile = addObject( PBXBuildFile(file: fileReference, settings: getDependencyFrameworkSettings(dependency: dependency)) ) targetFrameworkBuildFiles.append(linkFile) } else if embed { if directlyEmbedCarthage { let embedFile = addObject( PBXBuildFile(file: fileReference, settings: getEmbedSettings(dependency: dependency, codeSign: dependency.codeSign ?? true)) ) if let copyPhase = dependency.copyPhase { buildFileCopyPhases[embedFile] = copyPhase customCopyDependenciesReferences.append(embedFile) } else { copyFrameworksReferences.append(embedFile) } } else { carthageFrameworksToEmbed.append(dependency.reference) } } } carthageFrameworksToEmbed = carthageFrameworksToEmbed.uniqued() let packagePluginDependencies = makePackagePluginDependency(for: target) dependencies.append(contentsOf: packagePluginDependencies) var buildPhases: [PBXBuildPhase] = [] func getBuildFilesForSourceFiles(_ sourceFiles: [SourceFile]) -> [PBXBuildFile] { sourceFiles .reduce(into: [SourceFile]()) { output, sourceFile in if !output.contains(where: { $0.fileReference === sourceFile.fileReference }) { output.append(sourceFile) } } .map { addObject($0.buildFile) } } func getBuildFilesForPhase(_ buildPhase: BuildPhase) -> [PBXBuildFile] { let filteredSourceFiles = sourceFiles .filter { $0.buildPhase?.buildPhase == buildPhase } return getBuildFilesForSourceFiles(filteredSourceFiles) } func getBuildFilesForCopyFilesPhases() -> [BuildPhaseSpec.CopyFilesSettings: [PBXBuildFile]] { var sourceFilesByCopyFiles: [BuildPhaseSpec.CopyFilesSettings: [SourceFile]] = [:] for sourceFile in sourceFiles { guard case let .copyFiles(copyFilesSettings)? = sourceFile.buildPhase else { continue } sourceFilesByCopyFiles[copyFilesSettings, default: []].append(sourceFile) } return sourceFilesByCopyFiles.mapValues { getBuildFilesForSourceFiles($0) } } func getPBXCopyFilesBuildPhase(dstSubfolderSpec: PBXCopyFilesBuildPhase.SubFolder, dstPath: String = "", name: String, files: [PBXBuildFile]) -> PBXCopyFilesBuildPhase { return PBXCopyFilesBuildPhase( dstPath: dstPath, dstSubfolderSpec: dstSubfolderSpec, name: name, buildActionMask: target.onlyCopyFilesOnInstall ? PBXProjGenerator.copyFilesActionMask : PBXBuildPhase.defaultBuildActionMask, files: files, runOnlyForDeploymentPostprocessing: target.onlyCopyFilesOnInstall ? true : false ) } func splitCopyDepsByDestination(_ references: [PBXBuildFile]) -> [BuildPhaseSpec.CopyFilesSettings : [PBXBuildFile]] { var retval = [BuildPhaseSpec.CopyFilesSettings : [PBXBuildFile]]() for reference in references { guard let key = buildFileCopyPhases[reference] else { continue } var filesWithSameDestination = retval[key] ?? [PBXBuildFile]() filesWithSameDestination.append(reference) retval[key] = filesWithSameDestination } return retval } copyFilesBuildPhasesFiles.merge(getBuildFilesForCopyFilesPhases()) { $0 + $1 } buildPhases += try target.preBuildScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) } buildPhases += copyFilesBuildPhasesFiles .filter { $0.key.phaseOrder == .preCompile } .map { generateCopyFiles(targetName: target.name, copyFiles: $0, buildPhaseFiles: $1) } let headersBuildPhaseFiles = getBuildFilesForPhase(.headers) if !headersBuildPhaseFiles.isEmpty { if target.type.isFramework || target.type == .dynamicLibrary { let headersBuildPhase = addObject(PBXHeadersBuildPhase(files: headersBuildPhaseFiles)) buildPhases.append(headersBuildPhase) } else { headersBuildPhaseFiles.forEach { pbxProj.delete(object: $0) } } } func addResourcesBuildPhase() { let resourcesBuildPhaseFiles = getBuildFilesForPhase(.resources) + copyResourcesReferences let hasSynchronizedRootGroups = sourceFiles.contains { $0.fileReference is PBXFileSystemSynchronizedRootGroup } if !resourcesBuildPhaseFiles.isEmpty || hasSynchronizedRootGroups { let resourcesBuildPhase = addObject(PBXResourcesBuildPhase(files: resourcesBuildPhaseFiles)) buildPhases.append(resourcesBuildPhase) } } if target.putResourcesBeforeSourcesBuildPhase { addResourcesBuildPhase() } let sourcesBuildPhaseFiles = getBuildFilesForPhase(.sources) let shouldSkipSourcesBuildPhase = sourcesBuildPhaseFiles.isEmpty && target.type.canSkipCompileSourcesBuildPhase if !shouldSkipSourcesBuildPhase { let sourcesBuildPhase = addObject(PBXSourcesBuildPhase(files: sourcesBuildPhaseFiles)) buildPhases.append(sourcesBuildPhase) } buildPhases += try target.postCompileScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) } if !target.putResourcesBeforeSourcesBuildPhase { addResourcesBuildPhase() } let swiftObjCInterfaceHeader = project.getCombinedBuildSetting("SWIFT_OBJC_INTERFACE_HEADER_NAME", target: target, config: project.configs[0])?.stringValue let swiftInstallObjCHeader = project.getBoolBuildSetting("SWIFT_INSTALL_OBJC_HEADER", target: target, config: project.configs[0]) ?? true // Xcode default if target.type == .staticLibrary && swiftObjCInterfaceHeader != "" && swiftInstallObjCHeader && sourceFiles.contains(where: { $0.buildPhase == .sources && $0.path.extension == "swift" }) { let inputPaths = ["$(DERIVED_SOURCES_DIR)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)"] let outputPaths = ["$(BUILT_PRODUCTS_DIR)/include/$(PRODUCT_MODULE_NAME)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)"] let script = addObject( PBXShellScriptBuildPhase( name: "Copy Swift Objective-C Interface Header", inputPaths: inputPaths, outputPaths: outputPaths, shellPath: "/bin/sh", shellScript: "ditto \"${SCRIPT_INPUT_FILE_0}\" \"${SCRIPT_OUTPUT_FILE_0}\"\n" ) ) buildPhases.append(script) } buildPhases += copyFilesBuildPhasesFiles .filter { $0.key.phaseOrder == .postCompile } .map { generateCopyFiles(targetName: target.name, copyFiles: $0, buildPhaseFiles: $1) } if !carthageFrameworksToEmbed.isEmpty { let inputPaths = carthageFrameworksToEmbed .map { "$(SRCROOT)/\(carthageResolver.buildPath(for: target.platform, linkType: .dynamic))/\($0)\($0.contains(".") ? "" : ".framework")" } let outputPaths = carthageFrameworksToEmbed .map { "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/\($0)\($0.contains(".") ? "" : ".framework")" } let carthageExecutable = carthageResolver.executable let carthageScript = addObject( PBXShellScriptBuildPhase( name: "Carthage", inputPaths: inputPaths, outputPaths: outputPaths, shellPath: "/bin/sh -l", shellScript: "\(carthageExecutable) copy-frameworks\n" ) ) buildPhases.append(carthageScript) } if !targetFrameworkBuildFiles.isEmpty { let frameworkBuildPhase = addObject( PBXFrameworksBuildPhase(files: targetFrameworkBuildFiles) ) buildPhases.append(frameworkBuildPhase) } if !copyBundlesReferences.isEmpty { let copyBundlesPhase = addObject(PBXCopyFilesBuildPhase( dstSubfolderSpec: .resources, name: "Copy Bundle Resources", files: copyBundlesReferences )) buildPhases.append(copyBundlesPhase) } if !extensions.isEmpty { let copyFilesPhase = addObject( getPBXCopyFilesBuildPhase(dstSubfolderSpec: .plugins, name: "Embed Foundation Extensions", files: extensions) ) buildPhases.append(copyFilesPhase) } if !extensionKitExtensions.isEmpty { let copyFilesPhase = addObject( getPBXCopyFilesBuildPhase(dstSubfolderSpec: .productsDirectory, dstPath: "$(EXTENSIONS_FOLDER_PATH)", name: "Embed ExtensionKit Extensions", files: extensionKitExtensions) ) buildPhases.append(copyFilesPhase) } if !systemExtensions.isEmpty { let copyFilesPhase = addObject( // With parameters below the Xcode will show "Destination: System Extensions". getPBXCopyFilesBuildPhase(dstSubfolderSpec: .productsDirectory, dstPath: "$(SYSTEM_EXTENSIONS_FOLDER_PATH)", name: "Embed System Extensions", files: systemExtensions) ) buildPhases.append(copyFilesPhase) } if !appClips.isEmpty { let copyFilesPhase = addObject( PBXCopyFilesBuildPhase( dstPath: "$(CONTENTS_FOLDER_PATH)/AppClips", dstSubfolderSpec: .productsDirectory, name: "Embed App Clips", files: appClips ) ) buildPhases.append(copyFilesPhase) } copyFrameworksReferences += getBuildFilesForPhase(.frameworks) if !copyFrameworksReferences.isEmpty { let copyFilesPhase = addObject( getPBXCopyFilesBuildPhase(dstSubfolderSpec: .frameworks, name: "Embed Frameworks", files: copyFrameworksReferences) ) buildPhases.append(copyFilesPhase) } if !customCopyDependenciesReferences.isEmpty { let splitted = splitCopyDepsByDestination(customCopyDependenciesReferences) for (phase, references) in splitted { guard let destination = phase.destination.destination else { continue } let copyFilesPhase = addObject( getPBXCopyFilesBuildPhase(dstSubfolderSpec: destination, dstPath:phase.subpath, name: "Embed Dependencies", files: references) ) buildPhases.append(copyFilesPhase) } } if !copyWatchReferences.isEmpty { let copyFilesPhase = addObject( PBXCopyFilesBuildPhase( dstPath: "$(CONTENTS_FOLDER_PATH)/Watch", dstSubfolderSpec: .productsDirectory, name: "Embed Watch Content", files: copyWatchReferences ) ) buildPhases.append(copyFilesPhase) } let buildRules = target.buildRules.map { buildRule in addObject( PBXBuildRule( compilerSpec: buildRule.action.compilerSpec, fileType: buildRule.fileType.fileType, isEditable: true, filePatterns: buildRule.fileType.pattern, name: buildRule.name ?? "Build Rule", outputFiles: buildRule.outputFiles, outputFilesCompilerFlags: buildRule.outputFilesCompilerFlags, script: buildRule.action.script, runOncePerArchitecture: buildRule.runOncePerArchitecture ) ) } buildPhases += try target.postBuildScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) } let configs: [XCBuildConfiguration] = project.configs.map { config in var buildSettings = project.getTargetBuildSettings(target: target, config: config) // Set CODE_SIGN_ENTITLEMENTS if let entitlements = target.entitlements { buildSettings["CODE_SIGN_ENTITLEMENTS"] = .string(entitlements.path) } // Set INFOPLIST_FILE based on the resolved value if let infoPlistFile = infoPlistFiles[config] { buildSettings["INFOPLIST_FILE"] = .string(infoPlistFile) } // automatically calculate bundle id if let bundleIdPrefix = project.options.bundleIdPrefix, !project.targetHasBuildSetting("PRODUCT_BUNDLE_IDENTIFIER", target: target, config: config) { let characterSet = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-.")).inverted let escapedTargetName = target.name .replacingOccurrences(of: "_", with: "-") .components(separatedBy: characterSet) .joined(separator: "") buildSettings["PRODUCT_BUNDLE_IDENTIFIER"] = .string(bundleIdPrefix + "." + escapedTargetName) } // automatically set test target name if target.type == .uiTestBundle, !project.targetHasBuildSetting("TEST_TARGET_NAME", target: target, config: config) { for dependency in target.dependencies { if dependency.type == .target, let dependencyTarget = project.getTarget(dependency.reference), dependencyTarget.type.isApp { buildSettings["TEST_TARGET_NAME"] = .string(dependencyTarget.name) break } } } // automatically set TEST_HOST if target.type == .unitTestBundle, !project.targetHasBuildSetting("TEST_HOST", target: target, config: config) { for dependency in target.dependencies { if dependency.type == .target, let dependencyTarget = project.getTarget(dependency.reference), dependencyTarget.type.isApp { if dependencyTarget.platform == .macOS { buildSettings["TEST_HOST"] = "$(BUILT_PRODUCTS_DIR)/\(dependencyTarget.productName).app/Contents/MacOS/\(dependencyTarget.productName)" } else { buildSettings["TEST_HOST"] = "$(BUILT_PRODUCTS_DIR)/\(dependencyTarget.productName).app/\(dependencyTarget.productName)" } break } } } // objc linkage if anyDependencyRequiresObjCLinking { let otherLinkingFlags = "OTHER_LDFLAGS" let objCLinking = "-ObjC" if var array = buildSettings[otherLinkingFlags]?.arrayValue { array.append(objCLinking) buildSettings[otherLinkingFlags] = .array(array) } else if let string = buildSettings[otherLinkingFlags]?.stringValue { buildSettings[otherLinkingFlags] = .array([string, objCLinking]) } else { buildSettings[otherLinkingFlags] = .array(["$(inherited)", objCLinking]) } } // set Carthage search paths let configFrameworkBuildPaths: [String] if !carthageDependencies.isEmpty { var carthagePlatformBuildPaths: Set = [] if carthageDependencies.contains(where: { $0.dependency.carthageLinkType == .static }) { let carthagePlatformBuildPath = "$(PROJECT_DIR)/" + carthageResolver.buildPath(for: target.platform, linkType: .static) carthagePlatformBuildPaths.insert(carthagePlatformBuildPath) } if carthageDependencies.contains(where: { $0.dependency.carthageLinkType == .dynamic }) { let carthagePlatformBuildPath = "$(PROJECT_DIR)/" + carthageResolver.buildPath(for: target.platform, linkType: .dynamic) carthagePlatformBuildPaths.insert(carthagePlatformBuildPath) } configFrameworkBuildPaths = carthagePlatformBuildPaths.sorted() + frameworkBuildPaths.sorted() } else { configFrameworkBuildPaths = frameworkBuildPaths.sorted() } // set framework search paths if !configFrameworkBuildPaths.isEmpty { let frameworkSearchPaths = "FRAMEWORK_SEARCH_PATHS" if var array = buildSettings[frameworkSearchPaths]?.arrayValue { array.append(contentsOf: configFrameworkBuildPaths) buildSettings[frameworkSearchPaths] = .array(array) } else if let string = buildSettings[frameworkSearchPaths]?.stringValue { buildSettings[frameworkSearchPaths] = .array([string] + configFrameworkBuildPaths) } else { buildSettings[frameworkSearchPaths] = .array(["$(inherited)"] + configFrameworkBuildPaths) } } var baseConfiguration: PBXFileReference? if let configPath = target.configFiles[config.name], let fileReference = sourceGenerator.getContainedFileReference(path: project.basePath + configPath) as? PBXFileReference { baseConfiguration = fileReference } let buildConfig = XCBuildConfiguration( name: config.name, buildSettings: buildSettings ) buildConfig.baseConfiguration = baseConfiguration return addObject(buildConfig) } let defaultConfigurationName = project.options.defaultConfig ?? project.configs.first?.name ?? "" let buildConfigList = addObject(XCConfigurationList( buildConfigurations: configs, defaultConfigurationName: defaultConfigurationName )) let targetObject = targetObjects[target.name]! let targetFileReference = targetFileReferences[target.name] targetObject.name = target.name targetObject.buildConfigurationList = buildConfigList targetObject.buildPhases = buildPhases targetObject.dependencies = dependencies targetObject.productName = target.name targetObject.buildRules = buildRules targetObject.packageProductDependencies = packageDependencies targetObject.product = targetFileReference if !target.isLegacy { targetObject.productType = target.type } // add fileSystemSynchronizedGroups let synchronizedRootGroups: [PBXFileSystemSynchronizedRootGroup] = sourceFiles.compactMap { sourceFile in guard let syncedGroup = sourceFile.fileReference as? PBXFileSystemSynchronizedRootGroup else { return nil } configureMembershipExceptions( for: syncedGroup, path: sourceFile.path, target: target, targetObject: targetObject, infoPlistFiles: infoPlistFiles ) return syncedGroup } if !synchronizedRootGroups.isEmpty { targetObject.fileSystemSynchronizedGroups = synchronizedRootGroups } } private func configureMembershipExceptions( for syncedGroup: PBXFileSystemSynchronizedRootGroup, path syncedPath: Path, target: Target, targetObject: PBXTarget, infoPlistFiles: [Config: String] ) { guard let targetSource = target.sources.first(where: { (project.basePath + $0.path).normalize() == syncedPath }) else { return } var exceptions: Set = Set( sourceGenerator.syncedFolderExceptions(for: targetSource, at: syncedPath) .compactMap { try? $0.relativePath(from: syncedPath).string } ) for infoPlistPath in Set(infoPlistFiles.values) { let relative = try? (project.basePath + infoPlistPath).normalize() .relativePath(from: syncedPath) if let rel = relative?.string, !rel.hasPrefix("..") { exceptions.insert(rel) } } guard !exceptions.isEmpty else { return } let exceptionSet = PBXFileSystemSynchronizedBuildFileExceptionSet( target: targetObject, membershipExceptions: exceptions.sorted(), publicHeaders: nil, privateHeaders: nil, additionalCompilerFlagsByRelativePath: nil, attributesByRelativePath: nil ) addObject(exceptionSet) syncedGroup.exceptions = (syncedGroup.exceptions ?? []) + [exceptionSet] } private func makePlatformFilter(for filter: Dependency.PlatformFilter) -> String? { switch filter { case .all: return nil case .macOS: return "maccatalyst" case .iOS: return "ios" } } private func makeDestinationFilters(for filters: [SupportedDestination]?) -> [String]? { guard let filters = filters, !filters.isEmpty else { return nil } return filters.map { $0.string } } /// Make `Build Tools Plug-ins` as a dependency to the target /// - Parameter target: ProjectTarget /// - Returns: Elements for referencing other targets through content proxies. func makePackagePluginDependency(for target: ProjectTarget) -> [PBXTargetDependency] { target.buildToolPlugins.compactMap { buildToolPlugin in let packageReference = packageReferences[buildToolPlugin.package] if packageReference == nil, localPackageReferences[buildToolPlugin.package] == nil { return nil } let packageDependency = addObject( XCSwiftPackageProductDependency(productName: buildToolPlugin.plugin, package: packageReference, isPlugin: true) ) let targetDependency = addObject( PBXTargetDependency(product: packageDependency) ) return targetDependency } } func getInfoPlists(for target: Target) -> [Config: String] { var searchForDefaultInfoPlist: Bool = true var defaultInfoPlist: String? let values: [(Config, String)] = project.configs.compactMap { config in // First, if the plist path was defined by `INFOPLIST_FILE`, use that let buildSettings = project.getTargetBuildSettings(target: target, config: config) if let value = buildSettings["INFOPLIST_FILE"]?.stringValue { return (config, value) } // Otherwise check if the path was defined as part of the `info` spec if let value = target.info?.path { return (config, value) } // If we haven't yet looked for the default info plist, try doing so if searchForDefaultInfoPlist { searchForDefaultInfoPlist = false if let plistPath = getInfoPlist(target.sources) { let basePath = projectDirectory ?? project.basePath.absolute() let relative = (try? plistPath.relativePath(from: basePath)) ?? plistPath defaultInfoPlist = relative.string } } // Return the default plist if there was one if let value = defaultInfoPlist { return (config, value) } return nil } return Dictionary(uniqueKeysWithValues: values) } func getInfoPlist(_ sources: [TargetSource]) -> Path? { sources .lazy .map { self.project.basePath + $0.path } .compactMap { (path) -> Path? in if path.isFile { return path.lastComponent == "Info.plist" ? path : nil } else { return path.first(where: { $0.lastComponent == "Info.plist" })?.absolute() } } .first } func getAllDependenciesPlusTransitiveNeedingEmbedding(target topLevelTarget: Target) -> [Dependency] { // this is used to resolve cyclical target dependencies var visitedTargets: Set = [] var dependencies: [String: Dependency] = [:] var queue: [Target] = [topLevelTarget] while !queue.isEmpty { let target = queue.removeFirst() if visitedTargets.contains(target.name) { continue } let isTopLevel = target == topLevelTarget for dependency in target.dependencies { // don't overwrite dependencies, to allow top level ones to rule if dependencies[dependency.uniqueID] != nil { continue } // don't want a dependency if it's going to be embedded or statically linked in a non-top level target // in .target check we filter out targets that will embed all of their dependencies // For some more context about the `dependency.embed != true` lines, refer to https://github.com/yonaskolb/XcodeGen/pull/820 switch dependency.type { case .sdk: dependencies[dependency.uniqueID] = dependency case .framework, .carthage, .package: if isTopLevel || dependency.embed != true { dependencies[dependency.uniqueID] = dependency } case .target: let dependencyTargetReference = try! TargetReference(dependency.reference) switch dependencyTargetReference.location { case .local: if isTopLevel || dependency.embed != true { if let dependencyTarget = project.getTarget(dependency.reference) { dependencies[dependency.uniqueID] = dependency if !dependencyTarget.shouldEmbedDependencies { // traverse target's dependencies if it doesn't embed them itself queue.append(dependencyTarget) } } else if project.getAggregateTarget(dependency.reference) != nil { // Aggregate targets should be included dependencies[dependency.uniqueID] = dependency } } case .project: if isTopLevel || dependency.embed != true { dependencies[dependency.uniqueID] = dependency } } case .bundle: if isTopLevel { dependencies[dependency.uniqueID] = dependency } } } visitedTargets.update(with: target.name) } return dependencies.sorted(by: { $0.key < $1.key }).map { $0.value } } } extension Target { var shouldEmbedDependencies: Bool { type.isApp || type.isTest } var shouldEmbedCarthageDependencies: Bool { (type.isApp && platform != .watchOS) || type == .watch2Extension || type.isTest } } extension Platform { /// - returns: `true` for platforms that the app store requires simulator slices to be stripped. public var requiresSimulatorStripping: Bool { switch self { case .auto, .iOS, .tvOS, .watchOS, .visionOS: return true case .macOS: return false } } } extension PBXFileElement { /// - returns: `true` if the element is a group, a folder reference (likely an SPM package), or a synced folder. var isGroupOrFolder: Bool { self is PBXGroup || self is PBXFileSystemSynchronizedRootGroup || (self as? PBXFileReference)?.lastKnownFileType == "folder" } public func getSortOrder(groupSortPosition: SpecOptions.GroupSortPosition) -> Int { if self is PBXGroup || self is PBXFileSystemSynchronizedRootGroup { switch groupSortPosition { case .top: return -1 case .bottom: return 1 case .none: return 0 } } else { return 0 } } } private extension Dependency { var carthageLinkType: Dependency.CarthageLinkType? { switch type { case .carthage(_, let linkType): return linkType default: return nil } } } ================================================ FILE: Sources/XcodeGenKit/ProjectFormat.swift ================================================ public extension ProjectFormat { static let `default`: ProjectFormat = .xcode16_0 } public enum ProjectFormat: String { case xcode16_3 case xcode16_0 case xcode15_3 case xcode15_0 case xcode14_0 public var objectVersion: UInt { switch self { case .xcode16_3: 90 case .xcode16_0: 77 case .xcode15_3: 63 case .xcode15_0: 60 case .xcode14_0: 56 } } public var preferredProjectObjectVersion: UInt? { switch self { case .xcode16_3, .xcode16_0: objectVersion case .xcode15_3, .xcode15_0, .xcode14_0: nil } } public var compatibilityVersion: String? { switch self { case .xcode16_3, .xcode16_0: nil case .xcode15_3: "Xcode 15.3" case .xcode15_0: "Xcode 15.0" case .xcode14_0: "Xcode 14.0" } } } ================================================ FILE: Sources/XcodeGenKit/ProjectGenerator.swift ================================================ import Foundation import JSONUtilities import PathKit import ProjectSpec import XcodeProj import Yams public class ProjectGenerator { let project: Project public init(project: Project) { self.project = project } public func generateXcodeProject(in projectDirectory: Path? = nil, userName: String) throws -> XcodeProj { // generate PBXProj let pbxProjGenerator = PBXProjGenerator(project: project, projectDirectory: projectDirectory) let pbxProj = try pbxProjGenerator.generate() // generate Workspace let workspace = try generateWorkspace() // generate Schemes let schemeGenerator = SchemeGenerator(project: project, pbxProj: pbxProj) let (sharedSchemes, userSchemes, schemeManagement) = try schemeGenerator.generateSchemes() // generate Breakpoints let breakpointGenerator = BreakpointGenerator(project: project) let xcbreakpointlist = try breakpointGenerator.generateBreakpointList() // generate shared data let sharedData = XCSharedData(schemes: sharedSchemes, breakpoints: xcbreakpointlist) // generate user data let userData = userSchemes.isEmpty && schemeManagement == nil ? [] : [ XCUserData(userName: userName, schemes: userSchemes, schemeManagement: schemeManagement) ] return XcodeProj( workspace: workspace, pbxproj: pbxProj, sharedData: sharedData, userData: userData ) } func generateWorkspace() throws -> XCWorkspace { let selfReference = XCWorkspaceDataFileRef(location: .current("")) let dataElement = XCWorkspaceDataElement.file(selfReference) let workspaceData = XCWorkspaceData(children: [dataElement]) return XCWorkspace(data: workspaceData) } } ================================================ FILE: Sources/XcodeGenKit/SchemeGenerator.swift ================================================ import Foundation import ProjectSpec import XcodeProj import PathKit private func suitableConfig(for type: ConfigType, in project: Project) -> Config { if let defaultConfig = Config.defaultConfigs.first(where: { $0.type == type }), project.configs.contains(defaultConfig) { return defaultConfig } return project.configs.first { $0.type == type }! } public class SchemeGenerator { let project: Project let pbxProj: PBXProj var defaultDebugConfig: Config { suitableConfig(for: .debug, in: project) } var defaultReleaseConfig: Config { suitableConfig(for: .release, in: project) } public init(project: Project, pbxProj: PBXProj) { self.project = project self.pbxProj = pbxProj } private var projects: [ProjectReference: PBXProj] = [:] func getPBXProj(from reference: ProjectReference) throws -> PBXProj { if let cachedProject = projects[reference] { return cachedProject } let pbxproj = try XcodeProj(path: project.basePath + Path(reference.path)).pbxproj projects[reference] = pbxproj return pbxproj } public func generateSchemes() throws -> ( shared: [XCScheme], user: [XCScheme], management: XCSchemeManagement? ) { var schemes: [(Scheme, ProjectTarget?)] = [] for scheme in project.schemes { schemes.append((scheme, nil)) } for target in project.projectTargets { if let targetScheme = target.scheme { if targetScheme.configVariants.isEmpty { let schemeName = target.name let debugConfig = suitableConfig(for: .debug, in: project) let releaseConfig = suitableConfig(for: .release, in: project) let scheme = Scheme( name: schemeName, target: target, targetScheme: targetScheme, project: project, debugConfig: debugConfig.name, releaseConfig: releaseConfig.name ) schemes.append((scheme, target)) } else { for configVariant in targetScheme.configVariants { let schemeName = "\(target.name) \(configVariant)" let debugConfig = project.configs .first(including: configVariant, for: .debug)! let releaseConfig = project.configs .first(including: configVariant, for: .release)! let scheme = Scheme( name: schemeName, target: target, targetScheme: targetScheme, project: project, debugConfig: debugConfig.name, releaseConfig: releaseConfig.name ) schemes.append((scheme, target)) } } } } var sharedSchemes: [XCScheme] = [] var userSchemes: [XCScheme] = [] var schemeManagements: [XCSchemeManagement.UserStateScheme] = [] for (scheme, projectTarget) in schemes { let xcscheme = try generateScheme(scheme, for: projectTarget) if scheme.management?.shared == false { userSchemes.append(xcscheme) } else { sharedSchemes.append(xcscheme) } if let management = scheme.management { schemeManagements.append( XCSchemeManagement.UserStateScheme( name: scheme.name + ".xcscheme", shared: management.shared, orderHint: management.orderHint, isShown: management.isShown ) ) } } return ( shared: sharedSchemes, user: userSchemes, management: schemeManagements.isEmpty ? nil : XCSchemeManagement(schemeUserState: schemeManagements, suppressBuildableAutocreation: nil) ) } public func generateScheme(_ scheme: Scheme, for target: ProjectTarget? = nil) throws -> XCScheme { func getBuildableReference(_ target: TargetReference) throws -> XCScheme.BuildableReference { let pbxProj: PBXProj let projectFilePath: String switch target.location { case .project(let project): guard let projectReference = self.project.getProjectReference(project) else { throw SchemeGenerationError.missingProject(project) } pbxProj = try getPBXProj(from: projectReference) projectFilePath = projectReference.path case .local: pbxProj = self.pbxProj projectFilePath = "\(self.project.name).xcodeproj" } guard let pbxTarget = pbxProj.targets(named: target.name).first else { throw SchemeGenerationError.missingTarget(target, projectPath: projectFilePath) } let buildableName: String switch target.location { case .project: buildableName = pbxTarget.productNameWithExtension() ?? pbxTarget.name case .local: guard let _buildableName = project.getTarget(target.name)?.filename ?? project.getAggregateTarget(target.name)?.name else { fatalError("Unable to determinate \"buildableName\" for build target: \(target)") } buildableName = _buildableName } return XCScheme.BuildableReference( referencedContainer: "container:\(projectFilePath)", blueprint: pbxTarget, buildableName: buildableName, blueprintName: target.name ) } func getBuildableTestableReference(_ target: TestableTargetReference) throws -> XCScheme.BuildableReference { switch target.location { case .package(let packageName): guard let package = self.project.getPackage(packageName), case let .local(path, _, _) = package else { throw SchemeGenerationError.missingPackage(packageName) } return XCScheme.BuildableReference( referencedContainer: "container:\(path)", blueprintIdentifier: target.name, buildableName: target.name, blueprintName: target.name ) default: return try getBuildableReference(target.targetReference) } } func getBuildEntry(_ buildTarget: Scheme.BuildTarget) throws -> XCScheme.BuildAction.Entry { let buildableReference = try getBuildableTestableReference(buildTarget.target) return XCScheme.BuildAction.Entry(buildableReference: buildableReference, buildFor: buildTarget.buildTypes) } let testTargets = scheme.test?.targets ?? [] let testBuildTargets = testTargets.map { Scheme.BuildTarget(target: $0.targetReference, buildTypes: BuildType.testOnly) } let testBuildTargetEntries = try testBuildTargets.map(getBuildEntry) let buildActionEntries: [XCScheme.BuildAction.Entry] = try scheme.build.targets.map(getBuildEntry) func getExecutionAction(_ action: Scheme.ExecutionAction) -> XCScheme.ExecutionAction { // ExecutionActions can require the use of build settings. Xcode allows the settings to come from a build or test target. let environmentBuildable = action.settingsTarget.flatMap { settingsTarget in (buildActionEntries + testBuildTargetEntries) .first { settingsTarget == $0.buildableReference.blueprintName }? .buildableReference } return XCScheme.ExecutionAction( scriptText: action.script, title: action.name, shellToInvoke: action.shell, environmentBuildable: environmentBuildable ) } let schemeTarget: ProjectTarget? if let targetName = scheme.run?.executable { schemeTarget = project.getTarget(targetName) } else { guard let firstTarget = scheme.build.targets.first else { throw SchemeGenerationError.missingBuildTargets(scheme.name) } let name = scheme.build.targets.first { $0.buildTypes.contains(.running) }?.target.name ?? firstTarget.target.name schemeTarget = target ?? project.getTarget(name) } let shouldExecuteOnLaunch = schemeTarget?.shouldExecuteOnLaunch == true let buildableReference = buildActionEntries.first(where: { $0.buildableReference.blueprintName == schemeTarget?.name })?.buildableReference ?? buildActionEntries.first!.buildableReference let runnables = makeProductRunnables(for: schemeTarget, buildableReference: buildableReference) let buildAction = XCScheme.BuildAction( buildActionEntries: buildActionEntries, preActions: scheme.build.preActions.map(getExecutionAction), postActions: scheme.build.postActions.map(getExecutionAction), parallelizeBuild: scheme.build.parallelizeBuild, buildImplicitDependencies: scheme.build.buildImplicitDependencies, runPostActionsOnFailure: scheme.build.runPostActionsOnFailure ) let testables: [XCScheme.TestableReference] = zip(testTargets, testBuildTargetEntries).map { testTarget, testBuildEntries in var locationScenarioReference: XCScheme.LocationScenarioReference? if var location = testTarget.location { if location.contains(".gpx") { var path = Path(components: [project.options.schemePathPrefix, location]) path = path.simplifyingParentDirectoryReferences() location = path.string } let referenceType = location.contains(".gpx") ? "0" : "1" locationScenarioReference = XCScheme.LocationScenarioReference(identifier: location, referenceType: referenceType) } return XCScheme.TestableReference( skipped: testTarget.skipped, parallelization: testTarget.parallelizable ? .all : .none, randomExecutionOrdering: testTarget.randomExecutionOrder, buildableReference: testBuildEntries.buildableReference, locationScenarioReference: locationScenarioReference, skippedTests: testTarget.skippedTests.map(XCScheme.TestItem.init), selectedTests: testTarget.selectedTests.map(XCScheme.TestItem.init), useTestSelectionWhitelist: !testTarget.selectedTests.isEmpty ? true : nil ) } let coverageBuildableTargets = try scheme.test?.coverageTargets.map { try getBuildableTestableReference($0) } ?? [] let testCommandLineArgs = scheme.test.map { XCScheme.CommandLineArguments($0.commandLineArguments) } let launchCommandLineArgs = scheme.run.map { XCScheme.CommandLineArguments($0.commandLineArguments) } let profileCommandLineArgs = scheme.profile.map { XCScheme.CommandLineArguments($0.commandLineArguments) } let testVariables = scheme.test.flatMap { $0.environmentVariables.isEmpty ? nil : $0.environmentVariables } let launchVariables = scheme.run.flatMap { $0.environmentVariables.isEmpty ? nil : $0.environmentVariables } let profileVariables = scheme.profile.flatMap { $0.environmentVariables.isEmpty ? nil : $0.environmentVariables } let defaultTestPlanIndex = scheme.test?.testPlans.firstIndex { $0.defaultPlan } ?? 0 let testPlans = scheme.test?.testPlans.enumerated().map { index, testPlan in XCScheme.TestPlanReference(reference: "container:\(testPlan.path)", default: defaultTestPlanIndex == index) } ?? [] let testBuildableEntries = buildActionEntries.filter({ $0.buildFor.contains(.testing) }) + testBuildTargetEntries let testMacroExpansionBuildableRef = testBuildableEntries.map(\.buildableReference).contains(buildableReference) ? buildableReference : testBuildableEntries.first?.buildableReference let testMacroExpansion: XCScheme.BuildableReference = buildActionEntries.first( where: { value in if let macroExpansion = scheme.test?.macroExpansion { return value.buildableReference.blueprintName == macroExpansion } return false } )?.buildableReference ?? testMacroExpansionBuildableRef ?? buildableReference let testAction = XCScheme.TestAction( buildConfiguration: scheme.test?.config ?? defaultDebugConfig.name, macroExpansion: testMacroExpansion, testables: testables, testPlans: testPlans.isEmpty ? nil : testPlans, preActions: scheme.test?.preActions.map(getExecutionAction) ?? [], postActions: scheme.test?.postActions.map(getExecutionAction) ?? [], selectedDebuggerIdentifier: (scheme.test?.debugEnabled ?? Scheme.Test.debugEnabledDefault) ? XCScheme.defaultDebugger : "", selectedLauncherIdentifier: (scheme.test?.debugEnabled ?? Scheme.Test.debugEnabledDefault) ? XCScheme.defaultLauncher : "Xcode.IDEFoundation.Launcher.PosixSpawn", shouldUseLaunchSchemeArgsEnv: scheme.test?.shouldUseLaunchSchemeArgsEnv ?? true, codeCoverageEnabled: scheme.test?.gatherCoverageData ?? Scheme.Test.gatherCoverageDataDefault, codeCoverageTargets: coverageBuildableTargets, onlyGenerateCoverageForSpecifiedTargets: !coverageBuildableTargets.isEmpty, enableAddressSanitizer: scheme.test?.enableAddressSanitizer ?? Scheme.Test.enableAddressSanitizerDefault, enableASanStackUseAfterReturn: scheme.test?.enableASanStackUseAfterReturn ?? Scheme.Test.enableASanStackUseAfterReturnDefault, enableThreadSanitizer: scheme.test?.enableThreadSanitizer ?? Scheme.Test.enableThreadSanitizerDefault, enableUBSanitizer: scheme.test?.enableUBSanitizer ?? Scheme.Test.enableUBSanitizerDefault, disableMainThreadChecker: scheme.test?.disableMainThreadChecker ?? Scheme.Test.disableMainThreadCheckerDefault, commandlineArguments: testCommandLineArgs, environmentVariables: testVariables, language: scheme.test?.language, region: scheme.test?.region, systemAttachmentLifetime: scheme.test?.systemAttachmentLifetime, preferredScreenCaptureFormat: scheme.test?.preferredScreenCaptureFormat, customLLDBInitFile: scheme.test?.customLLDBInit ) let allowLocationSimulation = scheme.run?.simulateLocation?.allow ?? true var locationScenarioReference: XCScheme.LocationScenarioReference? if let simulateLocation = scheme.run?.simulateLocation, var identifier = simulateLocation.defaultLocation, let referenceType = simulateLocation.referenceType { if referenceType == .gpx { var path = Path(components: [project.options.schemePathPrefix, identifier]) path = path.simplifyingParentDirectoryReferences() identifier = path.string } locationScenarioReference = XCScheme.LocationScenarioReference(identifier: identifier, referenceType: referenceType.rawValue) } var storeKitConfigurationFileReference: XCScheme.StoreKitConfigurationFileReference? if let storeKitConfiguration = scheme.run?.storeKitConfiguration { let storeKitConfigurationPath = Path(components: [project.options.schemePathPrefix, storeKitConfiguration]).simplifyingParentDirectoryReferences() storeKitConfigurationFileReference = XCScheme.StoreKitConfigurationFileReference(identifier: storeKitConfigurationPath.string) } let macroExpansion: XCScheme.BuildableReference? if let macroExpansionName = scheme.run?.macroExpansion, let resolvedMacroExpansion = buildActionEntries.first(where: { $0.buildableReference.blueprintName == macroExpansionName })?.buildableReference { macroExpansion = resolvedMacroExpansion } else { macroExpansion = shouldExecuteOnLaunch ? nil : buildableReference } let launchAction = XCScheme.LaunchAction( runnable: shouldExecuteOnLaunch ? runnables.launch : nil, buildConfiguration: scheme.run?.config ?? defaultDebugConfig.name, preActions: scheme.run?.preActions.map(getExecutionAction) ?? [], postActions: scheme.run?.postActions.map(getExecutionAction) ?? [], macroExpansion: macroExpansion, selectedDebuggerIdentifier: selectedDebuggerIdentifier(for: schemeTarget, run: scheme.run), selectedLauncherIdentifier: selectedLauncherIdentifier(for: schemeTarget, run: scheme.run), askForAppToLaunch: scheme.run?.askForAppToLaunch, customWorkingDirectory: scheme.run?.customWorkingDirectory, useCustomWorkingDirectory: scheme.run?.customWorkingDirectory != nil, allowLocationSimulation: allowLocationSimulation, locationScenarioReference: locationScenarioReference, enableGPUFrameCaptureMode: scheme.run?.enableGPUFrameCaptureMode ?? XCScheme.LaunchAction.defaultGPUFrameCaptureMode, disableGPUValidationMode: !(scheme.run?.enableGPUValidationMode ?? Scheme.Run.enableGPUValidationModeDefault), enableAddressSanitizer: scheme.run?.enableAddressSanitizer ?? Scheme.Run.enableAddressSanitizerDefault, enableASanStackUseAfterReturn: scheme.run?.enableASanStackUseAfterReturn ?? Scheme.Run.enableASanStackUseAfterReturnDefault, enableThreadSanitizer: scheme.run?.enableThreadSanitizer ?? Scheme.Run.enableThreadSanitizerDefault, enableUBSanitizer: scheme.run?.enableUBSanitizer ?? Scheme.Run.enableUBSanitizerDefault, disableMainThreadChecker: scheme.run?.disableMainThreadChecker ?? Scheme.Run.disableMainThreadCheckerDefault, disablePerformanceAntipatternChecker: scheme.run?.disableThreadPerformanceChecker ?? Scheme.Run.disableThreadPerformanceCheckerDefault, stopOnEveryMainThreadCheckerIssue: scheme.run?.stopOnEveryMainThreadCheckerIssue ?? Scheme.Run.stopOnEveryMainThreadCheckerIssueDefault, commandlineArguments: launchCommandLineArgs, environmentVariables: launchVariables, language: scheme.run?.language, region: scheme.run?.region, launchAutomaticallySubstyle: scheme.run?.launchAutomaticallySubstyle ?? launchAutomaticallySubstyle(for: schemeTarget), storeKitConfigurationFileReference: storeKitConfigurationFileReference, customLLDBInitFile: scheme.run?.customLLDBInit ) let profileAction = XCScheme.ProfileAction( buildableProductRunnable: shouldExecuteOnLaunch ? runnables.profile : nil, buildConfiguration: scheme.profile?.config ?? defaultReleaseConfig.name, preActions: scheme.profile?.preActions.map(getExecutionAction) ?? [], postActions: scheme.profile?.postActions.map(getExecutionAction) ?? [], macroExpansion: shouldExecuteOnLaunch ? nil : buildableReference, shouldUseLaunchSchemeArgsEnv: scheme.profile?.shouldUseLaunchSchemeArgsEnv ?? true, askForAppToLaunch: scheme.profile?.askForAppToLaunch, commandlineArguments: profileCommandLineArgs, environmentVariables: profileVariables ) let analyzeAction = XCScheme.AnalyzeAction(buildConfiguration: scheme.analyze?.config ?? defaultDebugConfig.name) let archiveAction = XCScheme.ArchiveAction( buildConfiguration: scheme.archive?.config ?? defaultReleaseConfig.name, revealArchiveInOrganizer: scheme.archive?.revealArchiveInOrganizer ?? true, customArchiveName: scheme.archive?.customArchiveName, preActions: scheme.archive?.preActions.map(getExecutionAction) ?? [], postActions: scheme.archive?.postActions.map(getExecutionAction) ?? [] ) let lastUpgradeVersion = project.attributes["LastUpgradeCheck"] as? String ?? project.xcodeVersion return XCScheme( name: scheme.name, lastUpgradeVersion: lastUpgradeVersion, version: project.schemeVersion, buildAction: buildAction, testAction: testAction, launchAction: launchAction, profileAction: profileAction, analyzeAction: analyzeAction, archiveAction: archiveAction, wasCreatedForAppExtension: schemeTarget .flatMap { $0.type.isExtension ? true : nil } ) } private func launchAutomaticallySubstyle(for target: ProjectTarget?) -> String? { if target?.type.isExtension == true { return "2" } else { return nil } } private func makeProductRunnables(for target: ProjectTarget?, buildableReference: XCScheme.BuildableReference) -> (launch: XCScheme.Runnable, profile: XCScheme.BuildableProductRunnable) { let buildable = XCScheme.BuildableProductRunnable(buildableReference: buildableReference) if target?.type.isWatchApp == true { let remote = XCScheme.RemoteRunnable( buildableReference: buildableReference, bundleIdentifier: "com.apple.Carousel", runnableDebuggingMode: "2" ) return (remote, buildable) } else { return (buildable, buildable) } } private func selectedDebuggerIdentifier(for target: ProjectTarget?, run: Scheme.Run?) -> String { if target?.type.canUseDebugLauncher != false && run?.debugEnabled ?? Scheme.Run.debugEnabledDefault { return XCScheme.defaultDebugger } else { return "" } } private func selectedLauncherIdentifier(for target: ProjectTarget?, run: Scheme.Run?) -> String { if target?.type.canUseDebugLauncher != false && run?.debugEnabled ?? Scheme.Run.debugEnabledDefault { return XCScheme.defaultLauncher } else { return "Xcode.IDEFoundation.Launcher.PosixSpawn" } } } enum SchemeGenerationError: Error, CustomStringConvertible { case missingTarget(TargetReference, projectPath: String) case missingPackage(String) case missingProject(String) case missingBuildTargets(String) var description: String { switch self { case .missingTarget(let target, let projectPath): return "Unable to find target named \"\(target)\" in \"\(projectPath)\"" case .missingProject(let project): return "Unable to find project reference named \"\(project)\" in project.yml" case .missingBuildTargets(let name): return "Unable to find at least one build target in scheme \"\(name)\"" case .missingPackage(let package): return "Unable to find swift package named \"\(package)\" in project.yml" } } } extension Scheme { public init(name: String, target: ProjectTarget, targetScheme: TargetScheme, project: Project, debugConfig: String, releaseConfig: String) { self.init( name: name, build: .init( targets: Scheme.buildTargets(for: target, project: project), buildImplicitDependencies: targetScheme.buildImplicitDependencies, preActions: targetScheme.preActions, postActions: targetScheme.postActions ), run: .init( config: debugConfig, commandLineArguments: targetScheme.commandLineArguments, environmentVariables: targetScheme.environmentVariables, disableMainThreadChecker: targetScheme.disableMainThreadChecker, stopOnEveryMainThreadCheckerIssue: targetScheme.stopOnEveryMainThreadCheckerIssue, disableThreadPerformanceChecker: targetScheme.disableThreadPerformanceChecker, language: targetScheme.language, region: targetScheme.region, storeKitConfiguration: targetScheme.storeKitConfiguration ), test: .init( config: debugConfig, gatherCoverageData: targetScheme.gatherCoverageData, coverageTargets: targetScheme.coverageTargets, disableMainThreadChecker: targetScheme.disableMainThreadChecker, commandLineArguments: targetScheme.commandLineArguments, targets: targetScheme.testTargets, environmentVariables: targetScheme.environmentVariables, testPlans: targetScheme.testPlans, language: targetScheme.language, region: targetScheme.region ), profile: .init( config: releaseConfig, commandLineArguments: targetScheme.commandLineArguments, environmentVariables: targetScheme.environmentVariables ), analyze: .init( config: debugConfig ), archive: .init( config: releaseConfig ), management: targetScheme.management ) } private static func buildTargets(for target: ProjectTarget, project: Project) -> [BuildTarget] { let buildTarget = Scheme.BuildTarget(target: TestableTargetReference.local(target.name)) switch target.type { case .watchApp, .watch2App: let hostTarget = project.targets .first { projectTarget in projectTarget.dependencies.contains { $0.reference == target.name } } .map { BuildTarget(target: TestableTargetReference.local($0.name)) } return hostTarget.map { [buildTarget, $0] } ?? [buildTarget] default: return [buildTarget] } } } extension PBXProductType { var canUseDebugLauncher: Bool { // Extensions don't use the lldb launcher return !isExtension } var isWatchApp: Bool { switch self { case .watchApp, .watch2App: return true default: return false } } } extension Scheme.Test { var systemAttachmentLifetime: XCScheme.TestAction.AttachmentLifetime? { switch (captureScreenshotsAutomatically, deleteScreenshotsWhenEachTestSucceeds) { case (false, _): return .keepNever case (true, false): return .keepAlways case (true, true): return nil } } } ================================================ FILE: Sources/XcodeGenKit/SettingsBuilder.swift ================================================ import Foundation import JSONUtilities import PathKit import ProjectSpec import XcodeProj import Yams extension Project { public func getProjectBuildSettings(config: Config) -> BuildSettings { var buildSettings: BuildSettings = [:] // set project SDKROOT is a single platform if let firstPlatform = targets.first?.platform, targets.allSatisfy({ $0.platform == firstPlatform }) { buildSettings["SDKROOT"] = .string(firstPlatform.sdkRoot) } if let type = config.type, options.settingPresets.applyProject { buildSettings += SettingsPresetFile.base.getBuildSettings() buildSettings += SettingsPresetFile.config(type).getBuildSettings() } // apply custom platform version for platform in Platform.allCases { if let version = options.deploymentTarget.version(for: platform) { buildSettings[platform.deploymentTargetSetting] = .string(version.deploymentTarget) } } // Prevent setting presets from overwriting settings in project xcconfig files if let configPath = configFiles[config.name] { buildSettings = removeConfigFileSettings(from: buildSettings, configPath: configPath) } buildSettings += getBuildSettings(settings: settings, config: config) return buildSettings } public func getTargetBuildSettings(target: Target, config: Config) -> BuildSettings { var buildSettings = BuildSettings() // list of supported destination sorted by priority let specSupportedDestinations = target.supportedDestinations?.sorted(by: { $0.priority < $1.priority }) ?? [] if options.settingPresets.applyTarget { let platform: Platform if target.platform == .auto, let firstDestination = specSupportedDestinations.first, let firstDestinationPlatform = Platform(rawValue: firstDestination.rawValue) { platform = firstDestinationPlatform } else { platform = target.platform } buildSettings += SettingsPresetFile.platform(platform).getBuildSettings() buildSettings += SettingsPresetFile.product(target.type).getBuildSettings() buildSettings += SettingsPresetFile.productPlatform(target.type, platform).getBuildSettings() if target.platform == .auto { // this fix is necessary because the platform preset overrides the original value buildSettings["SDKROOT"] = .string(Platform.auto.rawValue) } if !specSupportedDestinations.isEmpty { var supportedPlatforms: [String] = [] var targetedDeviceFamily: [String] = [] for supportedDestination in specSupportedDestinations { let supportedPlatformBuildSettings = SettingsPresetFile.supportedDestination(supportedDestination).getBuildSettings() buildSettings += supportedPlatformBuildSettings if let value = supportedPlatformBuildSettings?["SUPPORTED_PLATFORMS"]?.stringValue { supportedPlatforms += value.components(separatedBy: " ") } if let value = supportedPlatformBuildSettings?["TARGETED_DEVICE_FAMILY"]?.stringValue { targetedDeviceFamily += value.components(separatedBy: ",") } } buildSettings["SUPPORTED_PLATFORMS"] = .string(supportedPlatforms.joined(separator: " ")) buildSettings["TARGETED_DEVICE_FAMILY"] = .string(targetedDeviceFamily.joined(separator: ",")) } } // apply custom platform version if let version = target.deploymentTarget { if !specSupportedDestinations.isEmpty { for supportedDestination in specSupportedDestinations { if let platform = Platform(rawValue: supportedDestination.rawValue) { buildSettings[platform.deploymentTargetSetting] = .string(version.deploymentTarget) } } } else { buildSettings[target.platform.deploymentTargetSetting] = .string(version.deploymentTarget) } } // Prevent setting presets from overrwriting settings in target xcconfig files if let configPath = target.configFiles[config.name] { buildSettings = removeConfigFileSettings(from: buildSettings, configPath: configPath) } // Prevent setting presets from overrwriting settings in project xcconfig files if let configPath = configFiles[config.name] { buildSettings = removeConfigFileSettings(from: buildSettings, configPath: configPath) } buildSettings += getBuildSettings(settings: target.settings, config: config) return buildSettings } public func getBuildSettings(settings: Settings, config: Config) -> BuildSettings { var buildSettings: BuildSettings = [:] for group in settings.groups { if let settings = settingGroups[group] { buildSettings += getBuildSettings(settings: settings, config: config) } } buildSettings += settings.buildSettings for (configVariant, settings) in settings.configSettings { let isPartialMatch = config.name.lowercased().contains(configVariant.lowercased()) if isPartialMatch { let exactConfig = getConfig(configVariant) let matchesExactlyToOtherConfig = exactConfig != nil && exactConfig?.name != config.name if !matchesExactlyToOtherConfig { buildSettings += getBuildSettings(settings: settings, config: config) } } } return buildSettings } // combines all levels of a target's settings: target, target config, project, project config public func getCombinedBuildSetting(_ setting: String, target: ProjectTarget, config: Config) -> BuildSetting? { if let target = target as? Target, let value = getTargetBuildSettings(target: target, config: config)[setting] { return value } if let configFilePath = target.configFiles[config.name], let value = loadConfigFileBuildSettings(path: configFilePath)?[setting] { return value } if let value = getProjectBuildSettings(config: config)[setting] { return value } if let configFilePath = configFiles[config.name], let value = loadConfigFileBuildSettings(path: configFilePath)?[setting] { return value } return nil } public func getBoolBuildSetting(_ setting: String, target: ProjectTarget, config: Config) -> Bool? { getCombinedBuildSetting(setting, target: target, config: config)?.boolValue } public func targetHasBuildSetting(_ setting: String, target: Target, config: Config) -> Bool { getCombinedBuildSetting(setting, target: target, config: config) != nil } /// Removes values from build settings if they are defined in an xcconfig file private func removeConfigFileSettings(from buildSettings: BuildSettings, configPath: String) -> BuildSettings { var buildSettings = buildSettings if let configSettings = loadConfigFileBuildSettings(path: configPath) { for key in configSettings.keys { // FIXME: Catch platform specifier. e.g. LD_RUNPATH_SEARCH_PATHS[sdk=iphone*] buildSettings.removeValue(forKey: key) buildSettings.removeValue(forKey: key.quoted) } } return buildSettings } /// Returns cached build settings from a config file private func loadConfigFileBuildSettings(path: String) -> BuildSettings? { let configFilePath = basePath + path if let cached = configFileSettings[configFilePath.string] { return cached.value } else { guard let configFile = try? XCConfig(path: configFilePath) else { configFileSettings[configFilePath.string] = .nothing return nil } let settings = configFile.flattenedBuildSettings() configFileSettings[configFilePath.string] = .cached(settings) return settings } } } private enum Cached { case cached(T) case nothing var value: T? { switch self { case let .cached(value): return value case .nothing: return nil } } } // cached flattened xcconfig file settings private var configFileSettings: [String: Cached] = [:] // cached setting preset settings private var settingPresetSettings: [String: Cached] = [:] extension SettingsPresetFile { public func getBuildSettings() -> BuildSettings? { if let cached = settingPresetSettings[path] { return cached.value } let bundlePath = Path(Bundle.main.bundlePath) let relativePath = Path("SettingPresets/\(path).yml") var possibleSettingsPaths: [Path] = [ relativePath, bundlePath + relativePath, bundlePath + "../share/xcodegen/\(relativePath)", Path(#file).parent().parent().parent() + relativePath, ] if let resourcePath = Bundle.main.resourcePath { possibleSettingsPaths.append(Path(resourcePath) + relativePath) } if let symlink = try? (bundlePath + "xcodegen").symlinkDestination() { possibleSettingsPaths = [ symlink.parent() + relativePath, ] + possibleSettingsPaths } if let moduleResourcePath = Bundle.availableModule?.path(forResource: "SettingPresets", ofType: nil) { possibleSettingsPaths.append(Path(moduleResourcePath) + "\(path).yml") } guard let settingsPath = possibleSettingsPaths.first(where: { $0.exists }) else { switch self { case .base, .config, .platform, .supportedDestination: print("No \"\(name)\" settings found") case .product, .productPlatform: break } settingPresetSettings[path] = .nothing return nil } guard let dictionary = try? loadYamlDictionary(path: settingsPath) else { print("Error parsing \"\(name)\" settings") return nil } let buildSettings: BuildSettings = dictionary.mapValues { BuildSetting(any: $0) } settingPresetSettings[path] = .cached(buildSettings) return buildSettings } } private class BundleFinder {} /// The default SPM generated `Bundle.module` crashes on runtime if there is no .bundle file. /// Below implementation modified from generated `Bundle.module` code which call `fatalError` if .bundle file not found. private extension Bundle { /// Returns the resource bundle associated with the current Swift module. static let availableModule: Bundle? = { let bundleName = "XcodeGen_XcodeGenKit" let overrides: [URL] #if DEBUG // The 'PACKAGE_RESOURCE_BUNDLE_PATH' name is preferred since the expected value is a path. The // check for 'PACKAGE_RESOURCE_BUNDLE_URL' will be removed when all clients have switched over. // This removal is tracked by rdar://107766372. if let override = ProcessInfo.processInfo.environment["PACKAGE_RESOURCE_BUNDLE_PATH"] ?? ProcessInfo.processInfo.environment["PACKAGE_RESOURCE_BUNDLE_URL"] { overrides = [URL(fileURLWithPath: override)] } else { overrides = [] } #else overrides = [] #endif let candidates = overrides + [ // Bundle should be present here when the package is linked into an App. Bundle.main.resourceURL, // Bundle should be present here when the package is linked into a framework. Bundle(for: BundleFinder.self).resourceURL, // For command-line tools. Bundle.main.bundleURL, ] for candidate in candidates { let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle") if let bundle = bundlePath.flatMap(Bundle.init(url:)) { return bundle } } return nil }() } ================================================ FILE: Sources/XcodeGenKit/SettingsPresetFile.swift ================================================ import Foundation import ProjectSpec import XcodeProj public enum SettingsPresetFile { case config(ConfigType) case platform(Platform) case supportedDestination(SupportedDestination) case product(PBXProductType) case productPlatform(PBXProductType, Platform) case base var path: String { switch self { case let .config(config): return "Configs/\(config.rawValue)" case let .platform(platform): return "Platforms/\(platform.rawValue)" case let .supportedDestination(supportedDestination): return "SupportedDestinations/\(supportedDestination.rawValue)" case let .product(product): return "Products/\(product.name)" case let .productPlatform(product, platform): return "Product_Platform/\(product.name)_\(platform.rawValue)" case .base: return "base" } } var name: String { switch self { case let .config(config): return "\(config.rawValue) config" case let .platform(platform): return platform.rawValue case let .supportedDestination(supportedDestination): return supportedDestination.rawValue case let .product(product): return product.name case let .productPlatform(product, platform): return "\(platform) \(product)" case .base: return "base" } } } ================================================ FILE: Sources/XcodeGenKit/SourceGenerator.swift ================================================ import Foundation import PathKit import ProjectSpec import XcodeProj import XcodeGenCore struct SourceFile { let path: Path let fileReference: PBXFileElement let buildFile: PBXBuildFile let buildPhase: BuildPhaseSpec? } class SourceGenerator { var rootGroups: Set = [] private let projectDirectory: Path? private var fileReferencesByPath: [String: PBXFileElement] = [:] private var groupsByPath: [Path: PBXGroup] = [:] private var variantGroupsByPath: [Path: PBXVariantGroup] = [:] private var syncedGroupsByPath: [String: PBXFileSystemSynchronizedRootGroup] = [:] private let project: Project let pbxProj: PBXProj private var defaultExcludedFiles = [ ".DS_Store", ] private let defaultExcludedExtensions = [ "orig", ] private(set) var knownRegions: Set = [] /// The effective base path for resolving group and file paths in the generated project. /// Uses `projectDirectory` when the xcodeproj is generated in a different location than the spec. private var basePath: Path { projectDirectory ?? project.basePath } init(project: Project, pbxProj: PBXProj, projectDirectory: Path?) { self.project = project self.pbxProj = pbxProj self.projectDirectory = projectDirectory } private func resolveGroupPath(_ path: Path, isTopLevelGroup: Bool) -> String { if isTopLevelGroup, let relativePath = try? path.relativePath(from: basePath).string { return relativePath } else { return path.lastComponent } } @discardableResult func addObject(_ object: T, context: String? = nil) -> T { pbxProj.add(object: object) object.context = context return object } func createLocalPackage(path: Path, group: Path?) throws { var parentGroup: String = project.options.localPackagesGroup ?? "Packages" if let group { parentGroup = group.string } let absolutePath = project.basePath + path.normalize() // Get the local package's relative path from the project root let fileReferencePath = try? absolutePath.relativePath(from: basePath).string let fileReference = addObject( PBXFileReference( sourceTree: .sourceRoot, name: absolutePath.lastComponent, lastKnownFileType: "folder", path: fileReferencePath ) ) if parentGroup == "" { rootGroups.insert(fileReference) } else { let parentGroups = parentGroup.components(separatedBy: "/") createParentGroups(parentGroups, for: fileReference) } } /// Collects an array complete of all `SourceFile` objects that make up the target based on the provided `TargetSource` definitions. /// /// - Parameters: /// - targetType: The type of target that the source files should belong to. /// - sources: The array of sources defined as part of the targets spec. /// - buildPhases: A dictionary containing any build phases that should be applied to source files at specific paths in the event that the associated `TargetSource` didn't already define a `buildPhase`. Values from this dictionary are used in cases where the project generator knows more about a file than the spec/filesystem does (i.e if the file should be treated as the targets Info.plist and so on). func getAllSourceFiles(targetType: PBXProductType, sources: [TargetSource], buildPhases: [Path : BuildPhaseSpec]) throws -> [SourceFile] { try sources.flatMap { try getSourceFiles(targetType: targetType, targetSource: $0, buildPhases: buildPhases) } } // get groups without build files. Use for Project.fileGroups func getFileGroups(path: String) throws { _ = try getSourceFiles(targetType: .none, targetSource: TargetSource(path: path), buildPhases: [:]) } func getFileType(path: Path) -> FileType? { if let fileExtension = path.extension { return project.options.fileTypes[fileExtension] ?? FileType.defaultFileTypes[fileExtension] } else { return nil } } private func makeDestinationFilters(for path: Path, with filters: [SupportedDestination]?, or inferDestinationFiltersByPath: Bool?) -> [String]? { if let filters = filters, !filters.isEmpty { return filters.map { $0.string } } else if inferDestinationFiltersByPath == true { for supportedDestination in SupportedDestination.allCases { let regex1 = try? NSRegularExpression(pattern: "\\/\(supportedDestination)\\/", options: .caseInsensitive) let regex2 = try? NSRegularExpression(pattern: "\\_\(supportedDestination)\\.swift$", options: .caseInsensitive) if regex1?.isMatch(to: path.string) == true || regex2?.isMatch(to: path.string) == true { return [supportedDestination.string] } } } return nil } func generateSourceFile(targetType: PBXProductType, targetSource: TargetSource, path: Path, fileReference: PBXFileElement? = nil, buildPhases: [Path: BuildPhaseSpec]) -> SourceFile { let fileReference = fileReference ?? fileReferencesByPath[path.string.lowercased()]! var settings: [String: BuildFileSetting] = [:] let fileType = getFileType(path: path) var attributes: [String] = targetSource.attributes + (fileType?.attributes ?? []) var chosenBuildPhase: BuildPhaseSpec? var compilerFlags: String = "" let assetTags: [String] = targetSource.resourceTags + (fileType?.resourceTags ?? []) let headerVisibility = targetSource.headerVisibility ?? .public if let buildPhase = targetSource.buildPhase { chosenBuildPhase = buildPhase } else if resolvedTargetSourceType(for: targetSource, at: path) == .folder { chosenBuildPhase = .resources } else if let buildPhase = buildPhases[path] { chosenBuildPhase = buildPhase } else { chosenBuildPhase = getDefaultBuildPhase(for: path, targetType: targetType) } if chosenBuildPhase == .headers && targetType == .staticLibrary { // Static libraries don't support the header build phase // For public headers they need to be copied if headerVisibility == .public { chosenBuildPhase = .copyFiles(BuildPhaseSpec.CopyFilesSettings( destination: .productsDirectory, subpath: "include/$(PRODUCT_NAME)", phaseOrder: .preCompile )) } else { chosenBuildPhase = nil } } if chosenBuildPhase == .headers { if headerVisibility != .project { // Xcode doesn't write the default of project attributes.append(headerVisibility.settingName) } } if let flags = fileType?.compilerFlags { compilerFlags += flags.joined(separator: " ") } if !targetSource.compilerFlags.isEmpty { if !compilerFlags.isEmpty { compilerFlags += " " } compilerFlags += targetSource.compilerFlags.joined(separator: " ") } if chosenBuildPhase == .sources && !compilerFlags.isEmpty { settings["COMPILER_FLAGS"] = .string(compilerFlags) } if !attributes.isEmpty { settings["ATTRIBUTES"] = .array(attributes) } if chosenBuildPhase == .resources && !assetTags.isEmpty { settings["ASSET_TAGS"] = .array(assetTags) } let platforms = makeDestinationFilters(for: path, with: targetSource.destinationFilters, or: targetSource.inferDestinationFiltersByPath) let buildFile = PBXBuildFile(file: fileReference, settings: settings.isEmpty ? nil : settings, platformFilters: platforms) return SourceFile( path: path, fileReference: fileReference, buildFile: buildFile, buildPhase: chosenBuildPhase ) } func getContainedFileReference(path: Path) -> PBXFileElement { let createIntermediateGroups = project.options.createIntermediateGroups let parentPath = path.parent() let fileReference = getFileReference(path: path, inPath: parentPath) let parentGroup = getGroup( path: parentPath, mergingChildren: [fileReference], createIntermediateGroups: createIntermediateGroups, hasCustomParent: false, isBaseGroup: true ) if createIntermediateGroups { createIntermediaGroups(for: parentGroup, at: parentPath) } return fileReference } func getFileReference(path: Path, inPath: Path, name: String? = nil, sourceTree: PBXSourceTree = .group, lastKnownFileType: String? = nil) -> PBXFileElement { let fileReferenceKey = path.string.lowercased() if let fileReference = fileReferencesByPath[fileReferenceKey] { return fileReference } else { let fileReferencePath = (try? path.relativePath(from: inPath)) ?? path var fileReferenceName: String? = name ?? fileReferencePath.lastComponent if fileReferencePath.string == fileReferenceName { fileReferenceName = nil } let lastKnownFileType = lastKnownFileType ?? Xcode.fileType(path: path) if path.extension == "xcdatamodeld" { let versionedModels = (try? path.children()) ?? [] // Sort the versions alphabetically let sortedPaths = versionedModels .filter { $0.extension == "xcdatamodel" } .sorted { $0.string.localizedStandardCompare($1.string) == .orderedAscending } let modelFileReferences = sortedPaths.map { path in addObject( PBXFileReference( sourceTree: .group, lastKnownFileType: "wrapper.xcdatamodel", path: path.lastComponent ) ) } // If no current version path is found we fall back to alphabetical // order by taking the last item in the sortedPaths array let currentVersionPath = findCurrentCoreDataModelVersionPath(using: versionedModels) ?? sortedPaths.last let currentVersion: PBXFileReference? = { guard let indexOf = sortedPaths.firstIndex(where: { $0 == currentVersionPath }) else { return nil } return modelFileReferences[indexOf] }() let versionGroup = addObject(XCVersionGroup( currentVersion: currentVersion, path: fileReferencePath.string, sourceTree: sourceTree, versionGroupType: "wrapper.xcdatamodel", children: modelFileReferences )) fileReferencesByPath[fileReferenceKey] = versionGroup return versionGroup } else { // For all extensions other than `xcdatamodeld` let fileReference = addObject( PBXFileReference( sourceTree: sourceTree, name: fileReferenceName, lastKnownFileType: lastKnownFileType, path: fileReferencePath.string ) ) fileReferencesByPath[fileReferenceKey] = fileReference return fileReference } } } /// returns a default build phase for a given path. This is based off the filename private func getDefaultBuildPhase(for path: Path, targetType: PBXProductType) -> BuildPhaseSpec? { if let buildPhase = getFileType(path: path)?.buildPhase { return buildPhase } if let fileExtension = path.extension { switch fileExtension { case "modulemap": guard targetType == .staticLibrary else { return nil } return .copyFiles(BuildPhaseSpec.CopyFilesSettings( destination: .productsDirectory, subpath: "include/$(PRODUCT_NAME)", phaseOrder: .preCompile )) case "swiftcrossimport": guard targetType == .framework else { return nil } return .copyFiles(BuildPhaseSpec.CopyFilesSettings( destination: .productsDirectory, subpath: "$(PRODUCT_NAME).framework/Modules", phaseOrder: .preCompile )) default: return .resources } } return nil } /// Create a group or return an existing one at the path. /// Any merged children are added to a new group or merged into an existing one. private func getGroup(path: Path, name: String? = nil, mergingChildren children: [PBXFileElement], createIntermediateGroups: Bool, hasCustomParent: Bool, isBaseGroup: Bool) -> PBXGroup { let groupReference: PBXGroup if let cachedGroup = groupsByPath[path] { var cachedGroupChildren = cachedGroup.children for child in children { // only add the children that aren't already in the cachedGroup // Check equality by path and sourceTree because XcodeProj.PBXObject.== is very slow. if !cachedGroupChildren.contains(where: { $0.name == child.name && $0.path == child.path && $0.sourceTree == child.sourceTree }) { cachedGroupChildren.append(child) child.parent = cachedGroup } } cachedGroup.children = cachedGroupChildren groupReference = cachedGroup } else { // lives outside the project base path let isOutOfBasePath = !path.absolute().string.contains(project.basePath.absolute().string) // whether the given path is a strict parent of the project base path // e.g. foo/bar is a parent of foo/bar/baz, but not foo/baz let isParentOfBasePath = isOutOfBasePath && ((try? path.isParent(of: project.basePath)) == true) // has no valid parent paths let isRootPath = (isBaseGroup && isOutOfBasePath && isParentOfBasePath) || path.parent() == project.basePath // is a top level group in the project let isTopLevelGroup = !hasCustomParent && ((isBaseGroup && !createIntermediateGroups) || isRootPath || isParentOfBasePath) let groupName = name ?? path.lastComponent let groupPath = resolveGroupPath(path, isTopLevelGroup: hasCustomParent || isTopLevelGroup) let group = PBXGroup( children: children, sourceTree: .group, name: groupName != groupPath ? groupName : nil, path: groupPath ) groupReference = addObject(group) groupsByPath[path] = groupReference if isTopLevelGroup { rootGroups.insert(groupReference) } } return groupReference } /// Creates a variant group or returns an existing one at the path private func getVariantGroup(path: Path, inPath: Path) -> PBXVariantGroup { let variantGroup: PBXVariantGroup if let cachedGroup = variantGroupsByPath[path] { variantGroup = cachedGroup } else { let group = PBXVariantGroup( sourceTree: .group, name: path.lastComponent ) variantGroup = addObject(group) variantGroupsByPath[path] = variantGroup } return variantGroup } /// Returns the expanded set of excluded paths for a target source by resolving its exclude glob patterns. func expandedExcludes(for targetSource: TargetSource) -> Set { getSourceMatches(targetSource: targetSource, patterns: targetSource.excludes) } /// Returns the expanded set of exception paths for a synced folder, including excludes and non-included files. func syncedFolderExceptions(for targetSource: TargetSource, at syncedPath: Path) -> Set { let excludePaths = expandedExcludes(for: targetSource) if targetSource.includes.isEmpty { return excludePaths } let includePaths = SortedArray(getSourceMatches(targetSource: targetSource, patterns: targetSource.includes)) var exceptions: Set = [] func findExceptions(in path: Path) { guard let children = try? path.children() else { return } for child in children { if isIncludedPath(child, excludePaths: excludePaths, includePaths: includePaths) { if child.isDirectory && !Xcode.isDirectoryFileWrapper(path: child) { findExceptions(in: child) } } else { exceptions.insert(child) } } } findExceptions(in: syncedPath) return exceptions } /// Collects all the excluded paths within the targetSource private func getSourceMatches(targetSource: TargetSource, patterns: [String]) -> Set { let rootSourcePath = project.basePath + targetSource.path return Set( patterns.parallelMap { pattern in guard !pattern.isEmpty else { return [] } return Glob(pattern: "\(rootSourcePath)/\(pattern)") .map { Path($0) } .map { guard $0.isDirectory else { return [$0] } return (try? $0.recursiveChildren()) ?? [] } .reduce([], +) } .reduce([], +) ) } /// Expands glob patterns in `explicitFolders` relative to the synced root path. private func resolveExplicitFolders(targetSource: TargetSource) -> [String] { let rootSourcePath = project.basePath + targetSource.path return targetSource.explicitFolders.flatMap { pattern in let matches = Glob(pattern: "\(rootSourcePath)/\(pattern)") .map { Path($0) } .filter { $0.isDirectory } .compactMap { try? $0.relativePath(from: rootSourcePath).string } .sorted() return matches.isEmpty ? [pattern] : matches } } /// Checks whether the path is not in any default or TargetSource excludes func isIncludedPath(_ path: Path, excludePaths: Set, includePaths: SortedArray?) -> Bool { return !defaultExcludedFiles.contains(where: { path.lastComponent == $0 }) && !(path.extension.map(defaultExcludedExtensions.contains) ?? false) && !excludePaths.contains(path) // If includes is empty, it's included. If it's not empty, the path either needs to match exactly, or it needs to be a direct parent of an included path. && (includePaths.flatMap { _isIncludedPathSorted(path, sortedPaths: $0) } ?? true) } private func _isIncludedPathSorted(_ path: Path, sortedPaths: SortedArray) -> Bool { guard let idx = sortedPaths.firstIndex(where: { $0 >= path }) else { return false } let foundPath = sortedPaths.value[idx] return foundPath.description.hasPrefix(path.description) } /// Gets all the children paths that aren't excluded private func getSourceChildren(targetSource: TargetSource, dirPath: Path, excludePaths: Set, includePaths: SortedArray?) throws -> [Path] { try dirPath.children() .filter { if $0.isDirectory { let children = try $0.children() if children.isEmpty { return project.options.generateEmptyDirectories } return !children .filter { self.isIncludedPath($0, excludePaths: excludePaths, includePaths: includePaths) } .isEmpty } else if $0.isFile { return self.isIncludedPath($0, excludePaths: excludePaths, includePaths: includePaths) } else { return false } } } /// creates all the source files and groups they belong to for a given targetSource private func getGroupSources( targetType: PBXProductType, targetSource: TargetSource, path: Path, isBaseGroup: Bool, hasCustomParent: Bool, excludePaths: Set, includePaths: SortedArray?, buildPhases: [Path: BuildPhaseSpec] ) throws -> (sourceFiles: [SourceFile], groups: [PBXGroup]) { let children = try getSourceChildren(targetSource: targetSource, dirPath: path, excludePaths: excludePaths, includePaths: includePaths) let createIntermediateGroups = targetSource.createIntermediateGroups ?? project.options.createIntermediateGroups let nonLocalizedChildren = children.filter { $0.extension != "lproj" } let stringCatalogChildren = children.filter { $0.extension == "xcstrings" } let directories = nonLocalizedChildren .filter { if let fileType = getFileType(path: $0) { return !fileType.file } else { return $0.isDirectory && !Xcode.isDirectoryFileWrapper(path: $0) } } let filePaths = nonLocalizedChildren .filter { if let fileType = getFileType(path: $0) { return fileType.file } else { return $0.isFile || $0.isDirectory && Xcode.isDirectoryFileWrapper(path: $0) } } let localisedDirectories = children .filter { $0.extension == "lproj" } var groupChildren: [PBXFileElement] = filePaths.map { getFileReference(path: $0, inPath: path) } var allSourceFiles: [SourceFile] = filePaths.map { generateSourceFile(targetType: targetType, targetSource: targetSource, path: $0, buildPhases: buildPhases) } var groups: [PBXGroup] = [] for path in directories { let subGroups = try getGroupSources( targetType: targetType, targetSource: targetSource, path: path, isBaseGroup: false, hasCustomParent: false, excludePaths: excludePaths, includePaths: includePaths, buildPhases: buildPhases ) guard !subGroups.sourceFiles.isEmpty || project.options.generateEmptyDirectories else { continue } allSourceFiles += subGroups.sourceFiles if let firstGroup = subGroups.groups.first { groupChildren.append(firstGroup) groups += subGroups.groups } else if project.options.generateEmptyDirectories { groups += subGroups.groups } } // find the base localised directory let baseLocalisedDirectory: Path? = { func findLocalisedDirectory(by languageId: String) -> Path? { localisedDirectories.first { $0.lastComponent == "\(languageId).lproj" } } return findLocalisedDirectory(by: "Base") ?? findLocalisedDirectory(by: NSLocale.canonicalLanguageIdentifier(from: project.options.developmentLanguage ?? "en")) }() knownRegions.formUnion(localisedDirectories.map { $0.lastComponentWithoutExtension }) // XCode 15 - Detect known regions from locales present in string catalogs let stringCatalogsLocales = stringCatalogChildren .compactMap { StringCatalog(from: $0) } .reduce(Set(), { partialResult, stringCatalog in partialResult.union(stringCatalog.includedLocales) }) knownRegions.formUnion(stringCatalogsLocales) // create variant groups of the base localisation first var baseLocalisationVariantGroups: [PBXVariantGroup] = [] if let baseLocalisedDirectory = baseLocalisedDirectory { let filePaths = try baseLocalisedDirectory.children() .filter { self.isIncludedPath($0, excludePaths: excludePaths, includePaths: includePaths) } .sorted() for filePath in filePaths { let variantGroup = getVariantGroup(path: filePath, inPath: path) groupChildren.append(variantGroup) baseLocalisationVariantGroups.append(variantGroup) let sourceFile = generateSourceFile(targetType: targetType, targetSource: targetSource, path: filePath, fileReference: variantGroup, buildPhases: buildPhases) allSourceFiles.append(sourceFile) } } // add references to localised resources into base localisation variant groups for localisedDirectory in localisedDirectories { let localisationName = localisedDirectory.lastComponentWithoutExtension let filePaths = try localisedDirectory.children() .filter { self.isIncludedPath($0, excludePaths: excludePaths, includePaths: includePaths) } .sorted { $0.lastComponent < $1.lastComponent } for filePath in filePaths { // find base localisation variant group // ex: Foo.strings will be added to Foo.strings or Foo.storyboard variant group let variantGroup = baseLocalisationVariantGroups .first { Path($0.name!).lastComponent == filePath.lastComponent } ?? baseLocalisationVariantGroups.first { Path($0.name!).lastComponentWithoutExtension == filePath.lastComponentWithoutExtension } let fileReference = getFileReference( path: filePath, inPath: path, name: variantGroup != nil ? localisationName : filePath.lastComponent ) if let variantGroup = variantGroup { if !variantGroup.children.contains(fileReference) { variantGroup.children.append(fileReference) } } else { // add SourceFile to group if there is no Base.lproj directory let sourceFile = generateSourceFile(targetType: targetType, targetSource: targetSource, path: filePath, fileReference: fileReference, buildPhases: buildPhases) allSourceFiles.append(sourceFile) groupChildren.append(fileReference) } } } let group = getGroup( path: path, mergingChildren: groupChildren, createIntermediateGroups: createIntermediateGroups, hasCustomParent: hasCustomParent, isBaseGroup: isBaseGroup ) if createIntermediateGroups { createIntermediaGroups(for: group, at: path) } groups.insert(group, at: 0) return (allSourceFiles, groups) } /// creates source files private func getSourceFiles(targetType: PBXProductType, targetSource: TargetSource, buildPhases: [Path: BuildPhaseSpec]) throws -> [SourceFile] { // generate excluded paths let path = project.basePath + targetSource.path let excludePaths = getSourceMatches(targetSource: targetSource, patterns: targetSource.excludes) // generate included paths. Excluded paths will override this. let includePaths = targetSource.includes.isEmpty ? nil : getSourceMatches(targetSource: targetSource, patterns: targetSource.includes) let type = resolvedTargetSourceType(for: targetSource, at: path) let customParentGroups = (targetSource.group ?? "").split(separator: "/").map { String($0) } let hasCustomParent = !customParentGroups.isEmpty let createIntermediateGroups = targetSource.createIntermediateGroups ?? project.options.createIntermediateGroups var sourceFiles: [SourceFile] = [] let sourceReference: PBXFileElement var sourcePath = path switch type { case .folder: let fileReference = getFileReference( path: path, inPath: project.basePath, name: targetSource.name ?? path.lastComponent, sourceTree: .sourceRoot, lastKnownFileType: "folder" ) if !(createIntermediateGroups || hasCustomParent) || path.parent() == project.basePath { rootGroups.insert(fileReference) } let sourceFile = generateSourceFile(targetType: targetType, targetSource: targetSource, path: path, buildPhases: buildPhases) sourceFiles.append(sourceFile) sourceReference = fileReference case .file: let parentPath = path.parent() let fileReference = getFileReference(path: path, inPath: parentPath, name: targetSource.name) let sourceFile = generateSourceFile(targetType: targetType, targetSource: targetSource, path: path, buildPhases: buildPhases) if hasCustomParent { sourcePath = path sourceReference = fileReference } else if parentPath == project.basePath { sourcePath = path sourceReference = fileReference rootGroups.insert(fileReference) } else { let parentGroup = getGroup( path: parentPath, mergingChildren: [fileReference], createIntermediateGroups: createIntermediateGroups, hasCustomParent: hasCustomParent, isBaseGroup: true ) sourcePath = parentPath sourceReference = parentGroup } sourceFiles.append(sourceFile) case .group: if targetSource.optional && !path.exists { // This group is missing, so if's optional just return an empty array return [] } let (groupSourceFiles, groups) = try getGroupSources( targetType: targetType, targetSource: targetSource, path: path, isBaseGroup: true, hasCustomParent: hasCustomParent, excludePaths: excludePaths, includePaths: includePaths.flatMap(SortedArray.init(_:)), buildPhases: buildPhases ) let group = groups.first! if let name = targetSource.name { group.name = name } sourceFiles += groupSourceFiles sourceReference = group case .syncedFolder: let relativePath = (try? path.relativePath(from: project.basePath)) ?? path let resolvedExplicitFolders = resolveExplicitFolders(targetSource: targetSource) let syncedRootGroup: PBXFileSystemSynchronizedRootGroup if let existingGroup = syncedGroupsByPath[relativePath.string] { syncedRootGroup = existingGroup let newExplicitFolders = Set(syncedRootGroup.explicitFolders ?? []) .union(resolvedExplicitFolders) .sorted() syncedRootGroup.explicitFolders = newExplicitFolders } else { syncedRootGroup = PBXFileSystemSynchronizedRootGroup( sourceTree: .group, path: relativePath.string, name: targetSource.name, explicitFileTypes: [:], exceptions: [], explicitFolders: resolvedExplicitFolders ) addObject(syncedRootGroup) syncedGroupsByPath[relativePath.string] = syncedRootGroup } sourceReference = syncedRootGroup if !(createIntermediateGroups || hasCustomParent) || path.parent() == project.basePath { rootGroups.insert(syncedRootGroup) } let sourceFile = generateSourceFile( targetType: targetType, targetSource: targetSource, path: path, fileReference: syncedRootGroup, buildPhases: buildPhases ) sourceFiles.append(sourceFile) } if hasCustomParent { createParentGroups(customParentGroups, for: sourceReference) try makePathRelative(for: sourceReference, at: path) } else if createIntermediateGroups { createIntermediaGroups(for: sourceReference, at: sourcePath) if type != .folder { try makePathRelative(for: sourceReference, at: sourcePath) } } return sourceFiles } /// Returns the resolved `SourceType` for a given `TargetSource`. /// /// While `TargetSource` declares `type`, its optional and in the event that the value is not defined then we must resolve a sensible default based on the path of the source. private func resolvedTargetSourceType(for targetSource: TargetSource, at path: Path) -> SourceType { if let chosenType = targetSource.type { return chosenType } else { if path.isFile || path.extension != nil { return .file } else if let sourceType = project.options.defaultSourceDirectoryType { return sourceType } else { return .group } } } private func createParentGroups(_ parentGroups: [String], for fileElement: PBXFileElement) { guard let parentName = parentGroups.last else { return } let parentPath = project.basePath + Path(parentGroups.joined(separator: "/")) let parentPathExists = parentPath.exists let parentGroupAlreadyExists = groupsByPath[parentPath] != nil let parentGroup = getGroup( path: parentPath, mergingChildren: [fileElement], createIntermediateGroups: false, hasCustomParent: false, isBaseGroup: parentGroups.count == 1 ) // As this path is a custom group, remove the path reference if !parentPathExists { parentGroup.name = String(parentName) parentGroup.path = nil } if !parentGroupAlreadyExists { createParentGroups(parentGroups.dropLast(), for: parentGroup) } } // Add groups for all parents recursively private func createIntermediaGroups(for fileElement: PBXFileElement, at path: Path) { let parentPath = path.parent() guard parentPath != project.basePath else { // we've reached the top return } let hasParentGroup = groupsByPath[parentPath] != nil if !hasParentGroup { do { // if the path is a parent of the project base path (or if calculating that fails) // do not create a parent group // e.g. for project path foo/bar/baz // - create foo/baz // - create baz/ // - do not create foo let pathIsParentOfProject = try path.isParent(of: project.basePath) if pathIsParentOfProject { return } } catch { return } } let parentGroup = getGroup( path: parentPath, mergingChildren: [fileElement], createIntermediateGroups: true, hasCustomParent: false, isBaseGroup: false ) if !hasParentGroup { createIntermediaGroups(for: parentGroup, at: parentPath) } } // Make the fileElement path and name relative to its parents aggregated paths private func makePathRelative(for fileElement: PBXFileElement, at path: Path) throws { // This makes the fileElement path relative to its parent and not to the project. Xcode then rebuilds the actual // path for the file based on the hierarchy this fileElement lives in. var paths: [String] = [] var element: PBXFileElement = fileElement while true { guard let parent = element.parent else { break } if let path = parent.path { paths.insert(path, at: 0) } element = parent } let completePath = (basePath) + Path(paths.joined(separator: "/")) let relativePath = try path.relativePath(from: completePath) let relativePathString = relativePath.string if relativePathString != fileElement.path { fileElement.path = relativePathString fileElement.name = relativePath.lastComponent } } private func findCurrentCoreDataModelVersionPath(using versionedModels: [Path]) -> Path? { // Find and parse the current version model stored in the .xccurrentversion file guard let versionPath = versionedModels.first(where: { $0.lastComponent == ".xccurrentversion" }), let data = try? versionPath.read(), let plist = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any], let versionString = plist["_XCCurrentVersionName"] as? String else { return nil } return versionedModels.first(where: { $0.lastComponent == versionString }) } } ================================================ FILE: Sources/XcodeGenKit/StringCatalogDecoding.swift ================================================ import Foundation import JSONUtilities import PathKit struct StringCatalog { /** * Sample string catalog: * { * "sourceLanguage" : "en", * "strings" : { * "foo" : { * "localizations" : { * "en" : { * ... * }, * "es" : { * ... * }, * "it" : { * ... * } * } * } * } * } */ private struct CatalogItem { private enum JSONKeys: String { case localizations } private let key: String let locales: Set init?(key: String, from jsonDictionary: JSONDictionary) { guard let localizations = jsonDictionary[JSONKeys.localizations.rawValue] as? JSONDictionary else { return nil } self.key = key self.locales = Set(localizations.keys) } } private enum JSONKeys: String { case strings } private let strings: [CatalogItem] init?(from path: Path) { guard let catalogDictionary = try? JSONDictionary.from(url: path.url), let catalog = StringCatalog(from: catalogDictionary) else { return nil } self = catalog } private init?(from jsonDictionary: JSONDictionary) { guard let stringsDictionary = jsonDictionary[JSONKeys.strings.rawValue] as? JSONDictionary else { return nil } self.strings = stringsDictionary.compactMap { key, value -> CatalogItem? in guard let stringDictionary = value as? JSONDictionary else { return nil } return CatalogItem(key: key, from: stringDictionary) } } var includedLocales: Set { strings.reduce(Set(), { partialResult, catalogItem in partialResult.union(catalogItem.locales) }) } } ================================================ FILE: Sources/XcodeGenKit/Version.swift ================================================ import Foundation import ProjectSpec extension Project { public var xcodeVersion: String { XCodeVersion.parse(options.xcodeVersion ?? "14.3") } public var projectFormat: ProjectFormat { options.projectFormat.flatMap(ProjectFormat.init) ?? .default } var schemeVersion: String { "1.7" } var compatibilityVersion: String? { projectFormat.compatibilityVersion } var objectVersion: UInt { projectFormat.objectVersion } var preferredProjectObjectVersion: UInt? { projectFormat.preferredProjectObjectVersion } var minimizedProjectReferenceProxies: Int { 1 } } public struct XCodeVersion { public static func parse(_ version: String) -> String { if version.contains(".") { let parts = version.split(separator: ".").map(String.init) var string = "" let major = parts[0] if major.count == 1 { string = "0\(major)" } else { string = major } let minor = parts[1] string += minor if parts.count > 2 { let patch = parts[2] string += patch } else { string += "0" } return string } else if version.count == 2 { return "\(version)00" } else if version.count == 1 { return "0\(version)00" } else { return version } } } ================================================ FILE: Sources/XcodeGenKit/XCProjExtensions.swift ================================================ import Foundation import PathKit import XcodeProj extension PBXFileElement { public var nameOrPath: String { return name ?? path ?? "" } static func sortByNamePath(_ lhs: PBXFileElement, _ rhs: PBXFileElement) -> Bool { return lhs.namePathSortString.localizedStandardCompare(rhs.namePathSortString) == .orderedAscending } private var namePathSortString: String { // This string needs to be unique for all combinations of name & path or the order won't be stable. return "\(name ?? path ?? "")\t\(name ?? "")\t\(path ?? "")" } } extension PBXProj { public func printGroups() -> String { guard let project = projects.first, let mainGroup = project.mainGroup else { return "" } return printGroup(group: mainGroup) } public func printGroup(group: PBXGroup) -> String { var string = group.nameOrPath for child in group.children { if let group = child as? PBXGroup { string += "\n 📁 " + printGroup(group: group).replacingOccurrences(of: "\n ", with: "\n ") } else if let fileReference = child as? PBXFileReference { string += "\n 📄 " + fileReference.nameOrPath } else if let variantGroup = child as? PBXVariantGroup { string += "\n 🌎 " + variantGroup.nameOrPath } else if let versionGroup = child as? XCVersionGroup { string += "\n 🔢 " + versionGroup.nameOrPath } else if let syncedFolder = child as? PBXFileSystemSynchronizedRootGroup { string += "\n 📁 " + syncedFolder.nameOrPath } } return string } } extension Dictionary { public var valueArray: [Value] { Array(values) } } extension Xcode { public static func fileType(path: Path, productType: PBXProductType? = nil) -> String? { guard let fileExtension = path.extension else { return nil } switch (fileExtension, productType) { // cases that aren't handled (yet) in XcodeProj. case ("appex", .extensionKitExtension): return "wrapper.extensionkit-extension" case ("swiftcrossimport", _): return "wrapper.swiftcrossimport" case ("xcstrings", _): return "text.json.xcstrings" case ("icon", _): return "wrapper.icon" default: // fallback to XcodeProj defaults return Xcode.filetype(extension: fileExtension) } } public static func isDirectoryFileWrapper(path: Path) -> Bool { guard path.isDirectory else { return false } return fileType(path: path) != nil } } ================================================ FILE: Tests/FixtureTests/FixtureTests.swift ================================================ import PathKit import ProjectSpec import Spectre import XcodeGenKit import XcodeProj import XCTest import TestSupport class FixtureTests: XCTestCase { func testProjectFixture() throws { try skipIfNecessary() describe { $0.it("generates Test Project") { try generateXcodeProject(specPath: fixturePath + "TestProject/AnotherProject/project.yml") try generateXcodeProject(specPath: fixturePath + "TestProject/project.yml") } $0.it("generates Carthage Project") { try generateXcodeProject(specPath: fixturePath + "CarthageProject/project.yml") } $0.it("generates SPM Project") { try generateXcodeProject(specPath: fixturePath + "SPM/project.yml") } } } } private func generateXcodeProject(specPath: Path, file: String = #file, line: Int = #line) throws { let project = try Project(path: specPath) let generator = ProjectGenerator(project: project) let writer = FileWriter(project: project) let xcodeProject = try generator.generateXcodeProject(userName: "someUser") try writer.writeXcodeProject(xcodeProject) try writer.writePlists() } ================================================ FILE: Tests/Fixtures/CarthageProject/.gitignore ================================================ Carthage/Build/iOS Carthage/Build/watchOS Carthage/Build/tvOS Carthage/Build/Mac Carthage/Checkouts ================================================ FILE: Tests/Fixtures/CarthageProject/Cartfile ================================================ github "antitypical/Result" github "rpassis/CarthageTestFixture" github "ReactiveCocoa/ReactiveSwift" ~> 4.0 github "ReactiveCocoa/ReactiveCocoa" ~> 8.0 ================================================ FILE: Tests/Fixtures/CarthageProject/Cartfile.resolved ================================================ github "ReactiveCocoa/ReactiveCocoa" "8.0.2" github "ReactiveCocoa/ReactiveSwift" "4.0.0" github "antitypical/Result" "4.1.0" github "rpassis/CarthageTestFixture" "1.0" ================================================ FILE: Tests/Fixtures/CarthageProject/Carthage/Build/.Alamofire.version ================================================ { "Mac" : [ { "name" : "Alamofire", "hash" : "c8ee4910ecef2738eccd34a7abcae7d6561c0d709fbc301f660391018bf654b5" } ], "watchOS" : [ { "name" : "Alamofire", "hash" : "960f25b2b83d186bf62a05e59af8a6f7c99ac20201f6d948106c52917f29b7da" } ], "tvOS" : [ { "name" : "Alamofire", "hash" : "b8994794ee1378f98ceb7d294c84990d60fdabcc5f5a7d8386e71315ff583f1f" } ], "commitish" : "4.7.2", "iOS" : [ { "name" : "Alamofire", "hash" : "a000ec6f95d6fd12843007ba8c22e69c64e922b45ccfe34e1d38200b54e5e391" } ] } ================================================ FILE: Tests/Fixtures/CarthageProject/Carthage/Build/.CarthageTestFixture.version ================================================ { "Mac" : [ { "name" : "CarthageTestFixture", "hash" : "071b5d10a76f763129c32223af9f0e34674f2d6766e1b8dd0658c634c750d88f" }, { "name" : "DependencyFixtureB", "hash" : "65d7482cfefd8952762f57de885e9929aae8e6fe6a1c556e6bad75811f8d096f" }, { "name" : "DependencyFixtureA", "hash" : "ada254978598a68cce0e7cc919549e4888793ad2e9082fca38a533b9731710d5" } ], "watchOS" : [ { "name" : "CarthageTestFixture", "hash" : "6f5aeb82a53091a5573be1b16b58156a2444f8a568e4a4b4914a5658a11ce91c" }, { "name" : "DependencyFixtureB", "hash" : "a252cd58636f621f39daa48f89e881665d4d1da1debaf46d7ba926f59720e928" }, { "name" : "DependencyFixtureA", "hash" : "1f2df2a6f33953eb61d56c195dcf2b102d458dcec4ad93b70d1f802cf1d01081" } ], "tvOS" : [ { "name" : "CarthageTestFixture", "hash" : "4d0c084ea320ef9d2e216f60956bdb50e4975887f19f4817b26657a3796ab411" }, { "name" : "DependencyFixtureA", "hash" : "f257e4d0348770f210196dedaf16b51d54b255ec3e9e7b1cce50ea74e57d61b3" }, { "name" : "DependencyFixtureB", "hash" : "47377bbe103c06a25150aece9f48ac4b42065bc0c4543a7249395482676a3c38" } ], "commitish" : "1.0", "iOS" : [ { "name" : "CarthageTestFixture", "hash" : "7c2a74fc024a50478bec9ca8833302b2850d1971aadc6c9d890266ea29af6966" }, { "name" : "DependencyFixtureA", "hash" : "d9979f6f61ad275ae8d4fdf575df66fde694f584cc7f58f2dcb86e9fe81b9a3a" }, { "name" : "DependencyFixtureB", "hash" : "1c418bb70678e1f47cffc0ad8d8e6e014372ad9936fff6302abb86511980ac01" } ] } ================================================ FILE: Tests/Fixtures/CarthageProject/Carthage/Build/.ReactiveCocoa.version ================================================ { "Mac" : [ { "name" : "ReactiveMapKit", "hash" : "ca2fed26f8381df41034bab95e4751c59a2e6790fb2a6def8f4bc9cdee0c5733" }, { "name" : "ReactiveMapKit", "hash" : "ca2fed26f8381df41034bab95e4751c59a2e6790fb2a6def8f4bc9cdee0c5733" }, { "name" : "ReactiveCocoa", "hash" : "944bf96ea2e3431d7c70daef2c5664586439ca59e92092983acb1f36e3cb7f50" }, { "name" : "ReactiveCocoa", "hash" : "944bf96ea2e3431d7c70daef2c5664586439ca59e92092983acb1f36e3cb7f50" } ], "watchOS" : [ { "name" : "ReactiveCocoa", "hash" : "f7634a0c6323009b856c6cd6b0d76e9727006289b0edc1cb8a97b85f6549e16c" } ], "tvOS" : [ { "name" : "ReactiveMapKit", "hash" : "19c5331921afd44ec907b2dda06a522cf7f106580d689b39c0ffea989efa558a" }, { "name" : "ReactiveMapKit", "hash" : "19c5331921afd44ec907b2dda06a522cf7f106580d689b39c0ffea989efa558a" }, { "name" : "ReactiveCocoa", "hash" : "6fd1ed75f76420c86c66d2900d37f66ea6c9378007bf9c52aa8ee0806ae24477" }, { "name" : "ReactiveCocoa", "hash" : "6fd1ed75f76420c86c66d2900d37f66ea6c9378007bf9c52aa8ee0806ae24477" } ], "commitish" : "8.0.2", "iOS" : [ { "name" : "ReactiveMapKit", "hash" : "8d1f84508d7403d8acdb4c188377777273ceba4eb6237728f97dcc18d41b94c8" }, { "name" : "ReactiveMapKit", "hash" : "8d1f84508d7403d8acdb4c188377777273ceba4eb6237728f97dcc18d41b94c8" }, { "name" : "ReactiveCocoa", "hash" : "3ecd35da18aeade9d027be56daa4a925fbf7fc05fd7d559260ef2fc7945fb840" }, { "name" : "ReactiveCocoa", "hash" : "3ecd35da18aeade9d027be56daa4a925fbf7fc05fd7d559260ef2fc7945fb840" } ] } ================================================ FILE: Tests/Fixtures/CarthageProject/Carthage/Build/.ReactiveSwift.version ================================================ { "Mac" : [ { "name" : "ReactiveSwift", "hash" : "ed8de3ce3e03c940ba43f99c5de552b9af89c2d850bb8afb6e5f9f493489f45d" } ], "watchOS" : [ { "name" : "ReactiveSwift", "hash" : "c015d0bc75885b9358870ef16cb9e67b5ef8bcaa3a5ec90d513d623a1091f7eb" } ], "tvOS" : [ { "name" : "ReactiveSwift", "hash" : "4d4c3af69ff5b35bec1b76638989b29cb2d785536b2e124c5cfd6660d3eb2640" } ], "commitish" : "4.0.0", "iOS" : [ { "name" : "ReactiveSwift", "hash" : "bdf6f01656281b630b9f323625fc5134bb4fe4e061ac1885e3dc6498f9cad7dc" } ] } ================================================ FILE: Tests/Fixtures/CarthageProject/Carthage/Build/.Result.version ================================================ { "Mac" : [ { "name" : "Result", "hash" : "a608337c467d3ef16f53035c587dab84152fc14b3b4c8b597c5a973d3bfd0d63" } ], "watchOS" : [ { "name" : "Result", "hash" : "24680c05790afb8bd5d1ae8f1960905210546943e13fdea8e2994c2f8efdc0a5" } ], "tvOS" : [ { "name" : "Result", "hash" : "dca6d7e70e25cfa2b3363a0839dd13271b4da374f22d6cd357cf45b39299c17b" } ], "commitish" : "4.1.0", "iOS" : [ { "name" : "Result", "hash" : "5ad79b86004be535ee374685fbbd95db8d91f8c0458d3534aacf426930c7bbf6" } ] } ================================================ FILE: Tests/Fixtures/CarthageProject/Project.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 77; objects = { /* Begin PBXBuildFile section */ 044DBCB804B0066EDF0B69F8 /* DependencyFixtureB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B7FE5849A5B2AAD3E62D3A7 /* DependencyFixtureB.framework */; }; 10B5DCB476B73C344D90E179 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A3F65F497639D85FA73DECA8 /* ReactiveSwift.framework */; }; 11D327D6D70177DDC0A79581 /* ReactiveMapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8E5AC3513ADA944932071EC /* ReactiveMapKit.framework */; }; 192B14D32FB49F4BF60C9908 /* DependencyFixtureB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BACF4A03F73DCFC3D95C21A /* DependencyFixtureB.framework */; }; 199F923C0D8B980562D75B2D /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C206A943C9E4A6803327024F /* ReactiveCocoa.framework */; }; 21CA04F29CD0DEB0DD27B808 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0C5AC2545AE4D4F7F44E2E9B /* Result.framework */; }; 307B5322FC5A220652BA6FE0 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D296BB7355994040E197A1EE /* Result.framework */; }; 34840A4B524AECE0CB665CCC /* DependencyFixtureB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 685E6928BB8552F257AF32A1 /* DependencyFixtureB.framework */; }; 3674EA49FAD6B59A4548DD6C /* CarthageTestFixture.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50B5CD1B9C7CDDBA1A9B530E /* CarthageTestFixture.framework */; }; 50774BBEADF9BEC85EEB7789 /* DependencyFixtureA.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9CBE843396A0F73A18F4B11 /* DependencyFixtureA.framework */; }; 53984F64C12E67CDD10C2238 /* ReactiveMapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E7DD996C029F078EA21CB67 /* ReactiveMapKit.framework */; }; 55B624A520B0E35A386B6569 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D437562D61F4BB3DE6378823 /* ReactiveCocoa.framework */; }; 58CB7129BEAF736118826A76 /* DependencyFixtureB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 80192E4B3F765386B9E8CCC5 /* DependencyFixtureB.framework */; }; 5BCC106D55292C73C015FAB1 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2073C309D451D8C3BDD03C1B /* Alamofire.framework */; }; 5C7583816F6826B7D7DCD018 /* DependencyFixtureA.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D57001AD1EEEB31B0EEEBE2 /* DependencyFixtureA.framework */; }; 7881D9B16F8FD97E7ACD27E5 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 80FF7051B3E5445E5E0E9770 /* ReactiveCocoa.framework */; }; 82F5012DAD668BFCCBA5CC6D /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9010572AFE5F328D1A4F35E1 /* Alamofire.framework */; }; 90429469A473C6702082B4B7 /* CarthageTestFixture.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2AA735E34BB5D2B7DA7212B /* CarthageTestFixture.framework */; }; 92E4113033B920B0830492B6 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB9DC024AC7A05E4437051CB /* ReactiveSwift.framework */; }; 9B4B21707CB152E15BE7ECBD /* DependencyFixtureA.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9BFA0DB3F2851A12A91DABFB /* DependencyFixtureA.framework */; }; 9DF5931DAD58C35B830A0A75 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B76E17CE3574081D5BF45B44 /* Result.framework */; }; 9EBFD7ADF57175109DE0EA94 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25C6E95FE2D09F3D963EE98A /* Alamofire.framework */; }; A1163E2229C5AC64A8DFF967 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CD62DEFF074E2C4A35C6F30 /* ReactiveCocoa.framework */; }; A1AEAAB53EAEDA1C307871FA /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB178D03E75929F3F5B10C56 /* Result.framework */; }; A7732A9A7B252C45427FCE62 /* CarthageTestFixture.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8172E4733F98AD213CB42FA9 /* CarthageTestFixture.framework */; }; AEA1B2369A0CD094D0F426F4 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A01E2E08924015A0687C0B6 /* ReactiveSwift.framework */; }; B5DE9CE83E10AB067A1D113E /* CarthageTestFixture.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4778BFC65091F5B2B07A7F86 /* CarthageTestFixture.framework */; }; B9CE5164FCCF4CAC768FB1D3 /* ReactiveMapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2AC45CB275013ADC262D975 /* ReactiveMapKit.framework */; }; D9D64CBA06FDE4DEC421D5D7 /* DependencyFixtureA.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9175CD404DD510116BEC0234 /* DependencyFixtureA.framework */; }; E471368A1DEC4EF63D70D84C /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B2B50BF9BD704D4CA45A7A9 /* ReactiveSwift.framework */; }; F0E692AFBFDF2CE25EE123BD /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 944523E6CAE426EEEFB898A2 /* Alamofire.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 0C5AC2545AE4D4F7F44E2E9B /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Result.framework; sourceTree = ""; }; 0D57001AD1EEEB31B0EEEBE2 /* DependencyFixtureA.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = DependencyFixtureA.framework; sourceTree = ""; }; 1B7FE5849A5B2AAD3E62D3A7 /* DependencyFixtureB.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = DependencyFixtureB.framework; sourceTree = ""; }; 1E7DD996C029F078EA21CB67 /* ReactiveMapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ReactiveMapKit.framework; sourceTree = ""; }; 2073C309D451D8C3BDD03C1B /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Alamofire.framework; sourceTree = ""; }; 25C6E95FE2D09F3D963EE98A /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Alamofire.framework; sourceTree = ""; }; 3B2B50BF9BD704D4CA45A7A9 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ReactiveSwift.framework; sourceTree = ""; }; 3CD62DEFF074E2C4A35C6F30 /* ReactiveCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ReactiveCocoa.framework; sourceTree = ""; }; 41FC82ED1C4C3B7B3D7B2FB7 /* Framework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Framework.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4778BFC65091F5B2B07A7F86 /* CarthageTestFixture.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = CarthageTestFixture.framework; sourceTree = ""; }; 4A01E2E08924015A0687C0B6 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ReactiveSwift.framework; sourceTree = ""; }; 4BACF4A03F73DCFC3D95C21A /* DependencyFixtureB.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = DependencyFixtureB.framework; sourceTree = ""; }; 50B5CD1B9C7CDDBA1A9B530E /* CarthageTestFixture.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = CarthageTestFixture.framework; sourceTree = ""; }; 6177CC6263783487E93F7F4D /* Framework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Framework.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 685E6928BB8552F257AF32A1 /* DependencyFixtureB.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = DependencyFixtureB.framework; sourceTree = ""; }; 7D67F1C1BFBACE101DE7DB51 /* Framework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Framework.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 80192E4B3F765386B9E8CCC5 /* DependencyFixtureB.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = DependencyFixtureB.framework; sourceTree = ""; }; 80FF7051B3E5445E5E0E9770 /* ReactiveCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ReactiveCocoa.framework; sourceTree = ""; }; 8172E4733F98AD213CB42FA9 /* CarthageTestFixture.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = CarthageTestFixture.framework; sourceTree = ""; }; 8A9274BE42A03DC5DA1FAD04 /* Framework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Framework.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9010572AFE5F328D1A4F35E1 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Alamofire.framework; sourceTree = ""; }; 9175CD404DD510116BEC0234 /* DependencyFixtureA.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = DependencyFixtureA.framework; sourceTree = ""; }; 944523E6CAE426EEEFB898A2 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Alamofire.framework; sourceTree = ""; }; 9BFA0DB3F2851A12A91DABFB /* DependencyFixtureA.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = DependencyFixtureA.framework; sourceTree = ""; }; A3F65F497639D85FA73DECA8 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ReactiveSwift.framework; sourceTree = ""; }; B2AA735E34BB5D2B7DA7212B /* CarthageTestFixture.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = CarthageTestFixture.framework; sourceTree = ""; }; B76E17CE3574081D5BF45B44 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Result.framework; sourceTree = ""; }; B9CBE843396A0F73A18F4B11 /* DependencyFixtureA.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = DependencyFixtureA.framework; sourceTree = ""; }; BB178D03E75929F3F5B10C56 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Result.framework; sourceTree = ""; }; C206A943C9E4A6803327024F /* ReactiveCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ReactiveCocoa.framework; sourceTree = ""; }; C2AC45CB275013ADC262D975 /* ReactiveMapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ReactiveMapKit.framework; sourceTree = ""; }; D296BB7355994040E197A1EE /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Result.framework; sourceTree = ""; }; D437562D61F4BB3DE6378823 /* ReactiveCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ReactiveCocoa.framework; sourceTree = ""; }; D8E5AC3513ADA944932071EC /* ReactiveMapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ReactiveMapKit.framework; sourceTree = ""; }; DB9DC024AC7A05E4437051CB /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ReactiveSwift.framework; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 2E6FCCFC594BE9FEB74FA2F0 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( A1AEAAB53EAEDA1C307871FA /* Result.framework in Frameworks */, 9EBFD7ADF57175109DE0EA94 /* Alamofire.framework in Frameworks */, A7732A9A7B252C45427FCE62 /* CarthageTestFixture.framework in Frameworks */, 9B4B21707CB152E15BE7ECBD /* DependencyFixtureA.framework in Frameworks */, 044DBCB804B0066EDF0B69F8 /* DependencyFixtureB.framework in Frameworks */, AEA1B2369A0CD094D0F426F4 /* ReactiveSwift.framework in Frameworks */, 55B624A520B0E35A386B6569 /* ReactiveCocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 5EFF61D0A49AA8EABD72DF44 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 9DF5931DAD58C35B830A0A75 /* Result.framework in Frameworks */, F0E692AFBFDF2CE25EE123BD /* Alamofire.framework in Frameworks */, B5DE9CE83E10AB067A1D113E /* CarthageTestFixture.framework in Frameworks */, 5C7583816F6826B7D7DCD018 /* DependencyFixtureA.framework in Frameworks */, 192B14D32FB49F4BF60C9908 /* DependencyFixtureB.framework in Frameworks */, E471368A1DEC4EF63D70D84C /* ReactiveSwift.framework in Frameworks */, 7881D9B16F8FD97E7ACD27E5 /* ReactiveCocoa.framework in Frameworks */, 53984F64C12E67CDD10C2238 /* ReactiveMapKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 9B861C58E640BD4AD391900C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 21CA04F29CD0DEB0DD27B808 /* Result.framework in Frameworks */, 5BCC106D55292C73C015FAB1 /* Alamofire.framework in Frameworks */, 3674EA49FAD6B59A4548DD6C /* CarthageTestFixture.framework in Frameworks */, D9D64CBA06FDE4DEC421D5D7 /* DependencyFixtureA.framework in Frameworks */, 34840A4B524AECE0CB665CCC /* DependencyFixtureB.framework in Frameworks */, 92E4113033B920B0830492B6 /* ReactiveSwift.framework in Frameworks */, 199F923C0D8B980562D75B2D /* ReactiveCocoa.framework in Frameworks */, 11D327D6D70177DDC0A79581 /* ReactiveMapKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; C2323597C6777A02E1FF671C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 307B5322FC5A220652BA6FE0 /* Result.framework in Frameworks */, 82F5012DAD668BFCCBA5CC6D /* Alamofire.framework in Frameworks */, 90429469A473C6702082B4B7 /* CarthageTestFixture.framework in Frameworks */, 50774BBEADF9BEC85EEB7789 /* DependencyFixtureA.framework in Frameworks */, 58CB7129BEAF736118826A76 /* DependencyFixtureB.framework in Frameworks */, 10B5DCB476B73C344D90E179 /* ReactiveSwift.framework in Frameworks */, A1163E2229C5AC64A8DFF967 /* ReactiveCocoa.framework in Frameworks */, B9CE5164FCCF4CAC768FB1D3 /* ReactiveMapKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 12809A79ACE69F501A5FE815 /* Carthage */ = { isa = PBXGroup; children = ( DBF93518FC96D95A54552713 /* iOS */, 912A7321F662FE41BAAEED67 /* Mac */, D557819B1EE5B42A0A3DD4D1 /* tvOS */, 2935454D05445817952E145D /* watchOS */, ); name = Carthage; path = Carthage/Build; sourceTree = ""; }; 2935454D05445817952E145D /* watchOS */ = { isa = PBXGroup; children = ( 25C6E95FE2D09F3D963EE98A /* Alamofire.framework */, 8172E4733F98AD213CB42FA9 /* CarthageTestFixture.framework */, 9BFA0DB3F2851A12A91DABFB /* DependencyFixtureA.framework */, 1B7FE5849A5B2AAD3E62D3A7 /* DependencyFixtureB.framework */, D437562D61F4BB3DE6378823 /* ReactiveCocoa.framework */, 4A01E2E08924015A0687C0B6 /* ReactiveSwift.framework */, BB178D03E75929F3F5B10C56 /* Result.framework */, ); path = watchOS; sourceTree = ""; }; 293D0FF827366B513839236A = { isa = PBXGroup; children = ( FC1515684236259C50A7747F /* Frameworks */, AC523591AC7BE9275003D2DB /* Products */, ); sourceTree = ""; }; 912A7321F662FE41BAAEED67 /* Mac */ = { isa = PBXGroup; children = ( 9010572AFE5F328D1A4F35E1 /* Alamofire.framework */, B2AA735E34BB5D2B7DA7212B /* CarthageTestFixture.framework */, B9CBE843396A0F73A18F4B11 /* DependencyFixtureA.framework */, 80192E4B3F765386B9E8CCC5 /* DependencyFixtureB.framework */, 3CD62DEFF074E2C4A35C6F30 /* ReactiveCocoa.framework */, C2AC45CB275013ADC262D975 /* ReactiveMapKit.framework */, A3F65F497639D85FA73DECA8 /* ReactiveSwift.framework */, D296BB7355994040E197A1EE /* Result.framework */, ); path = Mac; sourceTree = ""; }; AC523591AC7BE9275003D2DB /* Products */ = { isa = PBXGroup; children = ( 8A9274BE42A03DC5DA1FAD04 /* Framework.framework */, 41FC82ED1C4C3B7B3D7B2FB7 /* Framework.framework */, 7D67F1C1BFBACE101DE7DB51 /* Framework.framework */, 6177CC6263783487E93F7F4D /* Framework.framework */, ); name = Products; sourceTree = ""; }; D557819B1EE5B42A0A3DD4D1 /* tvOS */ = { isa = PBXGroup; children = ( 944523E6CAE426EEEFB898A2 /* Alamofire.framework */, 4778BFC65091F5B2B07A7F86 /* CarthageTestFixture.framework */, 0D57001AD1EEEB31B0EEEBE2 /* DependencyFixtureA.framework */, 4BACF4A03F73DCFC3D95C21A /* DependencyFixtureB.framework */, 80FF7051B3E5445E5E0E9770 /* ReactiveCocoa.framework */, 1E7DD996C029F078EA21CB67 /* ReactiveMapKit.framework */, 3B2B50BF9BD704D4CA45A7A9 /* ReactiveSwift.framework */, B76E17CE3574081D5BF45B44 /* Result.framework */, ); path = tvOS; sourceTree = ""; }; DBF93518FC96D95A54552713 /* iOS */ = { isa = PBXGroup; children = ( 2073C309D451D8C3BDD03C1B /* Alamofire.framework */, 50B5CD1B9C7CDDBA1A9B530E /* CarthageTestFixture.framework */, 9175CD404DD510116BEC0234 /* DependencyFixtureA.framework */, 685E6928BB8552F257AF32A1 /* DependencyFixtureB.framework */, C206A943C9E4A6803327024F /* ReactiveCocoa.framework */, D8E5AC3513ADA944932071EC /* ReactiveMapKit.framework */, DB9DC024AC7A05E4437051CB /* ReactiveSwift.framework */, 0C5AC2545AE4D4F7F44E2E9B /* Result.framework */, ); path = iOS; sourceTree = ""; }; FC1515684236259C50A7747F /* Frameworks */ = { isa = PBXGroup; children = ( 12809A79ACE69F501A5FE815 /* Carthage */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 536ACF18E4603B59207D43CE /* Framework_tvOS */ = { isa = PBXNativeTarget; buildConfigurationList = 658628E35283172E17BFC6A3 /* Build configuration list for PBXNativeTarget "Framework_tvOS" */; buildPhases = ( 9EC3C2991C5C1EE119E39533 /* Sources */, 5EFF61D0A49AA8EABD72DF44 /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Framework_tvOS; packageProductDependencies = ( ); productName = Framework_tvOS; productReference = 7D67F1C1BFBACE101DE7DB51 /* Framework.framework */; productType = "com.apple.product-type.framework"; }; 53A3B531E3947D8A8722745E /* Framework_macOS */ = { isa = PBXNativeTarget; buildConfigurationList = D60A551D881B4B91F4535B78 /* Build configuration list for PBXNativeTarget "Framework_macOS" */; buildPhases = ( D1F422E9C4DD531AA88418C9 /* Sources */, C2323597C6777A02E1FF671C /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Framework_macOS; packageProductDependencies = ( ); productName = Framework_macOS; productReference = 41FC82ED1C4C3B7B3D7B2FB7 /* Framework.framework */; productType = "com.apple.product-type.framework"; }; 71B5187E710718C1A205D4DC /* Framework_watchOS */ = { isa = PBXNativeTarget; buildConfigurationList = 4A8774E3B4F5C9B98E0D0CF9 /* Build configuration list for PBXNativeTarget "Framework_watchOS" */; buildPhases = ( 077D11E42A8E90CAB8A95DF2 /* Sources */, 2E6FCCFC594BE9FEB74FA2F0 /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Framework_watchOS; packageProductDependencies = ( ); productName = Framework_watchOS; productReference = 6177CC6263783487E93F7F4D /* Framework.framework */; productType = "com.apple.product-type.framework"; }; AE3F93DB94E7208F2F1D9A78 /* Framework_iOS */ = { isa = PBXNativeTarget; buildConfigurationList = 50DA67E9A951C40D9536609D /* Build configuration list for PBXNativeTarget "Framework_iOS" */; buildPhases = ( 40A4456A24F99A01E340C032 /* Sources */, 9B861C58E640BD4AD391900C /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Framework_iOS; packageProductDependencies = ( ); productName = Framework_iOS; productReference = 8A9274BE42A03DC5DA1FAD04 /* Framework.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 0FBAE303E3CFC2ABAC876A77 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1430; TargetAttributes = { }; }; buildConfigurationList = D91E14E36EC0B415578456F2 /* Build configuration list for PBXProject "Project" */; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 293D0FF827366B513839236A; minimizedProjectReferenceProxies = 1; preferredProjectObjectVersion = 77; productRefGroup = AC523591AC7BE9275003D2DB /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( AE3F93DB94E7208F2F1D9A78 /* Framework_iOS */, 53A3B531E3947D8A8722745E /* Framework_macOS */, 536ACF18E4603B59207D43CE /* Framework_tvOS */, 71B5187E710718C1A205D4DC /* Framework_watchOS */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ 077D11E42A8E90CAB8A95DF2 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 40A4456A24F99A01E340C032 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 9EC3C2991C5C1EE119E39533 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; D1F422E9C4DD531AA88418C9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 036FCF7C4B48C154279F289D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; }; name = Release; }; 06FEE6A4375FED73FBBB4162 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/iOS", ); INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_NAME = Framework; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; 6081E9515EB8A378BEE3E57A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/watchOS", ); INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; PRODUCT_NAME = Framework; SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 62D4835A6A4EDD4CF8DDCB41 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/tvOS", ); INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_NAME = Framework; SDKROOT = appletvos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 3; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; 6D4575F120C426FEC8956CCB /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/watchOS", ); INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; PRODUCT_NAME = Framework; SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; 86602EB00607C9B20F3534A4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/iOS", ); INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_NAME = Framework; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 999683C4B5329A72D12AD584 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "DEBUG=1", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; B7544571511FFC03F6471D80 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/Mac", ); INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PRODUCT_NAME = Framework; SDKROOT = macosx; SKIP_INSTALL = YES; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; C04D53CA1D822DDDB3DF4B3F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/Mac", ); INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PRODUCT_NAME = Framework; SDKROOT = macosx; SKIP_INSTALL = YES; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; EE917255DA1F2644A2985FE7 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/tvOS", ); INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_NAME = Framework; SDKROOT = appletvos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 3; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 4A8774E3B4F5C9B98E0D0CF9 /* Build configuration list for PBXNativeTarget "Framework_watchOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 6081E9515EB8A378BEE3E57A /* Debug */, 6D4575F120C426FEC8956CCB /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; 50DA67E9A951C40D9536609D /* Build configuration list for PBXNativeTarget "Framework_iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 86602EB00607C9B20F3534A4 /* Debug */, 06FEE6A4375FED73FBBB4162 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; 658628E35283172E17BFC6A3 /* Build configuration list for PBXNativeTarget "Framework_tvOS" */ = { isa = XCConfigurationList; buildConfigurations = ( EE917255DA1F2644A2985FE7 /* Debug */, 62D4835A6A4EDD4CF8DDCB41 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; D60A551D881B4B91F4535B78 /* Build configuration list for PBXNativeTarget "Framework_macOS" */ = { isa = XCConfigurationList; buildConfigurations = ( B7544571511FFC03F6471D80 /* Debug */, C04D53CA1D822DDDB3DF4B3F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; D91E14E36EC0B415578456F2 /* Build configuration list for PBXProject "Project" */ = { isa = XCConfigurationList; buildConfigurations = ( 999683C4B5329A72D12AD584 /* Debug */, 036FCF7C4B48C154279F289D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; /* End XCConfigurationList section */ }; rootObject = 0FBAE303E3CFC2ABAC876A77 /* Project object */; } ================================================ FILE: Tests/Fixtures/CarthageProject/Project.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Tests/Fixtures/CarthageProject/project.yml ================================================ name: Project options: findCarthageFrameworks: true targets: Framework: type: framework platform: [iOS, tvOS, watchOS, macOS] dependencies: - carthage: Result - carthage: Alamofire - carthage: CarthageTestFixture - carthage: ReactiveSwift - carthage: ReactiveCocoa ================================================ FILE: Tests/Fixtures/SPM/FooFeature/Package.swift ================================================ // swift-tools-version: 5.8 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "FooFeature", products: [ .library(name: "FooDomain", targets: [ "FooDomain" ]), .library(name: "FooUI", targets: [ "FooUI" ]) ], targets: [ .target(name: "FooDomain"), .target(name: "FooUI") ] ) ================================================ FILE: Tests/Fixtures/SPM/FooFeature/Sources/FooDomain/FooDomain.swift ================================================ public struct FooDomain {} ================================================ FILE: Tests/Fixtures/SPM/FooFeature/Sources/FooUI/FooUI.swift ================================================ public struct FooUI {} ================================================ FILE: Tests/Fixtures/SPM/SPM/App.xctestplan ================================================ { "configurations" : [ { "id" : "521B6958-2D62-4961-B353-91EF8F252F4B", "name" : "Configuration 1", "options" : { } } ], "defaultOptions" : { "codeCoverage" : false, "targetForVariableExpansion" : { "containerPath" : "container:SPM.xcodeproj", "identifier" : "C99E3C420D63D5219CE57E33", "name" : "App" } }, "testTargets" : [ ], "version" : 1 } ================================================ FILE: Tests/Fixtures/SPM/SPM/AppDelegate.swift ================================================ // // AppDelegate.swift // SPM // // Created by Yonas Kolb on 13/8/19. // Copyright © 2019 BeemIt. All rights reserved. // import UIKit import Codability @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. true } } ================================================ FILE: Tests/Fixtures/SPM/SPM/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "1x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" }, { "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" }, { "idiom" : "ios-marketing", "size" : "1024x1024", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Tests/Fixtures/SPM/SPM/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Tests/Fixtures/SPM/SPM/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 LSRequiresIPhoneOS UIApplicationSceneManifest UIApplicationSupportsMultipleScenes UISceneConfigurations UIWindowSceneSessionRoleApplication UISceneConfigurationName Default Configuration UISceneDelegateClassName $(PRODUCT_MODULE_NAME).SceneDelegate UISceneStoryboardFile Main UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: Tests/Fixtures/SPM/SPM.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 77; objects = { /* Begin PBXAggregateTarget section */ ADD3CE771A0D5E996031A193 /* AggTarget */ = { isa = PBXAggregateTarget; buildConfigurationList = A7ABF1B35D9170092F822790 /* Build configuration list for PBXAggregateTarget "AggTarget" */; buildPhases = ( ); dependencies = ( D287BAAB664D1A024D9DD57E /* PBXTargetDependency */, ); name = AggTarget; packageProductDependencies = ( ); productName = AggTarget; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 23C6626698DE560017A89F2F /* XcodeGen in Frameworks */ = {isa = PBXBuildFile; productRef = 6F7DEA2D82649EDF903FBDBD /* XcodeGen */; }; 2DA7998902987953B119E4CE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F7EFEE613987D1E1258A60 /* AppDelegate.swift */; }; 36CE2E6187D9709BAD9EF807 /* FooDomain in Frameworks */ = {isa = PBXBuildFile; productRef = 8D2DC638BEF7FDF23907E134 /* FooDomain */; }; 3986ED6965842721C46C0452 /* SwiftRoaringDynamic in Frameworks */ = {isa = PBXBuildFile; productRef = DC73B269C8B0C0BF6912842C /* SwiftRoaringDynamic */; }; 4CC663B42B270404EF5FD037 /* libStaticLibrary.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CAB5625F6FEA668410ED5482 /* libStaticLibrary.a */; }; 9AD886A88D3E4A1B5E900687 /* SPMTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7970A2253B14A9B27C307FAC /* SPMTests.swift */; }; 9C4AD0711D706FD3ED0E436D /* StaticLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C17B77601A9D1B7895AB42 /* StaticLibrary.swift */; }; AF8E362713B9D28EA9A5C9FC /* SwiftLocation in Frameworks */ = {isa = PBXBuildFile; productRef = 04F71F974C4771232AF4FEC2 /* SwiftLocation */; }; B89EA0F3859878A1DCF7BAFD /* SwiftRoaringDynamic in Embed Frameworks */ = {isa = PBXBuildFile; productRef = DC73B269C8B0C0BF6912842C /* SwiftRoaringDynamic */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CE46CBA5671B951B546C8673 /* Codability in Frameworks */ = {isa = PBXBuildFile; productRef = 16E6FE01D5BD99F78D4A17E2 /* Codability */; settings = {ATTRIBUTES = (Weak, ); }; }; E368431019ABC696E4FFC0CF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4E22B8BCC18A29EFE1DE3BE4 /* Assets.xcassets */; }; ECC4F5F3B3D1391712A7AFE3 /* FooUI in Frameworks */ = {isa = PBXBuildFile; productRef = 927CB19D94339CC9960E930C /* FooUI */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 29147E1DDAEB1AAC20CB0CF9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F7B09D77DB7447B17DCDA3C4 /* Project object */; proxyType = 1; remoteGlobalIDString = 3F8D94C4EFC431F646AAFB28; remoteInfo = StaticLibrary; }; 4C9144DB97F0499A0D5D508A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F7B09D77DB7447B17DCDA3C4 /* Project object */; proxyType = 1; remoteGlobalIDString = C99E3C420D63D5219CE57E33; remoteInfo = App; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 528B26CADD32E3F39B813BEE /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( B89EA0F3859878A1DCF7BAFD /* SwiftRoaringDynamic in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0613661C0D45064E81E80C37 /* Tests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 097F2DB5622B591E21BC3C73 /* App.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; 26F7EFEE613987D1E1258A60 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 464ACF8D8F2D9F219BCFD3E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 4E22B8BCC18A29EFE1DE3BE4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 61C17B77601A9D1B7895AB42 /* StaticLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLibrary.swift; sourceTree = ""; }; 7970A2253B14A9B27C307FAC /* SPMTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPMTests.swift; sourceTree = ""; }; 979AE1767E2AF6B3B9D7F13D /* FooFeature */ = {isa = PBXFileReference; lastKnownFileType = folder; name = FooFeature; path = FooFeature; sourceTree = SOURCE_ROOT; }; A9601593D0AD02931266A4E5 /* App.xctestplan */ = {isa = PBXFileReference; path = App.xctestplan; sourceTree = ""; }; CAB5625F6FEA668410ED5482 /* libStaticLibrary.a */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = archive.ar; path = libStaticLibrary.a; sourceTree = BUILT_PRODUCTS_DIR; }; ED284AB7C13DCC0A95DAA680 /* XcodeGen */ = {isa = PBXFileReference; lastKnownFileType = folder; name = XcodeGen; path = ../../..; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 796E7DE873F16699BD82FFEA /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( CE46CBA5671B951B546C8673 /* Codability in Frameworks */, 3986ED6965842721C46C0452 /* SwiftRoaringDynamic in Frameworks */, 4CC663B42B270404EF5FD037 /* libStaticLibrary.a in Frameworks */, 23C6626698DE560017A89F2F /* XcodeGen in Frameworks */, AF8E362713B9D28EA9A5C9FC /* SwiftLocation in Frameworks */, 36CE2E6187D9709BAD9EF807 /* FooDomain in Frameworks */, ECC4F5F3B3D1391712A7AFE3 /* FooUI in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 17DD374CC81D710476AFF41C /* SPM */ = { isa = PBXGroup; children = ( A9601593D0AD02931266A4E5 /* App.xctestplan */, 26F7EFEE613987D1E1258A60 /* AppDelegate.swift */, 4E22B8BCC18A29EFE1DE3BE4 /* Assets.xcassets */, 464ACF8D8F2D9F219BCFD3E7 /* Info.plist */, ED284AB7C13DCC0A95DAA680 /* XcodeGen */, ); path = SPM; sourceTree = ""; }; 1FA59BFD192FB5A68D5F587C /* StaticLibrary */ = { isa = PBXGroup; children = ( 61C17B77601A9D1B7895AB42 /* StaticLibrary.swift */, ); path = StaticLibrary; sourceTree = ""; }; 218F6C96DF9E182F526258CF = { isa = PBXGroup; children = ( AD0F3623091EEA8D1EA3DFF8 /* Packages */, 17DD374CC81D710476AFF41C /* SPM */, CF3BD77AEAA56553289456BA /* SPMTests */, 1FA59BFD192FB5A68D5F587C /* StaticLibrary */, 5D68FDDE55EE935627A1B376 /* Products */, ); sourceTree = ""; }; 5D68FDDE55EE935627A1B376 /* Products */ = { isa = PBXGroup; children = ( 097F2DB5622B591E21BC3C73 /* App.app */, CAB5625F6FEA668410ED5482 /* libStaticLibrary.a */, 0613661C0D45064E81E80C37 /* Tests.xctest */, ); name = Products; sourceTree = ""; }; AD0F3623091EEA8D1EA3DFF8 /* Packages */ = { isa = PBXGroup; children = ( 979AE1767E2AF6B3B9D7F13D /* FooFeature */, ); name = Packages; sourceTree = ""; }; CF3BD77AEAA56553289456BA /* SPMTests */ = { isa = PBXGroup; children = ( 7970A2253B14A9B27C307FAC /* SPMTests.swift */, ); path = SPMTests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 339863E54E2D955C00B56802 /* Tests */ = { isa = PBXNativeTarget; buildConfigurationList = 6055721F7910D4EA1E12D7A7 /* Build configuration list for PBXNativeTarget "Tests" */; buildPhases = ( AAE618C99C735F57B19F64DE /* Sources */, ); buildRules = ( ); dependencies = ( 8693351DA9DBE579AC9DD513 /* PBXTargetDependency */, ); name = Tests; packageProductDependencies = ( ); productName = Tests; productReference = 0613661C0D45064E81E80C37 /* Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 3F8D94C4EFC431F646AAFB28 /* StaticLibrary */ = { isa = PBXNativeTarget; buildConfigurationList = 3B861439E878E4B7EE6EE131 /* Build configuration list for PBXNativeTarget "StaticLibrary" */; buildPhases = ( B070E114B1D62BD5A07B61DF /* Sources */, 723C19B61A1AD980BD7C9DF0 /* Copy Swift Objective-C Interface Header */, ); buildRules = ( ); dependencies = ( D85FFB99444DD260A72DDDA7 /* PBXTargetDependency */, DDD17C561AD5065DF4FA4072 /* PBXTargetDependency */, C6360997FFC102F6725099D4 /* PBXTargetDependency */, 00B467060F3DEC027711F9C2 /* PBXTargetDependency */, 7EB17E90A4D8F26FEABEEDF6 /* PBXTargetDependency */, ); name = StaticLibrary; packageProductDependencies = ( AF233B61592982A7F6431FC6 /* Codability */, C816AEB28ED71C3C47F31B98 /* SwiftRoaringDynamic */, 5A36E2FE69703FCAC0BE8064 /* XcodeGen */, 6B8A6E1EA485E607A1D1DCD1 /* FooDomain */, 15DB49096E2978F6BCA8D604 /* FooUI */, ); productName = StaticLibrary; productReference = CAB5625F6FEA668410ED5482 /* libStaticLibrary.a */; productType = "com.apple.product-type.library.static"; }; C99E3C420D63D5219CE57E33 /* App */ = { isa = PBXNativeTarget; buildConfigurationList = 091BBC493EA2F0A446682C48 /* Build configuration list for PBXNativeTarget "App" */; buildPhases = ( 460F52476B5219D2CDA494C2 /* Sources */, F77D37B94534F63D9B461F30 /* Resources */, 796E7DE873F16699BD82FFEA /* Frameworks */, 528B26CADD32E3F39B813BEE /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( 078202CF7B08B66ACF7FEC23 /* PBXTargetDependency */, E157C6348B8AD6A28C706801 /* PBXTargetDependency */, ); name = App; packageProductDependencies = ( 16E6FE01D5BD99F78D4A17E2 /* Codability */, DC73B269C8B0C0BF6912842C /* SwiftRoaringDynamic */, 6F7DEA2D82649EDF903FBDBD /* XcodeGen */, 04F71F974C4771232AF4FEC2 /* SwiftLocation */, 8D2DC638BEF7FDF23907E134 /* FooDomain */, 927CB19D94339CC9960E930C /* FooUI */, ); productName = App; productReference = 097F2DB5622B591E21BC3C73 /* App.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F7B09D77DB7447B17DCDA3C4 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1430; TargetAttributes = { }; }; buildConfigurationList = 425866ADA259DB93FC4AF1E3 /* Build configuration list for PBXProject "SPM" */; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 218F6C96DF9E182F526258CF; minimizedProjectReferenceProxies = 1; packageReferences = ( 5BA91390AE78D2EE15C60091 /* XCRemoteSwiftPackageReference "Codability" */, 348C81C327DB1710B742C370 /* XCRemoteSwiftPackageReference "Prefire" */, 63B845B0C9058076DD19CA85 /* XCRemoteSwiftPackageReference "SwiftLocation" */, E3887F3CB2C069E70D98092F /* XCRemoteSwiftPackageReference "SwiftRoaring" */, 630A8CE9F2BE39704ED9D461 /* XCLocalSwiftPackageReference "FooFeature" */, C6539B364583AE96C18CE377 /* XCLocalSwiftPackageReference "../../.." */, ); preferredProjectObjectVersion = 77; productRefGroup = 5D68FDDE55EE935627A1B376 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( ADD3CE771A0D5E996031A193 /* AggTarget */, C99E3C420D63D5219CE57E33 /* App */, 3F8D94C4EFC431F646AAFB28 /* StaticLibrary */, 339863E54E2D955C00B56802 /* Tests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ F77D37B94534F63D9B461F30 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( E368431019ABC696E4FFC0CF /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 723C19B61A1AD980BD7C9DF0 /* Copy Swift Objective-C Interface Header */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "$(DERIVED_SOURCES_DIR)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)", ); name = "Copy Swift Objective-C Interface Header"; outputPaths = ( "$(BUILT_PRODUCTS_DIR)/include/$(PRODUCT_MODULE_NAME)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "ditto \"${SCRIPT_INPUT_FILE_0}\" \"${SCRIPT_OUTPUT_FILE_0}\"\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 460F52476B5219D2CDA494C2 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2DA7998902987953B119E4CE /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; AAE618C99C735F57B19F64DE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 9AD886A88D3E4A1B5E900687 /* SPMTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; B070E114B1D62BD5A07B61DF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 9C4AD0711D706FD3ED0E436D /* StaticLibrary.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 00B467060F3DEC027711F9C2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; productRef = 6B8A6E1EA485E607A1D1DCD1 /* FooDomain */; }; 078202CF7B08B66ACF7FEC23 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 3F8D94C4EFC431F646AAFB28 /* StaticLibrary */; targetProxy = 29147E1DDAEB1AAC20CB0CF9 /* PBXContainerItemProxy */; }; 7EB17E90A4D8F26FEABEEDF6 /* PBXTargetDependency */ = { isa = PBXTargetDependency; productRef = 15DB49096E2978F6BCA8D604 /* FooUI */; }; 8693351DA9DBE579AC9DD513 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C99E3C420D63D5219CE57E33 /* App */; targetProxy = 4C9144DB97F0499A0D5D508A /* PBXContainerItemProxy */; }; C6360997FFC102F6725099D4 /* PBXTargetDependency */ = { isa = PBXTargetDependency; productRef = 5A36E2FE69703FCAC0BE8064 /* XcodeGen */; }; D287BAAB664D1A024D9DD57E /* PBXTargetDependency */ = { isa = PBXTargetDependency; productRef = 896D1E2063A93D40F04D7864 /* PrefirePlaybookPlugin */; }; D85FFB99444DD260A72DDDA7 /* PBXTargetDependency */ = { isa = PBXTargetDependency; productRef = AF233B61592982A7F6431FC6 /* Codability */; }; DDD17C561AD5065DF4FA4072 /* PBXTargetDependency */ = { isa = PBXTargetDependency; productRef = C816AEB28ED71C3C47F31B98 /* SwiftRoaringDynamic */; }; E157C6348B8AD6A28C706801 /* PBXTargetDependency */ = { isa = PBXTargetDependency; productRef = DC47EF1BFBBD751E3C1C95E3 /* PrefirePlaybookPlugin */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 0C023F1AE037C42683029CE9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { }; name = Debug; }; 0CCC06807E5CD8361D899B7F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 1640ABF22E84A6AB9FFFB0D9 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; 41B31B6C4A1D9194EC6FFF6B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; INFOPLIST_FILE = SPM/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 5E087A904FBC0E623E672507 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { }; name = Release; }; 7A384B9B9CF42FCF9EF02057 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; INFOPLIST_FILE = SPM/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; AC10D9ACB37F0EE5A91CCC03 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/App.app/App"; }; name = Release; }; B4F2839AD4756B475B2005F2 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "DEBUG=1", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; B6C3FE81F43A4D3DA0830740 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/App.app/App"; }; name = Debug; }; BC33B43DF18620E6CCC43E96 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 091BBC493EA2F0A446682C48 /* Build configuration list for PBXNativeTarget "App" */ = { isa = XCConfigurationList; buildConfigurations = ( 41B31B6C4A1D9194EC6FFF6B /* Debug */, 7A384B9B9CF42FCF9EF02057 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; 3B861439E878E4B7EE6EE131 /* Build configuration list for PBXNativeTarget "StaticLibrary" */ = { isa = XCConfigurationList; buildConfigurations = ( 0CCC06807E5CD8361D899B7F /* Debug */, 1640ABF22E84A6AB9FFFB0D9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; 425866ADA259DB93FC4AF1E3 /* Build configuration list for PBXProject "SPM" */ = { isa = XCConfigurationList; buildConfigurations = ( B4F2839AD4756B475B2005F2 /* Debug */, BC33B43DF18620E6CCC43E96 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; 6055721F7910D4EA1E12D7A7 /* Build configuration list for PBXNativeTarget "Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( B6C3FE81F43A4D3DA0830740 /* Debug */, AC10D9ACB37F0EE5A91CCC03 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; A7ABF1B35D9170092F822790 /* Build configuration list for PBXAggregateTarget "AggTarget" */ = { isa = XCConfigurationList; buildConfigurations = ( 0C023F1AE037C42683029CE9 /* Debug */, 5E087A904FBC0E623E672507 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ 630A8CE9F2BE39704ED9D461 /* XCLocalSwiftPackageReference "FooFeature" */ = { isa = XCLocalSwiftPackageReference; relativePath = FooFeature; }; C6539B364583AE96C18CE377 /* XCLocalSwiftPackageReference "../../.." */ = { isa = XCLocalSwiftPackageReference; relativePath = ../../..; }; /* End XCLocalSwiftPackageReference section */ /* Begin XCRemoteSwiftPackageReference section */ 348C81C327DB1710B742C370 /* XCRemoteSwiftPackageReference "Prefire" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/BarredEwe/Prefire"; requirement = { kind = upToNextMajorVersion; minimumVersion = 1.4.1; }; }; 5BA91390AE78D2EE15C60091 /* XCRemoteSwiftPackageReference "Codability" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/yonaskolb/Codability"; requirement = { kind = upToNextMajorVersion; minimumVersion = 0.2.1; }; }; 63B845B0C9058076DD19CA85 /* XCRemoteSwiftPackageReference "SwiftLocation" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/malcommac/SwiftLocation"; requirement = { kind = exactVersion; version = 6.0; }; }; E3887F3CB2C069E70D98092F /* XCRemoteSwiftPackageReference "SwiftRoaring" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/piotte13/SwiftRoaring"; requirement = { kind = upToNextMajorVersion; minimumVersion = 1.0.4; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 04F71F974C4771232AF4FEC2 /* SwiftLocation */ = { isa = XCSwiftPackageProductDependency; package = 63B845B0C9058076DD19CA85 /* XCRemoteSwiftPackageReference "SwiftLocation" */; productName = SwiftLocation; }; 15DB49096E2978F6BCA8D604 /* FooUI */ = { isa = XCSwiftPackageProductDependency; productName = FooUI; }; 16E6FE01D5BD99F78D4A17E2 /* Codability */ = { isa = XCSwiftPackageProductDependency; package = 5BA91390AE78D2EE15C60091 /* XCRemoteSwiftPackageReference "Codability" */; productName = Codability; }; 5A36E2FE69703FCAC0BE8064 /* XcodeGen */ = { isa = XCSwiftPackageProductDependency; productName = XcodeGen; }; 6B8A6E1EA485E607A1D1DCD1 /* FooDomain */ = { isa = XCSwiftPackageProductDependency; productName = FooDomain; }; 6F7DEA2D82649EDF903FBDBD /* XcodeGen */ = { isa = XCSwiftPackageProductDependency; productName = XcodeGen; }; 896D1E2063A93D40F04D7864 /* PrefirePlaybookPlugin */ = { isa = XCSwiftPackageProductDependency; package = 348C81C327DB1710B742C370 /* XCRemoteSwiftPackageReference "Prefire" */; productName = "plugin:PrefirePlaybookPlugin"; }; 8D2DC638BEF7FDF23907E134 /* FooDomain */ = { isa = XCSwiftPackageProductDependency; productName = FooDomain; }; 927CB19D94339CC9960E930C /* FooUI */ = { isa = XCSwiftPackageProductDependency; productName = FooUI; }; AF233B61592982A7F6431FC6 /* Codability */ = { isa = XCSwiftPackageProductDependency; package = 5BA91390AE78D2EE15C60091 /* XCRemoteSwiftPackageReference "Codability" */; productName = Codability; }; C816AEB28ED71C3C47F31B98 /* SwiftRoaringDynamic */ = { isa = XCSwiftPackageProductDependency; package = E3887F3CB2C069E70D98092F /* XCRemoteSwiftPackageReference "SwiftRoaring" */; productName = SwiftRoaringDynamic; }; DC47EF1BFBBD751E3C1C95E3 /* PrefirePlaybookPlugin */ = { isa = XCSwiftPackageProductDependency; package = 348C81C327DB1710B742C370 /* XCRemoteSwiftPackageReference "Prefire" */; productName = "plugin:PrefirePlaybookPlugin"; }; DC73B269C8B0C0BF6912842C /* SwiftRoaringDynamic */ = { isa = XCSwiftPackageProductDependency; package = E3887F3CB2C069E70D98092F /* XCRemoteSwiftPackageReference "SwiftRoaring" */; productName = SwiftRoaringDynamic; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = F7B09D77DB7447B17DCDA3C4 /* Project object */; } ================================================ FILE: Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved ================================================ { "pins" : [ { "identity" : "aexml", "kind" : "remoteSourceControl", "location" : "https://github.com/tadija/AEXML.git", "state" : { "revision" : "38f7d00b23ecd891e1ee656fa6aeebd6ba04ecc3", "version" : "4.6.1" } }, { "identity" : "codability", "kind" : "remoteSourceControl", "location" : "https://github.com/yonaskolb/Codability", "state" : { "revision" : "eb5bac78e0679f521c3f058c1eb9be0dd657dadd", "version" : "0.2.1" } }, { "identity" : "graphviz", "kind" : "remoteSourceControl", "location" : "https://github.com/SwiftDocOrg/GraphViz.git", "state" : { "revision" : "70bebcf4597b9ce33e19816d6bbd4ba9b7bdf038", "version" : "0.2.0" } }, { "identity" : "jsonutilities", "kind" : "remoteSourceControl", "location" : "https://github.com/yonaskolb/JSONUtilities.git", "state" : { "revision" : "128d2ffc22467f69569ef8ff971683e2393191a0", "version" : "4.2.0" } }, { "identity" : "pathkit", "kind" : "remoteSourceControl", "location" : "https://github.com/kylef/PathKit.git", "state" : { "revision" : "3bfd2737b700b9a36565a8c94f4ad2b050a5e574", "version" : "1.0.1" } }, { "identity" : "prefire", "kind" : "remoteSourceControl", "location" : "https://github.com/BarredEwe/Prefire", "state" : { "revision" : "abb8dfa44391b4f47edb4937a4ba124e76270a87", "version" : "1.4.1" } }, { "identity" : "rainbow", "kind" : "remoteSourceControl", "location" : "https://github.com/onevcat/Rainbow.git", "state" : { "revision" : "626c3d4b6b55354b4af3aa309f998fae9b31a3d9", "version" : "3.2.0" } }, { "identity" : "spectre", "kind" : "remoteSourceControl", "location" : "https://github.com/kylef/Spectre.git", "state" : { "revision" : "26cc5e9ae0947092c7139ef7ba612e34646086c7", "version" : "0.10.1" } }, { "identity" : "swiftcli", "kind" : "remoteSourceControl", "location" : "https://github.com/jakeheis/SwiftCLI.git", "state" : { "revision" : "2e949055d9797c1a6bddcda0e58dada16cc8e970", "version" : "6.0.3" } }, { "identity" : "swiftroaring", "kind" : "remoteSourceControl", "location" : "https://github.com/piotte13/SwiftRoaring", "state" : { "revision" : "9104cf3f35e7a38c9fb633084c7adb63a9f27f8b", "version" : "1.0.4" } }, { "identity" : "version", "kind" : "remoteSourceControl", "location" : "https://github.com/mxcl/Version", "state" : { "revision" : "1fe824b80d89201652e7eca7c9252269a1d85e25", "version" : "2.0.1" } }, { "identity" : "xcodeproj", "kind" : "remoteSourceControl", "location" : "https://github.com/tuist/XcodeProj.git", "state" : { "revision" : "6e60fb55271c80f83a186c9b1b4982fd991cfc0a", "version" : "8.13.0" } }, { "identity" : "yams", "kind" : "remoteSourceControl", "location" : "https://github.com/jpsim/Yams.git", "state" : { "revision" : "0d9ee7ea8c4ebd4a489ad7a73d5c6cad55d6fed3", "version" : "5.0.6" } } ], "version" : 2 } ================================================ FILE: Tests/Fixtures/SPM/SPM.xcodeproj/xcshareddata/xcschemes/App.xcscheme ================================================ ================================================ FILE: Tests/Fixtures/SPM/SPMTests/SPMTests.swift ================================================ import XCTest class SPMTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. measure { // Put the code you want to measure the time of here. } } } ================================================ FILE: Tests/Fixtures/SPM/StaticLibrary/StaticLibrary.swift ================================================ import Codability func doThing() { _ = AnyCodable.self } ================================================ FILE: Tests/Fixtures/SPM/project.yml ================================================ name: SPM packages: Codability: url: https://github.com/yonaskolb/Codability majorVersion: 0.2.1 SwiftRoaring: url: https://github.com/piotte13/SwiftRoaring majorVersion: 1.0.4 Prefire: url: https://github.com/BarredEwe/Prefire majorVersion: 1.4.1 SwiftLocation: url: https://github.com/malcommac/SwiftLocation exactVersion: 6.0 XcodeGen: path: ../../.. #XcodeGen itself group: SPM FooFeature: path: FooFeature aggregateTargets: AggTarget: buildToolPlugins: - plugin: PrefirePlaybookPlugin package: Prefire targets: App: type: application platform: iOS sources: [SPM] scheme: testTargets: - package: XcodeGen/XcodeGenKitTests - Tests buildToolPlugins: - plugin: PrefirePlaybookPlugin package: Prefire dependencies: - package: Codability weak: true - package: SwiftRoaring product: SwiftRoaringDynamic embed: true - target: StaticLibrary - package: XcodeGen - package: SwiftLocation - package: FooFeature products: - FooDomain - FooUI Tests: type: bundle.unit-test platform: iOS sources: [SPMTests] dependencies: - target: App StaticLibrary: type: library.static platform: iOS sources: StaticLibrary dependencies: - package: Codability - package: SwiftRoaring product: SwiftRoaringDynamic - package: XcodeGen - package: FooFeature products: - FooDomain - FooUI ================================================ FILE: Tests/Fixtures/TestProject/.gitignore ================================================ Carthage ================================================ FILE: Tests/Fixtures/TestProject/.lldbinit ================================================ ================================================ FILE: Tests/Fixtures/TestProject/AnotherProject/AnotherProject.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 77; objects = { /* Begin PBXFileReference section */ 6023D61BF2C57E6AE09CE7A3 /* BundleX.bundle */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = BundleX.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 60D6679FB526839EAFEA2EEE /* config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = config.xcconfig; sourceTree = ""; }; D6340FC7DEBC81E0127BAFD6 /* ExternalTarget.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ExternalTarget.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F1EFFCA88BFC3A1DD2D89DA7 /* BundleY.bundle */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = BundleY.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXGroup section */ 3B245BCEF731A43880657E0E /* Configs */ = { isa = PBXGroup; children = ( 60D6679FB526839EAFEA2EEE /* config.xcconfig */, ); name = Configs; path = ../Configs; sourceTree = ""; }; 4E8CFA4275C972686621210C = { isa = PBXGroup; children = ( 3B245BCEF731A43880657E0E /* Configs */, 6BB7980FAF18A93459B051A1 /* Products */, ); sourceTree = ""; }; 6BB7980FAF18A93459B051A1 /* Products */ = { isa = PBXGroup; children = ( 6023D61BF2C57E6AE09CE7A3 /* BundleX.bundle */, F1EFFCA88BFC3A1DD2D89DA7 /* BundleY.bundle */, D6340FC7DEBC81E0127BAFD6 /* ExternalTarget.framework */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXLegacyTarget section */ A6D9FB94860C005F0B723B5F /* IncludedLegacy */ = { isa = PBXLegacyTarget; buildConfigurationList = AC68886F4CEE08D3593D0877 /* Build configuration list for PBXLegacyTarget "IncludedLegacy" */; buildPhases = ( 69078D1DA3F942D5BC752081 /* Sources */, ); buildToolPath = /usr/bin/true; buildWorkingDirectory = .; dependencies = ( ); name = IncludedLegacy; packageProductDependencies = ( ); passBuildSettingsInEnvironment = 0; productName = IncludedLegacy; }; /* End PBXLegacyTarget section */ /* Begin PBXNativeTarget section */ 201AC870383B8CD218AD0FAB /* BundleY */ = { isa = PBXNativeTarget; buildConfigurationList = EF9E2AA4073D3B2EC8195688 /* Build configuration list for PBXNativeTarget "BundleY" */; buildPhases = ( ); buildRules = ( ); dependencies = ( ); name = BundleY; packageProductDependencies = ( ); productName = BundleY; productReference = F1EFFCA88BFC3A1DD2D89DA7 /* BundleY.bundle */; productType = "com.apple.product-type.bundle"; }; 63A2D4898D974A06E85B07F8 /* BundleX */ = { isa = PBXNativeTarget; buildConfigurationList = 32C09717E388BCD9DB9E513C /* Build configuration list for PBXNativeTarget "BundleX" */; buildPhases = ( ); buildRules = ( ); dependencies = ( ); name = BundleX; packageProductDependencies = ( ); productName = BundleX; productReference = 6023D61BF2C57E6AE09CE7A3 /* BundleX.bundle */; productType = "com.apple.product-type.bundle"; }; E76A5F5E363E470416D3B487 /* ExternalTarget */ = { isa = PBXNativeTarget; buildConfigurationList = B5049D72EC10A5C87F29B6B1 /* Build configuration list for PBXNativeTarget "ExternalTarget" */; buildPhases = ( F08051CAC5E72EEEB8FB447B /* Sources */, ); buildRules = ( ); dependencies = ( ); name = ExternalTarget; packageProductDependencies = ( ); productName = ExternalTarget; productReference = D6340FC7DEBC81E0127BAFD6 /* ExternalTarget.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 1B166EB49192B73A9DD8E108 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1430; TargetAttributes = { }; }; buildConfigurationList = 3DFC1105373EDB6483D4BC5D /* Build configuration list for PBXProject "AnotherProject" */; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = 4E8CFA4275C972686621210C; minimizedProjectReferenceProxies = 1; preferredProjectObjectVersion = 77; productRefGroup = 6BB7980FAF18A93459B051A1 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 63A2D4898D974A06E85B07F8 /* BundleX */, 201AC870383B8CD218AD0FAB /* BundleY */, E76A5F5E363E470416D3B487 /* ExternalTarget */, A6D9FB94860C005F0B723B5F /* IncludedLegacy */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ 69078D1DA3F942D5BC752081 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; F08051CAC5E72EEEB8FB447B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 011619A463B654F60012FC9E /* Test Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; BUNDLE_ID_SUFFIX = .test; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; }; name = "Test Release"; }; 03418826FAC3BC1FAB207D05 /* Production Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; }; name = "Production Release"; }; 039F4FBE2C50C67BCD6BD67B /* Staging Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; BUNDLE_ID_SUFFIX = .staging; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; }; name = "Staging Release"; }; 1B1B4A623B8E9937627EF22C /* Test Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GENERATE_INFOPLIST_FILE = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.project.external; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = "Test Debug"; }; 1CE986A8B593E707AB71BDBA /* Production Release */ = { isa = XCBuildConfiguration; buildSettings = { GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Production Release"; }; 270E1D32776D2D196D435FDA /* Staging Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; BUNDLE_ID_SUFFIX = .staging; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "DEBUG=1", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = "Staging Debug"; }; 2D7C07F1D50007A04EF6C0EE /* Staging Debug */ = { isa = XCBuildConfiguration; buildSettings = { LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Staging Debug"; }; 30E2E954A636A240B1CE5C15 /* Staging Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GENERATE_INFOPLIST_FILE = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.project.external; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = "Staging Release"; }; 3B4B85DEBC49A5BEF2196886 /* Staging Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GENERATE_INFOPLIST_FILE = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.project.external; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = "Staging Debug"; }; 42D8986753AED7AA1F3DB83D /* Production Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GENERATE_INFOPLIST_FILE = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.project.external; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = "Production Debug"; }; 49FA7F235A6CA1F941192151 /* Staging Release */ = { isa = XCBuildConfiguration; buildSettings = { GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Staging Release"; }; 4D621C4C28C8614EA5D6ADC2 /* Production Debug */ = { isa = XCBuildConfiguration; buildSettings = { GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Production Debug"; }; 5280A75613CFD83BA7EE6AD4 /* Test Release */ = { isa = XCBuildConfiguration; buildSettings = { LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Test Release"; }; 56ADF89C9058B2C25F6C80CE /* Staging Debug */ = { isa = XCBuildConfiguration; buildSettings = { GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Staging Debug"; }; 5790F0FBCBF55A0DF258AD6A /* Production Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "DEBUG=1", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = "Production Debug"; }; 58F418B6745A09C6479FDD6E /* Staging Release */ = { isa = XCBuildConfiguration; buildSettings = { GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Staging Release"; }; 5F14CE04E33ACD729A0EE6B6 /* Test Debug */ = { isa = XCBuildConfiguration; buildSettings = { GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Test Debug"; }; 6081D1A42688EBF6CF4B2579 /* Production Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GENERATE_INFOPLIST_FILE = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.project.external; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = "Production Release"; }; 683A510C4120CF5D216E1667 /* Test Debug */ = { isa = XCBuildConfiguration; buildSettings = { LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Test Debug"; }; 6C48009F842BEC2467546ADF /* Production Debug */ = { isa = XCBuildConfiguration; buildSettings = { LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Production Debug"; }; 7919FDD28F9A535EA26FB151 /* Test Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GENERATE_INFOPLIST_FILE = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.project.external; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = "Test Release"; }; 816E80EA88CB645CE988138C /* Staging Debug */ = { isa = XCBuildConfiguration; buildSettings = { GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Staging Debug"; }; 9BD6CAD5463121A1C3FED138 /* Production Release */ = { isa = XCBuildConfiguration; buildSettings = { GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Production Release"; }; C3231A91F004B1D0018146DB /* Test Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 60D6679FB526839EAFEA2EEE /* config.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; BUNDLE_ID_SUFFIX = .test; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "DEBUG=1", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = "Test Debug"; }; C7A4FCD4E277AD34B36E5185 /* Test Release */ = { isa = XCBuildConfiguration; buildSettings = { GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Test Release"; }; CB49A0C6C6CF7FC894E453BE /* Staging Release */ = { isa = XCBuildConfiguration; buildSettings = { LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Staging Release"; }; E1A76975608AFAA966038B93 /* Production Release */ = { isa = XCBuildConfiguration; buildSettings = { LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Production Release"; }; E7907C46C6282D78E009083B /* Test Debug */ = { isa = XCBuildConfiguration; buildSettings = { GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Test Debug"; }; FB66976FF75B2B0B255D5AA4 /* Test Release */ = { isa = XCBuildConfiguration; buildSettings = { GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Test Release"; }; FD9323224BE5A91248B7BEF2 /* Production Debug */ = { isa = XCBuildConfiguration; buildSettings = { GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Production Debug"; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 32C09717E388BCD9DB9E513C /* Build configuration list for PBXNativeTarget "BundleX" */ = { isa = XCConfigurationList; buildConfigurations = ( 4D621C4C28C8614EA5D6ADC2 /* Production Debug */, 9BD6CAD5463121A1C3FED138 /* Production Release */, 56ADF89C9058B2C25F6C80CE /* Staging Debug */, 49FA7F235A6CA1F941192151 /* Staging Release */, 5F14CE04E33ACD729A0EE6B6 /* Test Debug */, C7A4FCD4E277AD34B36E5185 /* Test Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = "Production Debug"; }; 3DFC1105373EDB6483D4BC5D /* Build configuration list for PBXProject "AnotherProject" */ = { isa = XCConfigurationList; buildConfigurations = ( 5790F0FBCBF55A0DF258AD6A /* Production Debug */, 03418826FAC3BC1FAB207D05 /* Production Release */, 270E1D32776D2D196D435FDA /* Staging Debug */, 039F4FBE2C50C67BCD6BD67B /* Staging Release */, C3231A91F004B1D0018146DB /* Test Debug */, 011619A463B654F60012FC9E /* Test Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = "Production Debug"; }; AC68886F4CEE08D3593D0877 /* Build configuration list for PBXLegacyTarget "IncludedLegacy" */ = { isa = XCConfigurationList; buildConfigurations = ( 6C48009F842BEC2467546ADF /* Production Debug */, E1A76975608AFAA966038B93 /* Production Release */, 2D7C07F1D50007A04EF6C0EE /* Staging Debug */, CB49A0C6C6CF7FC894E453BE /* Staging Release */, 683A510C4120CF5D216E1667 /* Test Debug */, 5280A75613CFD83BA7EE6AD4 /* Test Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = "Production Debug"; }; B5049D72EC10A5C87F29B6B1 /* Build configuration list for PBXNativeTarget "ExternalTarget" */ = { isa = XCConfigurationList; buildConfigurations = ( 42D8986753AED7AA1F3DB83D /* Production Debug */, 6081D1A42688EBF6CF4B2579 /* Production Release */, 3B4B85DEBC49A5BEF2196886 /* Staging Debug */, 30E2E954A636A240B1CE5C15 /* Staging Release */, 1B1B4A623B8E9937627EF22C /* Test Debug */, 7919FDD28F9A535EA26FB151 /* Test Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = "Production Debug"; }; EF9E2AA4073D3B2EC8195688 /* Build configuration list for PBXNativeTarget "BundleY" */ = { isa = XCConfigurationList; buildConfigurations = ( FD9323224BE5A91248B7BEF2 /* Production Debug */, 1CE986A8B593E707AB71BDBA /* Production Release */, 816E80EA88CB645CE988138C /* Staging Debug */, 58F418B6745A09C6479FDD6E /* Staging Release */, E7907C46C6282D78E009083B /* Test Debug */, FB66976FF75B2B0B255D5AA4 /* Test Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = "Production Debug"; }; /* End XCConfigurationList section */ }; rootObject = 1B166EB49192B73A9DD8E108 /* Project object */; } ================================================ FILE: Tests/Fixtures/TestProject/AnotherProject/AnotherProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Tests/Fixtures/TestProject/AnotherProject/AnotherProject.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Tests/Fixtures/TestProject/AnotherProject/project.yml ================================================ name: AnotherProject include: [../environments.yml] options: useBaseInternationalization: false configFiles: Test Debug: ../Configs/config.xcconfig targets: BundleX: type: bundle platform: iOS settings: GENERATE_INFOPLIST_FILE: YES BundleY: type: bundle platform: iOS settings: GENERATE_INFOPLIST_FILE: YES ExternalTarget: type: framework platform: iOS settings: GENERATE_INFOPLIST_FILE: YES PRODUCT_BUNDLE_IDENTIFIER: com.project.external IncludedLegacy: type: "" platform: iOS legacy: toolPath: /usr/bin/true workingDirectory: . ================================================ FILE: Tests/Fixtures/TestProject/App_Clip/AppDelegate.swift ================================================ import Framework import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. _ = FrameworkStruct() return true } } ================================================ FILE: Tests/Fixtures/TestProject/App_Clip/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "1x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" }, { "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" }, { "idiom" : "ios-marketing", "size" : "1024x1024", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Tests/Fixtures/TestProject/App_Clip/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_Clip/Base.lproj/Main.storyboard ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_Clip/Clip.entitlements ================================================ com.apple.developer.parent-application-identifiers $(AppIdentifierPrefix)com.project.appwithclip com.apple.security.application-groups group.com.app ================================================ FILE: Tests/Fixtures/TestProject/App_Clip/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 42.1 CFBundleVersion 2 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: Tests/Fixtures/TestProject/App_Clip/ViewController.swift ================================================ import Contacts import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() _ = CNContact() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: Tests/Fixtures/TestProject/App_Clip_Tests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: Tests/Fixtures/TestProject/App_Clip_Tests/TestProjectTests.swift ================================================ import XCTest class TestProjectTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. measure { // Put the code you want to measure the time of here. } } } ================================================ FILE: Tests/Fixtures/TestProject/App_Clip_UITests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: Tests/Fixtures/TestProject/App_Clip_UITests/TestProjectUITests.swift ================================================ import XCTest class TestProjectUITests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. measure { // Put the code you want to measure the time of here. } } } ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/App.entitlements ================================================ com.apple.security.application-groups group.com.app ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/AppDelegate.swift ================================================ import Framework import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // file from a framework _ = FrameworkStruct() // Standalone files added to project by path-to-file. _ = standaloneHello() // file in a synced folder _ = SyncedStruct() return true } } ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/App_iOS.xctestplan ================================================ { "configurations" : [ { "id" : "AC4AE1EF-F65C-4037-8994-D607D2E5841E", "name" : "Configuration 1", "options" : { } } ], "defaultOptions" : { "commandLineArgumentEntries" : [ { "argument" : "MyDisabledArgument", "enabled" : false }, { "argument" : "MyEnabledArgument" } ], "mainThreadCheckerEnabled" : false, "targetForVariableExpansion" : { "containerPath" : "container:Project.xcodeproj", "identifier" : "0867B0DACEF28C11442DE8F7", "name" : "App_iOS" } }, "testTargets" : [ { "target" : { "containerPath" : "container:Project.xcodeproj", "identifier" : "DC2F16BAA6E13B44AB62F888", "name" : "App_iOS_Tests" } }, { "target" : { "containerPath" : "container:Project.xcodeproj", "identifier" : "F674B2CFC4738EEC49BAD0DA", "name" : "App_iOS_UITests" } } ], "version" : 1 } ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "1x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" }, { "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" }, { "idiom" : "ios-marketing", "size" : "1024x1024", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Base.lproj/Localizable.strings ================================================ /* Localizable.strings Project Created by ryohey on 2017/11/03. */ ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Base.lproj/Localizable.stringsdict ================================================ StringKey NSStringLocalizedFormatKey %#@VARIABLE@ Variable NSStringFormatSpecTypeKey NSStringPluralRuleType NSStringFormatValueTypeKey zero one two few many other ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Base.lproj/LocalizedStoryboard.storyboard ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Base.lproj/Main.storyboard ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Base.lproj/excluded-file ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Configuration.storekit ================================================ { "products" : [ { "displayPrice" : "0.00", "familyShareable" : false, "internalID" : "6D7919A3", "localizations" : [ { "description" : "", "displayName" : "", "locale" : "en_US" } ], "productID" : "com.xcodegen.0", "referenceName" : null, "type" : "Consumable" } ], "settings" : { }, "subscriptionGroups" : [ ], "version" : { "major" : 1, "minor" : 0 } } ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Documentation.docc/Documentation.md ================================================ # ``App_Clip`` Test ## Overview Test ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/FolderWithDot2.0/SwiftFileInDotPath.swift ================================================ import Foundation extension String { func printHelloWorld() { print("Hello World!") } } ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.2 CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Model.xcdatamodeld/.xccurrentversion ================================================ _XCCurrentVersionName Model 2.xcdatamodel ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Model.xcdatamodeld/Model 2.xcdatamodel/contents ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Model.xcdatamodeld/Model 3.xcdatamodel/contents ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Model.xcdatamodeld/Model.xcdatamodel/contents ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Model.xcmappingmodel/xcmapping.xml ================================================ 134481920 38F33A39-5295-484D-9718-6E727C63501B 103 NSPersistenceFrameworkVersion 977 NSStoreModelVersionHashes XDDevAttributeMapping 0plcXXRN7XHKl5CcF+fwriFmUpON3ZtcI/AfK748aWc= XDDevEntityMapping qeN1Ym3TkWN1G6dU9RfX6Kd2ccEvcDVWHpd3LpLgboI= XDDevMappingModel EqtMzvRnVZWkXwBHu4VeVGy8UyoOe+bi67KC79kphlQ= XDDevPropertyMapping XN33V44TTGY4JETlMoOB5yyTKxB+u4slvDIinv0rtGA= XDDevRelationshipMapping akYY9LhehVA/mCb4ATLWuI9XGLcjpm14wWL1oEBtIcs= NSStoreModelVersionHashesDigest +Hmc2uYZK6og+Pvx5GUJ7oW75UG4V/ksQanTjfTKUnxyGWJRMtB5tIRgVwGsrd7lz/QR57++wbvWsr6nxwyS0A== NSStoreModelVersionHashesVersion 3 NSStoreModelVersionIdentifiers App_iOS/Model.xcdatamodeld/Model.xcdatamodel YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0 cxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxA2AAsADAAbADcAOAA5AEEAQgBVAFYAVwBdAF4AagCAAIEAggCDAIQAhQCGAIcAiACJAKIApQCsALIAwQDQANMA4gDxAPQAVAEEARMBFwEbASoBMAExATkBSAFRAVUBXgFiAWYBagFyAXUBeQF6VSRudWxs1wANAA4ADwAQABEAEgATABQAFQAWABcAGAAXABpfEA9feGRfcm9vdFBhY2thZ2VWJGNsYXNzXF94ZF9jb21tZW50c18QEF94ZF9tb2RlbE1hbmFnZXJfEBVfY29uZmlndXJhdGlvbnNCeU5hbWVdX3hkX21vZGVsTmFtZV8QF19tb2RlbFZlcnNpb25JZGVudGlmaWVygAKANYAygACAM4AAgDTeABwAHQAeAB8AIAAhACIADgAjACQAJQAmACcAKAApACoAKwAJACkAFwAvADAAMQAyADMAKQApABdfEBxYREJ1Y2tldEZvckNsYXNzZXN3YXNFbmNvZGVkXxAaWERCdWNrZXRGb3JQYWNrYWdlc3N0b3JhZ2VfEBxYREJ1Y2tldEZvckludGVyZmFjZXNzdG9yYWdlXxAPX3hkX293bmluZ01vZGVsXxAdWERCdWNrZXRGb3JQYWNrYWdlc3dhc0VuY29kZWRWX293bmVyXxAbWERCdWNrZXRGb3JEYXRhVHlwZXNzdG9yYWdlW192aXNpYmlsaXR5XxAZWERCdWNrZXRGb3JDbGFzc2Vzc3RvcmFnZVVfbmFtZV8QH1hEQnVja2V0Rm9ySW50ZXJmYWNlc3dhc0VuY29kZWRfEB5YREJ1Y2tldEZvckRhdGFUeXBlc3dhc0VuY29kZWRfEBBfdW5pcXVlRWxlbWVudElEgASAMIAugAGABIAAgC+AMRAAgAWAA4AEgASAAFBTWUVT0wA6ADsADgA8AD4AQFdOUy5rZXlzWk5TLm9iamVjdHOhAD2ABqEAP4AHgCVWRW50aXR53ABDAEQARQBGACEADgBHACMASAAlACgASQBKAEsAKQApABQATwBQADEASgA9AFMAVF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZV8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZFtfaXNBYnN0cmFjdIAJgCyABIAEgAKALYAKgAmABoAICBIoNcHJV29yZGVyZWTTADoAOwAOAFgAWgBAoQBZgAuhAFuADIAlXlhEX1BTdGVyZW90eXBl2QAhACUAXwAOACgAYAAjAEkAYQA/AFkASgBlABcAKQAxAFQAaV8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAHgAuACYArgACABAiADdMAOgA7AA4AawB1AECpAGwAbQBuAG8AcABxAHIAcwB0gA6AD4AQgBGAEoATgBSAFYAWqQB2AHcAeAB5AHoAewB8AH0AfoAXgBuAHIAegB+AIYAjgCaAKoAlXxATWERQTUNvbXBvdW5kSW5kZXhlc18QEFhEX1BTS19lbGVtZW50SURfEBlYRFBNVW5pcXVlbmVzc0NvbnN0cmFpbnRzXxAaWERfUFNLX3ZlcnNpb25IYXNoTW9kaWZpZXJfEBlYRF9QU0tfZmV0Y2hSZXF1ZXN0c0FycmF5XxARWERfUFNLX2lzQWJzdHJhY3RfEA9YRF9QU0tfdXNlckluZm9fEBNYRF9QU0tfY2xhc3NNYXBwaW5nXxAWWERfUFNLX2VudGl0eUNsYXNzTmFtZd8QDwCKAIsAjAAhAI0AjgCPACMAkAAOACUAkQCSACgAkwAXAJUAFwBbAFQAVABUADEAVACcAGwAVABUABcAVFVfdHlwZVhfZGVmYXVsdFxfYXNzb2NpYXRpb25bX2lzUmVhZE9ubHlZX2lzU3RhdGljWV9pc1VuaXF1ZVpfaXNEZXJpdmVkWl9pc09yZGVyZWRcX2lzQ29tcG9zaXRlV19pc0xlYWaAAIAYgACADAgICAiAGoAOCAiAAAjSADsADgCjAKSggBnSAKYApwCoAKlaJGNsYXNzbmFtZVgkY2xhc3Nlc15OU011dGFibGVBcnJheaMAqACqAKtXTlNBcnJheVhOU09iamVjdNIApgCnAK0Arl8QEFhEVU1MUHJvcGVydHlJbXCkAK8AsACxAKtfEBBYRFVNTFByb3BlcnR5SW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AigCLAIwAIQCNAI4AjwAjAJAADgAlAJEAkgAoAJMAFwAXABcAWwBUAFQAVAAxAFQAnABtAFQAVAAXAFSAAIAAgACADAgICAiAGoAPCAiAAAjfEA8AigCLAIwAIQCNAI4AjwAjAJAADgAlAJEAkgAoAJMAFwDDABcAWwBUAFQAVAAxAFQAnABuAFQAVAAXAFSAAIAdgACADAgICAiAGoAQCAiAAAjSADsADgDRAKSggBnfEA8AigCLAIwAIQCNAI4AjwAjAJAADgAlAJEAkgAoAJMAFwAXABcAWwBUAFQAVAAxAFQAnABvAFQAVAAXAFSAAIAAgACADAgICAiAGoARCAiAAAjfEA8AigCLAIwAIQCNAI4AjwAjAJAADgAlAJEAkgAoAJMAFwDkABcAWwBUAFQAVAAxAFQAnABwAFQAVAAXAFSAAIAggACADAgICAiAGoASCAiAAAjSADsADgDyAKSggBnfEA8AigCLAIwAIQCNAI4AjwAjAJAADgAlAJEAkgAoAJMAFwD2ABcAWwBUAFQAVAAxAFQAnABxAFQAVAAXAFSAAIAigACADAgICAiAGoATCAiAAAgI3xAPAIoAiwCMACEAjQCOAI8AIwCQAA4AJQCRAJIAKACTABcBBgAXAFsAVABUAFQAMQBUAJwAcgBUAFQAFwBUgACAJIAAgAwICAgIgBqAFAgIgAAI0wA6ADsADgEUARUAQKCggCXSAKYApwEYARlfEBNOU011dGFibGVEaWN0aW9uYXJ5owEYARoAq1xOU0RpY3Rpb25hcnnfEA8AigCLAIwAIQCNAI4AjwAjAJAADgAlAJEAkgAoAJMAFwEdABcAWwBUAFQAVAAxAFQAnABzAFQAVAAXAFSAAIAngACADAgICAiAGoAVCAiAAAjWACUADgAoAEkAIQAjASsBLAAXAFQAFwAxgCiAKYAACIAAXxAUWERHZW5lcmljUmVjb3JkQ2xhc3PSAKYApwEyATNdWERVTUxDbGFzc0ltcKYBNAE1ATYBNwE4AKtdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AigCLAIwAIQCNAI4AjwAjAJAADgAlAJEAkgAoAJMAFwA9ABcAWwBUAFQAVAAxAFQAnAB0AFQAVAAXAFSAAIAGgACADAgICAiAGoAWCAiAAAjSAKYApwFJAUpfEBJYRFVNTFN0ZXJlb3R5cGVJbXCnAUsBTAFNAU4BTwFQAKtfEBJYRFVNTFN0ZXJlb3R5cGVJbXBdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDTADoAOwAOAVIBUwBAoKCAJdIApgCnAVYBV1pYRFBNRW50aXR5pwFYAVkBWgFbAVwBXQCrWlhEUE1FbnRpdHldWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDTADoAOwAOAV8BYABAoKCAJdMAOgA7AA4BYwFkAECgoIAl0wA6ADsADgFnAWgAQKCggCXSAKYApwFrAWxeWERNb2RlbFBhY2thZ2WmAW0BbgFvAXABcQCrXlhETW9kZWxQYWNrYWdlXxAPWERVTUxQYWNrYWdlSW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcNIAOwAOAXMApKCAGdMAOgA7AA4BdgF3AECgoIAlUNIApgCnAXsBfFlYRFBNTW9kZWyjAXsBfQCrV1hETW9kZWwACAAZACIALAAxADoAPwBRAFYAWwBdAMwA0gDvAQEBCAEVASgBQAFOAWgBagFsAW4BcAFyAXQBdgGvAc4B6wIKAhwCPAJDAmECbQKJAo8CsQLSAuUC5wLpAusC7QLvAvEC8wL1AvcC+QL7Av0C/wMBAwIDBgMTAxsDJgMpAysDLgMwAzIDOQNqA44DsgPVA/wEHAQ8BEgESgRMBE4EUARSBFQEVgRYBFoEXARdBGIEagR3BHoEfAR/BIEEgwSSBLcE2wUCBSYFKAUqBSwFLgUwBTIFMwU1BUIFVQVXBVkFWwVdBV8FYQVjBWUFZwV6BXwFfgWABYIFhAWGBYgFigWMBY4FpAW3BdMF8AYMBiAGMgZIBmEGoAamBq8GvAbIBtIG3AbnBvIG/wcHBwkHCwcNBw8HEAcRBxIHEwcVBxcHGAcZBxsHHAclByYHKAcxBzwHRQdUB1sHYwdsB3UHiAeRB6QHuwfNCAwIDggQCBIIFAgVCBYIFwgYCBoIHAgdCB4IIAghCGAIYghkCGYIaAhpCGoIawhsCG4IcAhxCHIIdAh1CH4IfwiBCMAIwgjECMYIyAjJCMoIywjMCM4I0AjRCNII1AjVCRQJFgkYCRoJHAkdCR4JHwkgCSIJJAklCSYJKAkpCTIJMwk1CXQJdgl4CXoJfAl9CX4JfwmACYIJhAmFCYYJiAmJCYoJyQnLCc0JzwnRCdIJ0wnUCdUJ1wnZCdoJ2wndCd4J6wnsCe0J7wn4Cg4KFQoiCmEKYwplCmcKaQpqCmsKbAptCm8KcQpyCnMKdQp2Co8KkQqTCpUKlgqYCq8KuArGCtMK4Qr2CwoLIQszC3ILdAt2C3gLegt7C3wLfQt+C4ALgguDC4QLhguHC5ALpQu0C8kL1wvsDAAMFwwpDDYMNww4DDoMQwxODF0MaAx2DIsMnwy2DMgM1QzWDNcM2QzmDOcM6AzqDPcM+Az5DPsNBA0TDSANLw1BDVUNbA1+DYcNiA2KDZcNmA2ZDZsNnA2lDa8NtgAAAAAAAAICAAAAAAAAAX4AAAAAAAAAAAAAAAAAAA2+ App_iOS/Model.xcdatamodeld/Model 2.xcdatamodel YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0 cxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxA2AAsADAAbADcAOAA5AEEAQgBVAFYAVwBdAF4AagCAAIEAggCDAIQAhQCGAIcAiACJAKIApQCsALIAwQDQANMA4gDxAPQAVAEEARMBFwEbASoBMAExATkBSAFRAVUBXgFiAWYBagFyAXUBeQF6VSRudWxs1wANAA4ADwAQABEAEgATABQAFQAWABcAGAAXABpfEA9feGRfcm9vdFBhY2thZ2VWJGNsYXNzXF94ZF9jb21tZW50c18QEF94ZF9tb2RlbE1hbmFnZXJfEBVfY29uZmlndXJhdGlvbnNCeU5hbWVdX3hkX21vZGVsTmFtZV8QF19tb2RlbFZlcnNpb25JZGVudGlmaWVygAKANYAygACAM4AAgDTeABwAHQAeAB8AIAAhACIADgAjACQAJQAmACcAKAApACoAKwAJACkAFwAvADAAMQAyADMAKQApABdfEBxYREJ1Y2tldEZvckNsYXNzZXN3YXNFbmNvZGVkXxAaWERCdWNrZXRGb3JQYWNrYWdlc3N0b3JhZ2VfEBxYREJ1Y2tldEZvckludGVyZmFjZXNzdG9yYWdlXxAPX3hkX293bmluZ01vZGVsXxAdWERCdWNrZXRGb3JQYWNrYWdlc3dhc0VuY29kZWRWX293bmVyXxAbWERCdWNrZXRGb3JEYXRhVHlwZXNzdG9yYWdlW192aXNpYmlsaXR5XxAZWERCdWNrZXRGb3JDbGFzc2Vzc3RvcmFnZVVfbmFtZV8QH1hEQnVja2V0Rm9ySW50ZXJmYWNlc3dhc0VuY29kZWRfEB5YREJ1Y2tldEZvckRhdGFUeXBlc3dhc0VuY29kZWRfEBBfdW5pcXVlRWxlbWVudElEgASAMIAugAGABIAAgC+AMRAAgAWAA4AEgASAAFBTWUVT0wA6ADsADgA8AD4AQFdOUy5rZXlzWk5TLm9iamVjdHOhAD2ABqEAP4AHgCVWRW50aXR53ABDAEQARQBGACEADgBHACMASAAlACgASQBKAEsAKQApABQATwBQADEASgA9AFMAVF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZV8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZFtfaXNBYnN0cmFjdIAJgCyABIAEgAKALYAKgAmABoAICBL7Tr8NV29yZGVyZWTTADoAOwAOAFgAWgBAoQBZgAuhAFuADIAlXlhEX1BTdGVyZW90eXBl2QAhACUAXwAOACgAYAAjAEkAYQA/AFkASgBlABcAKQAxAFQAaV8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAHgAuACYArgACABAiADdMAOgA7AA4AawB1AECpAGwAbQBuAG8AcABxAHIAcwB0gA6AD4AQgBGAEoATgBSAFYAWqQB2AHcAeAB5AHoAewB8AH0AfoAXgBuAHIAegB+AIYAjgCaAKoAlXxATWERQTUNvbXBvdW5kSW5kZXhlc18QEFhEX1BTS19lbGVtZW50SURfEBlYRFBNVW5pcXVlbmVzc0NvbnN0cmFpbnRzXxAaWERfUFNLX3ZlcnNpb25IYXNoTW9kaWZpZXJfEBlYRF9QU0tfZmV0Y2hSZXF1ZXN0c0FycmF5XxARWERfUFNLX2lzQWJzdHJhY3RfEA9YRF9QU0tfdXNlckluZm9fEBNYRF9QU0tfY2xhc3NNYXBwaW5nXxAWWERfUFNLX2VudGl0eUNsYXNzTmFtZd8QDwCKAIsAjAAhAI0AjgCPACMAkAAOACUAkQCSACgAkwAXAJUAFwBbAFQAVABUADEAVACcAGwAVABUABcAVFVfdHlwZVhfZGVmYXVsdFxfYXNzb2NpYXRpb25bX2lzUmVhZE9ubHlZX2lzU3RhdGljWV9pc1VuaXF1ZVpfaXNEZXJpdmVkWl9pc09yZGVyZWRcX2lzQ29tcG9zaXRlV19pc0xlYWaAAIAYgACADAgICAiAGoAOCAiAAAjSADsADgCjAKSggBnSAKYApwCoAKlaJGNsYXNzbmFtZVgkY2xhc3Nlc15OU011dGFibGVBcnJheaMAqACqAKtXTlNBcnJheVhOU09iamVjdNIApgCnAK0Arl8QEFhEVU1MUHJvcGVydHlJbXCkAK8AsACxAKtfEBBYRFVNTFByb3BlcnR5SW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AigCLAIwAIQCNAI4AjwAjAJAADgAlAJEAkgAoAJMAFwAXABcAWwBUAFQAVAAxAFQAnABtAFQAVAAXAFSAAIAAgACADAgICAiAGoAPCAiAAAjfEA8AigCLAIwAIQCNAI4AjwAjAJAADgAlAJEAkgAoAJMAFwDDABcAWwBUAFQAVAAxAFQAnABuAFQAVAAXAFSAAIAdgACADAgICAiAGoAQCAiAAAjSADsADgDRAKSggBnfEA8AigCLAIwAIQCNAI4AjwAjAJAADgAlAJEAkgAoAJMAFwAXABcAWwBUAFQAVAAxAFQAnABvAFQAVAAXAFSAAIAAgACADAgICAiAGoARCAiAAAjfEA8AigCLAIwAIQCNAI4AjwAjAJAADgAlAJEAkgAoAJMAFwDkABcAWwBUAFQAVAAxAFQAnABwAFQAVAAXAFSAAIAggACADAgICAiAGoASCAiAAAjSADsADgDyAKSggBnfEA8AigCLAIwAIQCNAI4AjwAjAJAADgAlAJEAkgAoAJMAFwD2ABcAWwBUAFQAVAAxAFQAnABxAFQAVAAXAFSAAIAigACADAgICAiAGoATCAiAAAgI3xAPAIoAiwCMACEAjQCOAI8AIwCQAA4AJQCRAJIAKACTABcBBgAXAFsAVABUAFQAMQBUAJwAcgBUAFQAFwBUgACAJIAAgAwICAgIgBqAFAgIgAAI0wA6ADsADgEUARUAQKCggCXSAKYApwEYARlfEBNOU011dGFibGVEaWN0aW9uYXJ5owEYARoAq1xOU0RpY3Rpb25hcnnfEA8AigCLAIwAIQCNAI4AjwAjAJAADgAlAJEAkgAoAJMAFwEdABcAWwBUAFQAVAAxAFQAnABzAFQAVAAXAFSAAIAngACADAgICAiAGoAVCAiAAAjWACUADgAoAEkAIQAjASsBLAAXAFQAFwAxgCiAKYAACIAAXxAUWERHZW5lcmljUmVjb3JkQ2xhc3PSAKYApwEyATNdWERVTUxDbGFzc0ltcKYBNAE1ATYBNwE4AKtdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AigCLAIwAIQCNAI4AjwAjAJAADgAlAJEAkgAoAJMAFwA9ABcAWwBUAFQAVAAxAFQAnAB0AFQAVAAXAFSAAIAGgACADAgICAiAGoAWCAiAAAjSAKYApwFJAUpfEBJYRFVNTFN0ZXJlb3R5cGVJbXCnAUsBTAFNAU4BTwFQAKtfEBJYRFVNTFN0ZXJlb3R5cGVJbXBdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDTADoAOwAOAVIBUwBAoKCAJdIApgCnAVYBV1pYRFBNRW50aXR5pwFYAVkBWgFbAVwBXQCrWlhEUE1FbnRpdHldWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDTADoAOwAOAV8BYABAoKCAJdMAOgA7AA4BYwFkAECgoIAl0wA6ADsADgFnAWgAQKCggCXSAKYApwFrAWxeWERNb2RlbFBhY2thZ2WmAW0BbgFvAXABcQCrXlhETW9kZWxQYWNrYWdlXxAPWERVTUxQYWNrYWdlSW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcNIAOwAOAXMApKCAGdMAOgA7AA4BdgF3AECgoIAlUNIApgCnAXsBfFlYRFBNTW9kZWyjAXsBfQCrV1hETW9kZWwACAAZACIALAAxADoAPwBRAFYAWwBdAMwA0gDvAQEBCAEVASgBQAFOAWgBagFsAW4BcAFyAXQBdgGvAc4B6wIKAhwCPAJDAmECbQKJAo8CsQLSAuUC5wLpAusC7QLvAvEC8wL1AvcC+QL7Av0C/wMBAwIDBgMTAxsDJgMpAysDLgMwAzIDOQNqA44DsgPVA/wEHAQ8BEgESgRMBE4EUARSBFQEVgRYBFoEXARdBGIEagR3BHoEfAR/BIEEgwSSBLcE2wUCBSYFKAUqBSwFLgUwBTIFMwU1BUIFVQVXBVkFWwVdBV8FYQVjBWUFZwV6BXwFfgWABYIFhAWGBYgFigWMBY4FpAW3BdMF8AYMBiAGMgZIBmEGoAamBq8GvAbIBtIG3AbnBvIG/wcHBwkHCwcNBw8HEAcRBxIHEwcVBxcHGAcZBxsHHAclByYHKAcxBzwHRQdUB1sHYwdsB3UHiAeRB6QHuwfNCAwIDggQCBIIFAgVCBYIFwgYCBoIHAgdCB4IIAghCGAIYghkCGYIaAhpCGoIawhsCG4IcAhxCHIIdAh1CH4IfwiBCMAIwgjECMYIyAjJCMoIywjMCM4I0AjRCNII1AjVCRQJFgkYCRoJHAkdCR4JHwkgCSIJJAklCSYJKAkpCTIJMwk1CXQJdgl4CXoJfAl9CX4JfwmACYIJhAmFCYYJiAmJCYoJyQnLCc0JzwnRCdIJ0wnUCdUJ1wnZCdoJ2wndCd4J6wnsCe0J7wn4Cg4KFQoiCmEKYwplCmcKaQpqCmsKbAptCm8KcQpyCnMKdQp2Co8KkQqTCpUKlgqYCq8KuArGCtMK4Qr2CwoLIQszC3ILdAt2C3gLegt7C3wLfQt+C4ALgguDC4QLhguHC5ALpQu0C8kL1wvsDAAMFwwpDDYMNww4DDoMQwxODF0MaAx2DIsMnwy2DMgM1QzWDNcM2QzmDOcM6AzqDPcM+Az5DPsNBA0TDSANLw1BDVUNbA1+DYcNiA2KDZcNmA2ZDZsNnA2lDa8NtgAAAAAAAAICAAAAAAAAAX4AAAAAAAAAAAAAAAAAAA2+ Entity Undefined 1 Entity 1 ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Resource.abc ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Resource.abcd/File.json ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/Settings.bundle/Root.plist ================================================ StringsTable Root PreferenceSpecifiers Type PSGroupSpecifier Title Group Type PSTextFieldSpecifier Title Name Key name_preference DefaultValue IsSecure KeyboardType Alphabet AutocapitalizationType None AutocorrectionType No Type PSToggleSwitchSpecifier Title Enabled Key enabled_preference DefaultValue Type PSSliderSpecifier Key slider_preference DefaultValue 0.5 MinimumValue 0 MaximumValue 1 MinimumValueImage MaximumValueImage ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/TestIcon.icon/icon.json ================================================ { "groups" : [ { "layers" : [ { "image-name" : "back.svg", "name" : "back", "position" : { "scale" : 1, "translation-in-points" : [ 0, 0 ] } } ], "shadow" : { "kind" : "neutral", "opacity" : 0 }, "translucency" : { "enabled" : false, "value" : 0.1 } } ], "supported-platforms" : { "squares" : "shared" } } ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/ViewController.swift ================================================ import Contacts import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() _ = CNContact() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/en.lproj/Localizable.strings ================================================ /* Localizable.strings Project Created by ryohey on 2017/11/03. */ ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/en.lproj/Localizable.stringsdict ================================================ StringKey NSStringLocalizedFormatKey %#@VARIABLE@ Variable NSStringFormatSpecTypeKey NSStringPluralRuleType NSStringFormatValueTypeKey zero one two few many other ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/en.lproj/LocalizedStoryboard.strings ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/excluded-file ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/inputList.xcfilelist ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/module.modulemap ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_iOS/outputList.xcfilelist ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_iOS_Tests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: Tests/Fixtures/TestProject/App_iOS_Tests/TestProjectTests.swift ================================================ import XCTest class TestProjectTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. measure { // Put the code you want to measure the time of here. } } } ================================================ FILE: Tests/Fixtures/TestProject/App_iOS_UITests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: Tests/Fixtures/TestProject/App_iOS_UITests/TestProjectUITests.swift ================================================ import XCTest class TestProjectUITests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. measure { // Put the code you want to measure the time of here. } } } ================================================ FILE: Tests/Fixtures/TestProject/App_macOS/App-Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1 CustomSetting $CUSTOM_SETTING LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSMainStoryboardFile Main NSPrincipalClass NSApplication ================================================ FILE: Tests/Fixtures/TestProject/App_macOS/AppDelegate.swift ================================================ import Cocoa @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { // Insert code here to initialize your application } func applicationWillTerminate(_ aNotification: Notification) { // Insert code here to tear down your application } } ================================================ FILE: Tests/Fixtures/TestProject/App_macOS/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "size" : "16x16", "scale" : "1x" }, { "idiom" : "mac", "size" : "16x16", "scale" : "2x" }, { "idiom" : "mac", "size" : "32x32", "scale" : "1x" }, { "idiom" : "mac", "size" : "32x32", "scale" : "2x" }, { "idiom" : "mac", "size" : "128x128", "scale" : "1x" }, { "idiom" : "mac", "size" : "128x128", "scale" : "2x" }, { "idiom" : "mac", "size" : "256x256", "scale" : "1x" }, { "idiom" : "mac", "size" : "256x256", "scale" : "2x" }, { "idiom" : "mac", "size" : "512x512", "scale" : "1x" }, { "idiom" : "mac", "size" : "512x512", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Tests/Fixtures/TestProject/App_macOS/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Tests/Fixtures/TestProject/App_macOS/Base.lproj/Main.storyboard ================================================ Default Left to Right Right to Left Default Left to Right Right to Left ================================================ FILE: Tests/Fixtures/TestProject/App_macOS/ViewController.swift ================================================ import Cocoa class ViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } override var representedObject: Any? { didSet { // Update the view, if already loaded. } } } ================================================ FILE: Tests/Fixtures/TestProject/App_macOS_Tests/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: Tests/Fixtures/TestProject/App_macOS_Tests/TestProjectTests.swift ================================================ import XCTest class TestProjectTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. measure { // Put the code you want to measure the time of here. } } } ================================================ FILE: Tests/Fixtures/TestProject/App_supportedDestinations/Info.generated.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName TestApp CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1.0.0 ================================================ FILE: Tests/Fixtures/TestProject/App_supportedDestinations/Sources/MyAppApp.swift ================================================ import SwiftUI @main struct MyAppApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: Tests/Fixtures/TestProject/App_supportedDestinations/Sources/iOS/ContentView.swift ================================================ import SwiftUI struct ContentView: View { var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) Text("Hello, world! on iOS") } .padding() } } ================================================ FILE: Tests/Fixtures/TestProject/App_supportedDestinations/Sources/tvOS/ContentView.swift ================================================ import SwiftUI struct ContentView: View { var body: some View { VStack { Image(systemName: "house") .imageScale(.large) .foregroundColor(.accentColor) Text("Hello, world! tvOS") } .padding() } } ================================================ FILE: Tests/Fixtures/TestProject/App_supportedDestinations/Storyboards/LaunchScreen.storyboard ================================================ ================================================ FILE: Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/File_MACCATALYST.swift ================================================ import Foundation ================================================ FILE: Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/File_ios.swift ================================================ import Foundation ================================================ FILE: Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/File_macOS.swift ================================================ import Foundation ================================================ FILE: Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/File_tvOs.swift ================================================ import Foundation ================================================ FILE: Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/TVOS/File_B.swift ================================================ import Foundation ================================================ FILE: Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/iOs/File_A.swift ================================================ import Foundation ================================================ FILE: Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/macCatalyst/File_D.swift ================================================ import Foundation ================================================ FILE: Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/macos/File_C.swift ================================================ import Foundation ================================================ FILE: Tests/Fixtures/TestProject/App_watchOS/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "24x24", "idiom" : "watch", "scale" : "2x", "role" : "notificationCenter", "subtype" : "38mm" }, { "size" : "27.5x27.5", "idiom" : "watch", "scale" : "2x", "role" : "notificationCenter", "subtype" : "42mm" }, { "size" : "29x29", "idiom" : "watch", "role" : "companionSettings", "scale" : "2x" }, { "size" : "29x29", "idiom" : "watch", "role" : "companionSettings", "scale" : "3x" }, { "size" : "40x40", "idiom" : "watch", "scale" : "2x", "role" : "appLauncher", "subtype" : "38mm" }, { "size" : "86x86", "idiom" : "watch", "scale" : "2x", "role" : "quickLook", "subtype" : "38mm" }, { "size" : "98x98", "idiom" : "watch", "scale" : "2x", "role" : "quickLook", "subtype" : "42mm" }, { "idiom" : "watch-marketing", "size" : "1024x1024", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Tests/Fixtures/TestProject/App_watchOS/Base.lproj/Interface.storyboard ================================================