diff --git a/.circleci/config.yml b/.circleci/config.yml
deleted file mode 100644
index 14b4cb35a..000000000
--- a/.circleci/config.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-version: 2
-jobs:
- macOS_swift_4.1:
- macos:
- xcode: "9.4.0"
- steps:
- - checkout
- - run:
- name: Build Test Project
- command: ./build-fixtures.sh
- macOS_swift_4.2:
- macos:
- xcode: "10.0.0"
- steps:
- - checkout
- - run:
- name: Build
- command: swift build
- - run:
- name: Test
- command: swift test 2>&1 | xcpretty
- - run:
- name: Check Project Diff
- command: ./diff-fixtures.sh
- - run:
- name: Build Test Project
- command: ./build-fixtures.sh
- - run:
- name: Build release
- command: make build
-workflows:
- version: 2
- workflow:
- jobs:
- - macOS_swift_4.1
- - macOS_swift_4.2
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 000000000..84679494b
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,36 @@
+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
diff --git a/.gitignore b/.gitignore
index 01d32c761..aeb56d61d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
.DS_Store
-
+.swiftpm
/.build
/Packages
xcuserdata
@@ -7,3 +7,7 @@ xcuserdata
*.xcuserstate
XcodeGen.xcodeproj
xcodegen.zip
+xcodegen.artifactbundle.zip
+.vscode/launch.json
+DerivedData
+.context
diff --git a/.swiftformat b/.swiftformat
new file mode 100644
index 000000000..1899668f8
--- /dev/null
+++ b/.swiftformat
@@ -0,0 +1,14 @@
+--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
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45cde5806..f876da2ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,492 +1,1384 @@
# Change Log
-## Master
+## Next Version
+
+## 2.45.4
+
+### Fixed
+- Fix makePathRelative using wrong base path when projectDirectory differs #1608 @yonaskolb
+- Fix synced folder configFiles creating duplicate groups #1607 @AlexNsbmr
+- Fix synced folder root group duplication for paths referenced by multiple targets #1607 @AlexNsbmr
+- Fix synced folder directory-level membershipExceptions not working #1607 @AlexNsbmr
+
+## 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
-- Added `weak` Weak linking setting for dependencies [411](https://github.com/yonaskolb/XcodeGen/pull/411) @alvarhansen
+
+- 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
-- Performance improvements for large projects [388](https://github.com/yonaskolb/XcodeGen/pull/388) [417](https://github.com/yonaskolb/XcodeGen/pull/417) [416](https://github.com/yonaskolb/XcodeGen/pull/416) @yonaskolb @kastiglione
-- Upgraded to xcodeproj 6 [388](https://github.com/yonaskolb/XcodeGen/pull/388) @yonaskolb
-- Upgraded to Swift 4.2 [388](https://github.com/yonaskolb/XcodeGen/pull/388) @yonaskolb
-- Remove iOS codesigning sdk restriction in setting preset [414](https://github.com/yonaskolb/XcodeGen/pull/414) @yonaskolb
+
+- 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
-- Fixed code signing issues [414](https://github.com/yonaskolb/XcodeGen/pull/414) @yonaskolb
-- Fixed `TargetSource.headerVisibility` not being set in initializer [419](https://github.com/yonaskolb/XcodeGen/pull/419) @jerrymarino
+
+- 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](https://github.com/yonaskolb/XcodeGen/pull/404) @pepibumur
-- Fixed default settings for macOS unit-tests [387](https://github.com/yonaskolb/XcodeGen/pull/387) @frankdilo
-- Fixed Copy Headers phase ordering for Xcode 10 [401](https://github.com/yonaskolb/XcodeGen/pull/401) @brentleyjones
-- Fixed generated schemes on aggregate targets [394](https://github.com/yonaskolb/XcodeGen/pull/394) @vgorloff
+
+- 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](https://github.com/yonaskolb/XcodeGen/pull/390) @Saik0s
-- Update `PathKit`, `Spectre`, `Yams` and `xcodeproj` dependencies
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.11.1...1.11.2)
+- 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](https://github.com/yonaskolb/XcodeGen/pull/382) @brentleyjones
-- Fixed aggregate targets not being found with `transitivelyLinkDependencies` [383](https://github.com/yonaskolb/XcodeGen/pull/383) @brentleyjones
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.11.0...1.11.1)
+- 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](https://github.com/yonaskolb/XcodeGen/pull/351) @keith
-- Added `requiresObjCLinking` to `target` [354](https://github.com/yonaskolb/XcodeGen/pull/354) @brentleyjones
-- Added `targetTemplates` [355](https://github.com/yonaskolb/XcodeGen/pull/355) @yonaskolb
-- Added `aggregateTargets` [353](https://github.com/yonaskolb/XcodeGen/pull/353) [381](https://github.com/yonaskolb/XcodeGen/pull/381) @yonaskolb
-- Added `options.groupSortPosition` [356](https://github.com/yonaskolb/XcodeGen/pull/356) @yonaskolb
-- Added ability to specify `copyFiles` build phase for sources [345](https://github.com/yonaskolb/XcodeGen/pull/345) @brentleyjones
-- Added ability to specify a `minimumXcodeGenVersion` [349](https://github.com/yonaskolb/XcodeGen/pull/349) @brentleyjones
-- Added `customArchiveName` and `revealArchiveInOrganizer` to `archive` [367](https://github.com/yonaskolb/XcodeGen/pull/367) @sxua
-
-#### Fixed
-- Sort files using localizedStandardCompare [341](https://github.com/yonaskolb/XcodeGen/pull/341) @rohitpal440
-- Use the latest `xcdatamodel` when sorted by version [341](https://github.com/yonaskolb/XcodeGen/pull/341) @rohitpal440
-- Fixed compiler flags being set on non source files in mixed build phase target sources [347](https://github.com/yonaskolb/XcodeGen/pull/347) @brentleyjones
-- Fixed `options.xcodeVersion` not being parsed [348](https://github.com/yonaskolb/XcodeGen/pull/348) @brentleyjones
-- Fixed non-application targets using `carthage copy-frameworks` [361](https://github.com/yonaskolb/XcodeGen/pull/361) @brentleyjones
-- Set `xcdatamodel` based on `xccurrentversion` if available [364](https://github.com/yonaskolb/XcodeGen/pull/364) @rpassis
-- XPC Services are now correctly copied [368](https://github.com/yonaskolb/XcodeGen/pull/368) @brentley
-- Fixed `.metal` files being added to resources [380](https://github.com/yonaskolb/XcodeGen/pull/380) @vgorloff
-
-#### Changed
-- Improved linking for `static.library` targets [352](https://github.com/yonaskolb/XcodeGen/pull/352) @brentleyjones
-- Changed default group sorting to be after files [356](https://github.com/yonaskolb/XcodeGen/pull/356) @yonaskolb
-- Moved `Frameworks` and `Products` top level groups to bottom [356](https://github.com/yonaskolb/XcodeGen/pull/356) @yonaskolb
-- `modulemap` files are automatically copied to the products directory for static library targets [346](https://github.com/yonaskolb/XcodeGen/pull/346) @brentleyjones
-- Public header files are automatically copied to the products directory for static library targets [365](https://github.com/yonaskolb/XcodeGen/pull/365) @brentleyjones
-- Swift Objective-C Interface Header files are automatically copied to the products directory for static library targets [366](https://github.com/yonaskolb/XcodeGen/pull/366) @brentleyjones
-- `FRAMEWORK_SEARCH_PATHS` are adjusted for `framework` dependencies [373](https://github.com/yonaskolb/XcodeGen/pull/373) @brentley
-- `library.static` targets have `SKIP_INSTALL` set to `YES` [358](https://github.com/yonaskolb/XcodeGen/pull/358) @brentley
-- Copy files phases have descriptive names [360](https://github.com/yonaskolb/XcodeGen/pull/360) @brentley
+
+- 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`
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.10.3...1.11.0)
-
## 1.10.3
#### Fixed
-- Fixed Mint installations finding `SettingPresets` [338](https://github.com/yonaskolb/XcodeGen/pull/338) @yonaskolb
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.10.2...1.10.3)
+- Fixed Mint installations finding `SettingPresets` #338 @yonaskolb
## 1.10.2
#### Changed
-- Set `transitivelyLinkDependencies` to false by default
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.10.1...1.10.2)
+- Set `transitivelyLinkDependencies` to false by default
## 1.10.1
#### Fixed
-- Fixed `transitivelyLinkDependencies` typo [332](https://github.com/yonaskolb/XcodeGen/pull/332) @brentleyjones
-- Fixed framework target dependencies not being code signed by default [332](https://github.com/yonaskolb/XcodeGen/pull/332) @yonaskolb
+
+- 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](https://github.com/yonaskolb/XcodeGen/pull/332) @yonaskolb
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.10.0...1.10.1)
+- Code sign all dependencies by default except target executables #332 @yonaskolb
## 1.10.0
#### Added
-- Added build rule support [306](https://github.com/yonaskolb/XcodeGen/pull/306) @yonaskolb
-- Added support for frameworks in sources [308](https://github.com/yonaskolb/XcodeGen/pull/308) @keith
-- Added ability to automatically embed transient dependencies. Controlled with `transitivelyLinkDependencies` [327](https://github.com/yonaskolb/XcodeGen/pull/327) @brentleyjones
+
+- 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](https://github.com/yonaskolb/XcodeGen/pull/298) @keith
-- By default don't CodeSignOnCopy `target` dependencies. This can still be controlled with `Dependency.codeSign` [324](https://github.com/yonaskolb/XcodeGen/pull/324) @yonaskolb
-
-#### Fixed
-- Fixed PBXBuildFile and PBXFileReference being incorrectly generated for Legacy targets [296](https://github.com/yonaskolb/XcodeGen/pull/296) @sascha
-- Fixed required sources build phase not being generated if there are no sources [307](https://github.com/yonaskolb/XcodeGen/pull/307) @yonaskolb
-- Fixed install script in binary release [303](https://github.com/yonaskolb/XcodeGen/pull/303) @alvarhansen
-- Removed `ENABLE_TESTABILITY` from framework setting presets [299](https://github.com/yonaskolb/XcodeGen/pull/299) @allu22
-- Fixed homebrew installation [297](https://github.com/yonaskolb/XcodeGen/pull/297) @vhbit
-- `cc` files are now automatically recognized as source files [317](https://github.com/yonaskolb/XcodeGen/pull/317) @maicki
-- Fixed `commandLineArguments` not parsing when they had dots in them [323](https://github.com/yonaskolb/XcodeGen/pull/323) @yonaskolb
-- Fixed excluding directories that only have sub directories [326](https://github.com/yonaskolb/XcodeGen/pull/326) @brentleyjones
-- Made `PBXContainerItemProxy` ID more deterministic
-- Fixed generated framework schemes from being executable [328](https://github.com/yonaskolb/XcodeGen/pull/328) @brentleyjones
+- 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
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.9.0...1.10.0)
+#### 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](https://github.com/yonaskolb/XcodeGen/pull/280) @yonaskolb
-- Individual files can now be added to `fileGroups` [293](https://github.com/yonaskolb/XcodeGen/pull/293) @yonaskolb
+
+- 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](https://github.com/yonaskolb/XcodeGen/pull/284) @LinusU
-- **Breaking for ProjectSpec library users** Changed `ProjectSpec` to `Project` and `ProjectSpec.Options` to `SpecOptions` [281](https://github.com/yonaskolb/XcodeGen/pull/281) @jerrymarino
+- 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](https://github.com/yonaskolb/XcodeGen/pull/288) @yonaskolb
-- Quoted values now correctly get parsed as strings [282](https://github.com/yonaskolb/XcodeGen/pull/282) @yonaskolb
-- Fixed adding a root source folder when `createIntermediateGroups` is on [291](https://github.com/yonaskolb/XcodeGen/pull/291) @yonaskolb
-- Fixed Homebrew installations issues on some machines [289](https://github.com/yonaskolb/XcodeGen/pull/289) @vhbit
-- Fixed files that are added as root sources from having invalid parent groups outside the project directory [293](https://github.com/yonaskolb/XcodeGen/pull/293) @yonaskolb
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.8.0...1.9.0)
+- 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](https://github.com/yonaskolb/XcodeGen/pull/269) @keith
-- Added Target `attributes` [276](https://github.com/yonaskolb/XcodeGen/pull/276) @yonaskolb
-- Automatically set `DevelopmentTeam` and `ProvisioningStyle` within `TargetAttributes` if relevant build settings are defined [277](https://github.com/yonaskolb/XcodeGen/pull/277) @yonaskolb
+
+- 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](https://github.com/yonaskolb/XcodeGen/pull/272) @LinusU
+
+- Fixed default `LD_RUNPATH_SEARCH_PATHS` for app extensions #272 @LinusU
#### Internal
-- Make `LegacyTarget` init public [264](https://github.com/yonaskolb/XcodeGen/pull/264) @jerrymarino
-- Upgrade to *xcproj* to 4.2.0, *Yams* to 0.6.0 and *PathKit* to 0.9.1 @yonaskolb
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.7.0...1.8.0)
+- 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](https://github.com/yonaskolb/XcodeGen/pull/239) [254](https://github.com/yonaskolb/XcodeGen/pull/254) [259](https://github.com/yonaskolb/XcodeGen/pull/259) @turekj @toshi0383
-- Added `carthageExecutablePath` option [244](https://github.com/yonaskolb/XcodeGen/pull/244) @akkyie
-- Added `parallelizeBuild` and `buildImplicitDependencies` to Schemes [241](https://github.com/yonaskolb/XcodeGen/pull/241) @rahul-malik
+- 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](https://github.com/yonaskolb/XcodeGen/pull/249) @yonaskolb
-- Projects are now generated atomically by writing to a temporary directory first [250](https://github.com/yonaskolb/XcodeGen/pull/250) @yonaskolb
-- Added script for adding precompiled binary to releases [246](https://github.com/yonaskolb/XcodeGen/pull/246) @toshi0383
-- Added optional `headerVisibilty` to target source. This still defaults to public [252](https://github.com/yonaskolb/XcodeGen/pull/252) @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](https://github.com/yonaskolb/XcodeGen/pull/248) @yonaskolb
-- Fixed setting `buildPhase` on a `folder` source. This allows for a folder of header files [254](https://github.com/yonaskolb/XcodeGen/pull/254) @toshi0383
-- Carthage dependencies are not automatically embedded into test targets [256](https://github.com/yonaskolb/XcodeGen/pull/256) @yonaskolb
-- Carthage dependencies now respect the `embed` property [256](https://github.com/yonaskolb/XcodeGen/pull/256) @yonaskolb
-- iMessage extensions now have proper setting presets in regards to app icon and runtime search paths [255](https://github.com/yonaskolb/XcodeGen/pull/255) @yonaskolb
-- Excluded files are not added within .lproj directories [238](https://github.com/yonaskolb/XcodeGen/pull/238) @toshi0383
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.6.0...1.7.0)
+- 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](https://github.com/yonaskolb/XcodeGen/pull/231) @kastiglione
-- Added `options.disabledValidations` including `missingConfigs` to disable project validation errors [220](https://github.com/yonaskolb/XcodeGen/pull/220) @keith
-- Generate UI Test Target Attributes [221](https://github.com/yonaskolb/XcodeGen/pull/221) @anreitersimon
+
+- 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](https://github.com/yonaskolb/XcodeGen/pull/217) @allu22
-- Fixed how `lastKnownFileType` and `explicitFileType` were generated across platforms [115](https://github.com/yonaskolb/XcodeGen/pull/115) @toshi0383
+
+- 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](https://github.com/yonaskolb/XcodeGen/pull/218) @allu22
-- Updated to xcproj 4.0 [227](https://github.com/yonaskolb/XcodeGen/pull/227)
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.5.0...1.6.0)
+- 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](https://github.com/yonaskolb/XcodeGen/pull/170) @alexruperez
-- added support for `commandLineOptions` in target schemes [172](https://github.com/yonaskolb/XcodeGen/pull/172) @rahul-malik
-- added Project spec as a SwiftPM library for reuse in other projects [164](https://github.com/yonaskolb/XcodeGen/pull/164) @soffes
-- added `implicit` option for framework dependencies [166](https://github.com/yonaskolb/XcodeGen/pull/166) @sbarow
-- added `--quite` option to CLI [167](https://github.com/yonaskolb/XcodeGen/pull/167) @soffes
-- can now print version with `-v` in addition to `--version` [174](https://github.com/yonaskolb/XcodeGen/pull/174) @kastiglione
-- added support for legacy targets [175](https://github.com/yonaskolb/XcodeGen/pull/175) @bkase
-- added support for indentation options [190](https://github.com/yonaskolb/XcodeGen/pull/190) @bkase
-- added source excludes [135](https://github.com/yonaskolb/XcodeGen/pull/135) [161](https://github.com/yonaskolb/XcodeGen/pull/161) [190](https://github.com/yonaskolb/XcodeGen/pull/190) @peymankh @
-- added `options.xcodeVersion` [197](https://github.com/yonaskolb/XcodeGen/pull/197) @yonaskolb @peymankh
-- add test targets to Scheme [195](https://github.com/yonaskolb/XcodeGen/pull/195) @vhbit
-- add option to make a source file optional incase it will be generated later [200](https://github.com/yonaskolb/XcodeGen/pull/200) @vhbit
-- finalize Scheme spec [201](https://github.com/yonaskolb/XcodeGen/pull/201) @yonaskolb
-- added `buildPhase` setting to target source for overriding the guessed build phase of files [206](https://github.com/yonaskolb/XcodeGen/pull/206) @yonaskolb
-- added `deploymentTarget` setting to project and target [205](https://github.com/yonaskolb/XcodeGen/pull/205) @yonaskolb
+
+- 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](https://github.com/yonaskolb/XcodeGen/pull/194) @kastiglione
-- change setting presets so that icon settings only get applied to application targets [204](https://github.com/yonaskolb/XcodeGen/pull/204) @yonaskolb
-- changed scheme build targets format [203](https://github.com/yonaskolb/XcodeGen/pull/203) @yonaskolb
-- when specifying a `--spec` argument, the default for the `--project` path is now the directory containing the spec [211](https://github.com/yonaskolb/XcodeGen/pull/211) @yonaskolb
+- 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](https://github.com/yonaskolb/XcodeGen/pull/186) @allu22
-- fixed `createIntermediateGroups` when using a relative spec path [184](https://github.com/yonaskolb/XcodeGen/pull/184) @kastiglione
-- fixed command line arguments for test and profile from being overridden [199](https://github.com/yonaskolb/XcodeGen/pull/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](https://github.com/yonaskolb/XcodeGen/pull/212) @yonaskolb
-- fixed target product name not being written. Fixes integration with R.swift [213](https://github.com/yonaskolb/XcodeGen/pull/213) @yonaskolb
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.4.0...1.5.0)
+- 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](https://github.com/yonaskolb/XcodeGen/pull/112) @mironal
-- added support for adding individual file sources [106](https://github.com/yonaskolb/XcodeGen/pull/106) [133](https://github.com/yonaskolb/XcodeGen/pull/133) [142](https://github.com/yonaskolb/XcodeGen/pull/142) [139](https://github.com/yonaskolb/XcodeGen/pull/139) @bkase
-- added source compiler flag support [121](https://github.com/yonaskolb/XcodeGen/pull/121) @bkase
-- added `ProjectSpec.options.createIntermediateGroups` [108](https://github.com/yonaskolb/XcodeGen/pull/108) @bkase
-- added better json loading support [127](https://github.com/yonaskolb/XcodeGen/pull/127) @rahul-malik
-- added source `name` for customizing names of source directories and file [146](https://github.com/yonaskolb/XcodeGen/pull/146) @yonaskolb
-- added folder reference source support via a new `type` property [151](https://github.com/yonaskolb/XcodeGen/pull/151) @yonaskolb
-- added `ProjectSpec.options.developmentLanguage` [155](https://github.com/yonaskolb/XcodeGen/pull/155) @yonaskolb
+
+- 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](https://github.com/yonaskolb/XcodeGen/pull/113) @yonaskolb
-- build settings from presets will be removed if they are provided in `xcconfig` files [77](https://github.com/yonaskolb/XcodeGen/pull/77) @toshi0383
-- all files and groups are sorted by type and then alphabetically [144](https://github.com/yonaskolb/XcodeGen/pull/144) @yonaskolb
-- target sources can now have an expanded form [119](https://github.com/yonaskolb/XcodeGen/pull/119) @yonaskolb
-- empty build phases are now not generated [149](https://github.com/yonaskolb/XcodeGen/pull/149) @yonaskolb
-- make UUIDs more deterministic [154](https://github.com/yonaskolb/XcodeGen/pull/154) @yonaskolb
+
+- 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](https://github.com/yonaskolb/XcodeGen/pull/118) @ryohey
-- fixed localized files with the same name [126](https://github.com/yonaskolb/XcodeGen/pull/126) @ryohey
-- fix intermediate sources [144](https://github.com/yonaskolb/XcodeGen/pull/144) @yonaskolb
-- fix cyclical target dependencies not working [147](https://github.com/yonaskolb/XcodeGen/pull/147) @yonaskolb
-- fix directory bundles not being added properly when referenced directly [148](https://github.com/yonaskolb/XcodeGen/pull/1478) @yonaskolb
-- made `mm`, `c` and `S` file be parsed as source files [120](https://github.com/yonaskolb/XcodeGen/pull/120) [125](https://github.com/yonaskolb/XcodeGen/pull/125) [138](https://github.com/yonaskolb/XcodeGen/pull/138) @bkase @enmiller
-- fix the generation of localized variant groups if there is no `Base.lproj` [157](https://github.com/yonaskolb/XcodeGen/pull/157) @ryohey
-- all localizations found are added to a projects known regions [157](https://github.com/yonaskolb/XcodeGen/pull/157) @ryohey
+
+- 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
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.3.0...1.4.0)
-
## 1.3.0
#### Added
-- generate output files for Carthage copy-frameworks script [84](https://github.com/yonaskolb/XcodeGen/pull/84) @mironal
-- added options.settingPreset to choose which setting presets get applied [100](https://github.com/yonaskolb/XcodeGen/pull/101) @yonaskolb
-- added `link` option for target dependencies [109](https://github.com/yonaskolb/XcodeGen/pull/109) @keith
+
+- 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](https://github.com/yonaskolb/XcodeGen/pull/85) @enmiller
-- don't copy base settings if config type has been left out [100](https://github.com/yonaskolb/XcodeGen/pull/100) @yonaskolb
-- generate localised files under a single variant group [70](https://github.com/yonaskolb/XcodeGen/pull/70) @ryohey
-- don't apply common project settings to configs with no type [100](https://github.com/yonaskolb/XcodeGen/pull/100) @yonaskolb
-- config references in settings can now be partially matched and are case insensitive [111](https://github.com/yonaskolb/XcodeGen/pull/111) @yonaskolb
+
+- 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](https://github.com/yonaskolb/XcodeGen/pull/82) @toshi0383
-- fixed copying of watchOS app resources [96](https://github.com/yonaskolb/XcodeGen/pull/96) @keith
-- automatically ignore more file types for a target's sources (entitlements, gpx, apns) [94](https://github.com/yonaskolb/XcodeGen/pull/94) @keith
-- change make build to a PHONY task [98](https://github.com/yonaskolb/XcodeGen/pull/98) @keith
-- allow copying of resource files from dependant targets [95](https://github.com/yonaskolb/XcodeGen/pull/95) @keith
-- fixed library linking [93](https://github.com/yonaskolb/XcodeGen/pull/93) @keith
-- fixed duplicate carthage file references [107](https://github.com/yonaskolb/XcodeGen/pull/107) @yonaskolb
-- an error is now shown if you try and generate a target scheme and don't have debug and release builds @yonaskolb
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.2.4...1.3.0)
+- 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
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.2.3...1.2.4)
+- update to xcproj 0.3.0
## 1.2.3
#### Fixed
-- Fixed wrong carthage directory name reference for macOS [74](https://github.com/yonaskolb/XcodeGen/pull/74) @toshi0383
-- Removed unnecessary `carthage copy-frameworks` for macOS app target [76](https://github.com/yonaskolb/XcodeGen/pull/76) @toshi0383
+
+- 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
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.2.2...1.2.3)
-
## 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`
-
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.2.0...1.2.2)
-
## 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` [PR#64](https://github.com/yonaskolb/XcodeGen/pull/64)
-- add `fileGroups` to project spec for adding groups of files that aren't target source files [PR#64](https://github.com/yonaskolb/XcodeGen/pull/64)
+- 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` [PR#67](https://github.com/yonaskolb/XcodeGen/pull/67)
-- add `:REPLACE` syntax when merging `include` [PR#68](https://github.com/yonaskolb/XcodeGen/pull/68)
+- 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` [PR#64](https://github.com/yonaskolb/XcodeGen/pull/64)
-- look for `INFOPLIST_FILE` setting in project and xcconfig files before adding it automatically. It was just looking in target settings before [PR#64](https://github.com/yonaskolb/XcodeGen/pull/64)
+- 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
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.1.0...1.2.0)
-
## 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`
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.0.1...1.1.0)
-
### 1.0.1
### Fixed
+
- fixed incorrect default build script shell path
- fixed install scripts
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/1.0.0...1.0.1)
-
## 1.0.0
#### Added
-- Swift 4 support [PR#52](https://github.com/yonaskolb/XcodeGen/pull/52)
-- Support for C and C++ files [PR#48](https://github.com/yonaskolb/XcodeGen/pull/48) by @antoniocasero
+
+- 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 [PR#50](https://github.com/yonaskolb/XcodeGen/pull/50) by @antoniocasero
-#### Changed
-- updated to xcodeproj 0.1.2 [PR#56](https://github.com/yonaskolb/XcodeGen/pull/56)
-- **BREAKING**: changed target definitions from list to map [PR#54](https://github.com/yonaskolb/XcodeGen/pull/54) See [Project Spec](docs/ProjectSpec.md)
+- fixed empty string in YAML not being parsed properly #50 by @antoniocasero
+#### Changed
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/0.6.1...1.0.0)
+- 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 [PR#45](https://github.com/yonaskolb/XcodeGen/pull/45)
+
+- 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 [PR#46](https://github.com/yonaskolb/XcodeGen/pull/46)
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/0.6.0...0.6.1)
+- 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 [PR#44](https://github.com/yonaskolb/XcodeGen/pull/44)
+
+- 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
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/0.5.1...0.6.0)
-
## 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 [PR#43](https://github.com/yonaskolb/XcodeGen/pull/43)
-
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/0.5.0...0.5.1)
+- Fix build scripts with "" generating invalid projects #43
## 0.5.0
+
#### Added
-- Added multi platform targets [PR#35](https://github.com/yonaskolb/XcodeGen/pull/35)
-- Automatically generate platform specific `FRAMEWORK_SEARCH_PATHS` for Carthage dependencies [PR#38](https://github.com/yonaskolb/XcodeGen/pull/38)
-- Automatically find Info.plist and set `INFOPLIST_FILE` build setting if it doesn't exist on a target [PR#40](https://github.com/yonaskolb/XcodeGen/pull/40)
-- Add options for controlling embedding of dependencies [PR#37](https://github.com/yonaskolb/XcodeGen/pull/37)
+
+- 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
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/0.4.0...0.5.0)
-
## 0.4.0
##### Added
-- Homebrew support [PR#16](https://github.com/yonaskolb/XcodeGen/pull/16) by @pepibumur
-- Added `runOnlyWhenInstalling` to build scripts [PR#32](https://github.com/yonaskolb/XcodeGen/pull/32)
-- Added `carthageBuildPath` option [PR#34](https://github.com/yonaskolb/XcodeGen/pull/34)
+
+- 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 https://github.com/swift-xcode/xcodeproj 0.1.1 [PR#33](https://github.com/yonaskolb/XcodeGen/pull/33)
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/0.3.0...0.4.0)
+- 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 [PR#19](https://github.com/yonaskolb/XcodeGen/pull/19)
-- Added test targets to generated target schemes via `Target.scheme.testTargets` [PR#21](https://github.com/yonaskolb/XcodeGen/pull/21)
+
+- 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`
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/0.2...0.3.0)
+- Changed `Target.generatedSchemes` to `Target.scheme.configVariants`
## 0.2.0 - Build scripts
#### Added
-- Added Target build scripts with `Target.prebuildScripts` and `Target.postbuildScripts` [PR#17](https://github.com/yonaskolb/XcodeGen/pull/17)
+
+- 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
-[Commits](https://github.com/yonaskolb/XcodeGen/compare/0.1...0.2)
+- Fixed some project objects sometimes having duplicate ids
## 0.1.0
+
First official release
diff --git a/Docs/Examples.md b/Docs/Examples.md
index 8237adf66..a4a32b93f 100644
--- a/Docs/Examples.md
+++ b/Docs/Examples.md
@@ -2,5 +2,13 @@
These are a bunch of real world examples of XcodeGen project specs. Feel free to add your own via PR.
-- [num42/RxUserDefaults](https://github.com/num42/RxUserDefaults/blob/master/project.yml)
- [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.
diff --git a/Docs/FAQ.md b/Docs/FAQ.md
index b57c39be8..9d1096816 100644
--- a/Docs/FAQ.md
+++ b/Docs/FAQ.md
@@ -1,6 +1,7 @@
# 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 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
@@ -8,12 +9,34 @@ Absolutely. You will get the most out of XcodeGen by adding your project to your
>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. Unfortunately this is a manual step at the moment, but in the future this could be automated.
+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.
-For now you can always add xcodegen as a git `post-checkout` hook.
+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, simply generate your project and then run `pod install` which will integrate with your project and create a workspace.
+## 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
diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md
index 5117b78ed..17f6c5b33 100644
--- a/Docs/ProjectSpec.md
+++ b/Docs/ProjectSpec.md
@@ -2,53 +2,102 @@
The project spec can be written in either YAML or JSON. All the examples below use YAML.
-Required properties are marked with checkbox. Some of the YAML examples don't show all the required properties. For example not all target examples will have a platform or type, even though they are required.
+- [x] required property
+- [ ] optional property
-### Index
+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)
- - [Configs](#configs)
- - [Setting Groups](#setting-groups)
+ - [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)
- - [Sources](#sources)
- - [Config Files](#config-files)
- - [Settings](#settings)
- - [Build Script](#build-script)
- - [Build Rule](#build-rule)
- - [Dependency](#dependency)
- - [Target Scheme](#target-scheme)
- - [Legacy Target](#legacy-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. This defaults to ``{"LastUpgradeCheck": "XcodeVersion"}`` with `xcodeVersion` being set by [Options](#options)`.xcodeVersion`
+- [ ] **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 hierachy anyway. For example a folder of xcconfig files that aren't already added by any target sources, or a Readme file.
+- [ ] **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)
-- [ ] **targetTemplates**: **[String: [Target](#target)]** - 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`.
+- [ ] **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 string paths or a single string path. They will be merged in order and then the current spec will be merged on top.
+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 hierachy
+- 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
@@ -72,7 +121,7 @@ Note that target names can also be changed by adding a `name` property to a targ
- [ ] **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.
+- [ ] **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
@@ -84,99 +133,272 @@ Note that target names can also be changed by adding a `name` property to a targ
- [ ] **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:
- - `top` - at the top, before files
- - `bottom` - at the bottom, after other files
- `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.
+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
- Release: release
+ 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
+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:
- preset1:
- BUILD_SETTING: value
- preset2:
+ preset_generic:
+ CUSTOM_SETTING: value_custom
+ preset_debug:
+ BUILD_SETTING: value_debug
+ preset_release:
base:
- BUILD_SETTING: value
+ BUILD_SETTING: value_release
+ preset_all:
groups:
- - preset
- preset3:
- configs:
- debug:
- groups:
- - preset
+ - preset_generic
+ configs:
+ debug:
+ groups:
+ - preset_debug
+ release:
+ groups:
+ - preset_release
+
+targets:
+ Application:
+ settings:
+ groups:
+ - preset_all
```
## Settings
-Settings can either be a simple map of build settings `[String:String]`, or can be more advanced with the following properties:
+Settings correspond to `Build Settings` tab in Xcode. To display Setting Names instead of Setting Titles, select `Editor -> Show Setting Names` in Xcode.
-- [ ] **groups**: **[String]** - List of 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`.
+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:
- BUILD_SETTING_1: value 1
- BUILD_SETTING_2: value 2
+ 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:
- BUILD_SETTING_1: value 1
+ PRODUCT_NAME: XcodeGenProduct
configs:
- my_config:
- BUILD_SETTING_2: value 2
+ 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.
+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 dependencies are used, the platform build path will be added to 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
-- [ ] **templates**: **[String]** - A list of target templates that will be merged in order
+- [ ] **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 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 catagories 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 catagories 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.
-- [ ] **prebuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *before* any other build phases
-- [ ] **postbuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *after* any other build phases
+- [ ] **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.
@@ -184,29 +406,39 @@ Settings are merged in the following order: groups, base, configs.
- `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`
-- `app-extension`
- `bundle`
-- `bundle.unit-test`
-- `bundle.ui-testing`
- `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)
@@ -214,17 +446,22 @@ This will provide default build settings for a certain product type. It can be a
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 you reference the string `$platform` anywhere within the target spec, that will be replaced with the 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`.
+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.
@@ -233,30 +470,67 @@ 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
+ MY_SETTING: platform ${platform}
groups:
- - $platform
+ - ${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 Object**:
+#### 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
-- [ ] **compilerFlags**: **[String]** or **String** - A list of compilerFlags to add to files under this specific path provided as a list or a space delimitted string. Defaults to empty.
-- [ ] **excludes**: **[String]** - A list of global patterns representing the files to exclude.
+- [ ] **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
@@ -280,28 +554,36 @@ A source can be provided via a string (the path) or an object of the form:
- `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:
@@ -310,25 +592,44 @@ targets:
subpath: include/$(PRODUCT_NAME)
- path: Resources
type: folder
+ - path: Path/To/File.asset
+ resourceTags: [tag1, tag2]
```
### Dependency
-A dependency can be one of a 3 types:
-
-- `target: name` - links to another target
-- `framework: path` - links to a framework
-- `carthage: name` - helper for linking to a Carthage framework
+A dependency can be one of a 6 types:
-**Embed options**:
+- `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.
-These only applied to `target` and `framework` dependencies.
+**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
+- [ ] **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**:
@@ -338,25 +639,102 @@ This only applies to `framework` dependencies. Implicit framework dependencies a
**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.
@@ -371,19 +749,78 @@ targets:
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 added via **prebuildScripts** or **postBuildScripts**. They run before or after any other build phases respectively and in the order defined. Each script can contain:
+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 yes
-- [ ] **runOnlyWhenInstalling**: **Bool** - whether the script is only run when installing (`runOnlyForDeploymentPostprocessing`). Defaults to no
+- [ ] **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.
@@ -392,21 +829,29 @@ A multiline script can be written using the various YAML multiline methods, for
```yaml
targets:
MyTarget:
- prebuildScripts:
+ 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
- postbuildScripts:
+ 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
@@ -418,19 +863,45 @@ targets:
- [ ] **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
+### 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**: **[String]** - a list of test targets that should be included in the scheme. These will be added to the build targets and the test entries
+- [ ] **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.
-- [ ] **preActions**: **[[Execution Action](#execution-action)]** - Scripts that are run *before* all actions
-- [ ] **postActions**: **[[Execution Action](#execution-action)]** - Scripts that are run *after* all actions
+- [ ] **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:
@@ -449,7 +920,7 @@ configs:
Test Release: release
Staging Release: release
Production Release: release
-targets
+targets:
MyApp:
scheme:
testTargets:
@@ -459,6 +930,9 @@ targets
- Staging
- Production
gatherCoverageData: true
+ coverageTargets:
+ - MyTarget1
+ - ExternalTarget/OtherTarget1
commandLineArguments:
"-MyEnabledArg": true
"-MyDisabledArg": false
@@ -468,7 +942,7 @@ targets
sources: Tests
```
-### Legacy Target
+### 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.
@@ -483,11 +957,36 @@ 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.
+- [ ] **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)
@@ -498,6 +997,7 @@ Schemes allows for more control than the convenience [Target Scheme](#target-sch
- [ ] ***profile***: The profile action
- [ ] ***analyze***: The analyze action
- [ ] ***archive***: The archive action
+- [ ] ***management***: management metadata
### Build
@@ -511,15 +1011,20 @@ Schemes allows for more control than the convenience [Target Scheme](#target-sch
- [ ] **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 be implicit dependencies of this scheme. By default this is `true` if not set.
+- [ ] **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
- myTarget2: [test, run]
+ MyTarget: all
+ FooLib/FooTarget: [test, run]
parallelizeBuild: true
buildImplicitDependencies: true
```
@@ -535,6 +1040,19 @@ The different actions share some properties:
- [ ] **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
@@ -546,16 +1064,83 @@ Scheme run scripts added via **preActions** or **postActions**. They run before
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
-- [ ] **targets**: **[String]** - a list of targets to test
+- [ ] **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.
@@ -571,14 +1156,30 @@ schemes:
MyTarget2: [run, archive]
run:
config: prod-debug
- commandLineArguments: "--option value"
+ commandLineArguments:
+ "-MyEnabledArg": true
+ "-MyDisabledArg": false
environmentVariables:
RUN_ENV_VAR: VALUE
test:
config: prod-debug
- commandLineArguments: "--option testValue"
+ commandLineArguments:
+ "-MyEnabledArg": true
+ "-MyDisabledArg": false
gatherCoverageData: true
- targets: [Tester1, Tester2]
+ 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
@@ -592,3 +1193,105 @@ schemes:
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"]
+```
diff --git a/Docs/Usage.md b/Docs/Usage.md
index d87630bbd..390cf5875 100644
--- a/Docs/Usage.md
+++ b/Docs/Usage.md
@@ -4,9 +4,12 @@
- [Settings](#settings)
- [Setting Groups](#setting-groups)
- [xcconfig files](#xcconfig-files)
-- [Use dependencies](#use-dependencies)
- - [Cocoapods](#cocoapods)
+- [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
@@ -33,10 +36,10 @@ The values from [xcconfig files](#xcconfig-files) will then sit a level above th
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](#options)
+>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#settings) for more details.
+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:
@@ -55,7 +58,7 @@ targets:
```
### 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#setting-groups) for more details. Note that each setting group is also a full [Settings](ProjectSpec#settings) object, so you can reference other groups or define settings by config.
+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:
@@ -84,23 +87,23 @@ targets:
```
### xcodebuild environment variables
-You can also always overide any build settings on CI when building by passing specific build settings to xcodebuild like so:
+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 ...
```
-# Use dependencies
+# Dependencies
-Each target can declare one or more dependencies. See [Dependency](#dependency) for more info
+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.
+### 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 neccessary.
+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:
@@ -111,4 +114,110 @@ targets:
Framework:
dependencies:
- carthage: Alamofire
-```
\ No newline at end of file
+```
+
+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
+```
diff --git a/Makefile b/Makefile
index 931c6b90e..62d71313d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,47 +1,49 @@
TOOL_NAME = XcodeGen
export EXECUTABLE_NAME = xcodegen
-VERSION = 1.11.2
+VERSION = 2.45.4
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)
-RELEASE_TAR = $(REPO)/archive/$(VERSION).tar.gz
-SHA = $(shell curl -L -s $(RELEASE_TAR) | shasum -a 256 | sed 's/ .*//')
+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 update_brew release
+.PHONY: install build uninstall format_code release
install: build
mkdir -p $(PREFIX)/bin
- cp -f .build/release/$(EXECUTABLE_NAME) $(INSTALL_PATH)
+ cp -f $(EXECUTABLE_PATH) $(INSTALL_PATH)
mkdir -p $(SHARE_PATH)
cp -R $(CURRENT_PATH)/SettingPresets $(SHARE_PATH)/SettingPresets
build:
- swift build --disable-sandbox -c release -Xswiftc -static-stdlib
+ swift build $(SWIFT_BUILD_FLAGS)
uninstall:
rm -f $(INSTALL_PATH)
rm -rf $(SHARE_PATH)
format_code:
- swiftformat Tests --wraparguments beforefirst --stripunusedargs closure-only --header strip --disable blankLinesAtStartOfScope
- swiftformat Sources --wraparguments beforefirst --stripunusedargs closure-only --header strip --disable blankLinesAtStartOfScope
+ swiftformat .
-release: format_code
- sed -i '' 's|\(let version = try Version("\)\(.*\)\(")\)|\1$(VERSION)\3|' Sources/XcodeGen/main.swift
+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 bump_brew
+publish: archive
echo "published $(VERSION)"
-bump_brew:
- brew update
- brew bump-formula-pr --url=$(RELEASE_TAR) XcodeGen
-
archive: build
- ./scripts/archive.sh
+ ./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
diff --git a/Package.resolved b/Package.resolved
index bf25ace51..b0833fcb0 100644
--- a/Package.resolved
+++ b/Package.resolved
@@ -1,88 +1,95 @@
{
- "object": {
- "pins": [
- {
- "package": "AEXML",
- "repositoryURL": "https://github.com/tadija/AEXML",
- "state": {
- "branch": null,
- "revision": "54bb8ea6fb693dd3f92a89e5fcc19e199fdeedd0",
- "version": "4.3.3"
- }
- },
- {
- "package": "Commander",
- "repositoryURL": "https://github.com/kylef/Commander.git",
- "state": {
- "branch": null,
- "revision": "e5b50ad7b2e91eeb828393e89b03577b16be7db9",
- "version": "0.8.0"
- }
- },
- {
- "package": "JSONUtilities",
- "repositoryURL": "https://github.com/yonaskolb/JSONUtilities.git",
- "state": {
- "branch": null,
- "revision": "db238f4858ac2ac3fff228b5b71ce90488a6a9b3",
- "version": "4.1.0"
- }
- },
- {
- "package": "PathKit",
- "repositoryURL": "https://github.com/kylef/PathKit.git",
- "state": {
- "branch": null,
- "revision": "e2f5be30e4c8f531c9c1e8765aa7b71c0a45d7a0",
- "version": "0.9.2"
- }
- },
- {
- "package": "Rainbow",
- "repositoryURL": "https://github.com/onevcat/Rainbow.git",
- "state": {
- "branch": null,
- "revision": "797a68d0a642609424b08f11eb56974a54d5f6e2",
- "version": "3.1.4"
- }
- },
- {
- "package": "Spectre",
- "repositoryURL": "https://github.com/kylef/Spectre.git",
- "state": {
- "branch": null,
- "revision": "f14ff47f45642aa5703900980b014c2e9394b6e5",
- "version": "0.9.0"
- }
- },
- {
- "package": "SwiftShell",
- "repositoryURL": "https://github.com/kareman/SwiftShell",
- "state": {
- "branch": null,
- "revision": "beebe43c986d89ea5359ac3adcb42dac94e5e08a",
- "version": "4.1.2"
- }
- },
- {
- "package": "xcodeproj",
- "repositoryURL": "https://github.com/tuist/xcodeproj.git",
- "state": {
- "branch": null,
- "revision": "869ae3f417b0fb545e454ead36e2e13f157265a2",
- "version": "6.2.0"
- }
- },
- {
- "package": "Yams",
- "repositoryURL": "https://github.com/jpsim/Yams.git",
- "state": {
- "branch": null,
- "revision": "26ab35f50ea891e8edefcc9d975db2f6b67e1d68",
- "version": "1.0.1"
- }
+ "pins" : [
+ {
+ "identity" : "aexml",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/tadija/AEXML.git",
+ "state" : {
+ "revision" : "db806756c989760b35108146381535aec231092b",
+ "version" : "4.7.0"
}
- ]
- },
- "version": 1
+ },
+ {
+ "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
}
diff --git a/Package.swift b/Package.swift
index 9f44a5c4a..cded9e422 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,43 +1,93 @@
-// swift-tools-version:4.2
+// 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: "0.9.0"),
- .package(url: "https://github.com/kylef/Commander.git", from: "0.8.0"),
- .package(url: "https://github.com/jpsim/Yams.git", from: "1.0.0"),
- .package(url: "https://github.com/yonaskolb/JSONUtilities.git", from: "4.1.0"),
- .package(url: "https://github.com/kylef/Spectre.git", from: "0.9.0"),
- .package(url: "https://github.com/onevcat/Rainbow.git", from: "3.0.0"),
- .package(url: "https://github.com/tuist/xcodeproj.git", from: "6.2.0"),
+ .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: [
- .target(name: "XcodeGen", dependencies: [
- "XcodeGenKit",
- "Commander",
- "Rainbow",
+ .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",
- "JSONUtilities",
- "xcodeproj",
- "PathKit",
+ "ProjectSpec",
+ .product(name: "JSONUtilities", package: "JSONUtilities"),
+ .product(name: "XcodeProj", package: "XcodeProj"),
+ .product(name: "PathKit", package: "PathKit"),
+ "XcodeGenCore",
+ ], resources: [
+ .copy("SettingPresets")
]),
.target(name: "ProjectSpec", dependencies: [
- "JSONUtilities",
- "xcodeproj",
- "Yams",
+ .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",
- "Spectre",
- ])
+ "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",
+ ]),
]
)
diff --git a/README.md b/README.md
index 40ec7fbcd..68715addb 100644
--- a/README.md
+++ b/README.md
@@ -4,17 +4,17 @@
-
-
-
-
+
+
+
+
-
-
+
+
-
+
@@ -24,7 +24,7 @@ XcodeGen is a command line tool written in Swift that generates your Xcode proje
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` file from git, which means **no more merge conflicts**!
+- ✅ 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
@@ -36,12 +36,18 @@ The project spec is a YAML or JSON file that defines your targets, configuration
- ✅ Easily create **multi-platform** frameworks
- ✅ Integrate **Carthage** frameworks without any work
-Given a very simple project spec file like this:
+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
@@ -49,13 +55,18 @@ targets:
deploymentTarget: "10.0"
sources: [MyApp]
settings:
- debug:
- CUSTOM_BUILD_SETTING: my_debug_value
- release:
- CUSTOM_BUILD_SETTING: my_release_value
+ 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
@@ -65,7 +76,7 @@ A project would be created with 2 connected targets, with all the required confi
## Installing
-Make sure Xcode 9.3 is installed first.
+Make sure the latest stable (non-beta) version of Xcode is installed first.
### [Mint](https://github.com/yonaskolb/mint)
```sh
@@ -77,7 +88,7 @@ mint install yonaskolb/xcodegen
```shell
git clone https://github.com/yonaskolb/XcodeGen.git
cd XcodeGen
-make
+make install
```
### Homebrew
@@ -101,7 +112,7 @@ swift run xcodegen
Add the following to your Package.swift file's dependencies:
```swift
-.package(url: "https://github.com/yonaskolb/XcodeGen.git", from: "1.0.0"),
+.package(url: "https://github.com/yonaskolb/XcodeGen.git", from: "2.45.4"),
```
And then import wherever needed: `import XcodeGenKit`
@@ -111,16 +122,20 @@ And then import wherever needed: `import XcodeGenKit`
Simply run:
```shell
-xcodegen
+xcodegen generate
```
-This will look for a project spec in the current directory called `project.yml`
+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.
-Use `xcodegen --help` to see the list of options:
+Options:
-- **--spec**: An optional path to a `.yml` or `.json` project spec.
+- **--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
@@ -128,7 +143,7 @@ git clone https://github.com/yonaskolb/XcodeGen.git
cd XcodeGen
swift package generate-xcodeproj
```
-This use Swift Project Manager to create an `xcodeproj` file that you can open, edit and run in Xcode, which makes editing any code easier.
+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.
@@ -138,25 +153,32 @@ If you want to pass any required arguments when running in Xcode, you can edit t
- 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
-## Attributions
+## 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/carambalabs/xcodeproj)
+- [XcodeProj](https://github.com/tuist/XcodeProj)
- [JSONUtilities](https://github.com/yonaskolb/JSONUtilities)
- [Spectre](https://github.com/kylef/Spectre)
- [PathKit](https://github.com/kylef/PathKit)
-- [Commander](https://github.com/kylef/Commander)
- [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/jcampbell05/xcake)
+- [Xcake](https://github.com/igor-makarov/xcake)
- [CocoaPods Xcodeproj](https://github.com/CocoaPods/Xcodeproj)
## Contributions
-Pull requests and issues are welcome
+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/links/0)[](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/links/1)[](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/links/2)[](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/links/3)[](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/links/4)[](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/links/5)[](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/links/6)[](https://sourcerer.io/fame/yonaskolb/yonaskolb/XcodeGen/links/7)
## License
diff --git a/RELEASE.md b/RELEASE.md
index d89acfa31..c3e6f549e 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -1,12 +1,10 @@
# The release process for XcodeGen
1. Make sure `CHANGELOG.md` is up to date:
- - All relevant entries have been added with the PR link and author
+ - 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`
- - The `Commits` link is at the bottom of the release
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
-1. Run `make archive` and upload `xcodegen.zip` to the github release
-1. Run `make bump_brew` which will open a PR on homebrew core
+1. Create release from tag on GitHub using the version number and relevant changelog contents, attaching `xcodegen.zip` and `xcodegen.artifactbundle.zip`
diff --git a/SettingPresets/Configs/debug.yml b/SettingPresets/Configs/debug.yml
index 5b0b75213..6c3e3c20f 100644
--- a/SettingPresets/Configs/debug.yml
+++ b/SettingPresets/Configs/debug.yml
@@ -6,7 +6,7 @@ ENABLE_TESTABILITY: YES
GCC_DYNAMIC_NO_PIC: NO
GCC_OPTIMIZATION_LEVEL: '0'
GCC_PREPROCESSOR_DEFINITIONS: ["$(inherited)", "DEBUG=1"]
-MTL_ENABLE_DEBUG_INFO: YES
+MTL_ENABLE_DEBUG_INFO: INCLUDE_SOURCE
ONLY_ACTIVE_ARCH: YES
# Swift Settings
diff --git a/SettingPresets/Configs/release.yml b/SettingPresets/Configs/release.yml
index b32b0c585..7a14baa93 100644
--- a/SettingPresets/Configs/release.yml
+++ b/SettingPresets/Configs/release.yml
@@ -3,7 +3,8 @@
# /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
-VALIDATE_PRODUCT: YES
+MTL_ENABLE_DEBUG_INFO: NO
# Swift Settings
-SWIFT_OPTIMIZATION_LEVEL: -Owholemodule
+SWIFT_COMPILATION_MODE: wholemodule
+SWIFT_OPTIMIZATION_LEVEL: -O
diff --git a/SettingPresets/Platforms/iOS.yml b/SettingPresets/Platforms/iOS.yml
index 088cbd64a..bb337dc87 100644
--- a/SettingPresets/Platforms/iOS.yml
+++ b/SettingPresets/Platforms/iOS.yml
@@ -1,3 +1,3 @@
-LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/Frameworks"
+LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks"]
SDKROOT: iphoneos
TARGETED_DEVICE_FAMILY: '1,2'
diff --git a/SettingPresets/Platforms/macOS.yml b/SettingPresets/Platforms/macOS.yml
index 908145b4c..062dc9a83 100644
--- a/SettingPresets/Platforms/macOS.yml
+++ b/SettingPresets/Platforms/macOS.yml
@@ -1,3 +1,3 @@
-LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/../Frameworks"
+LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/../Frameworks"]
SDKROOT: macosx
COMBINE_HIDPI_IMAGES: 'YES'
diff --git a/SettingPresets/Platforms/tvOS.yml b/SettingPresets/Platforms/tvOS.yml
index 810ce3ab0..40491c07f 100644
--- a/SettingPresets/Platforms/tvOS.yml
+++ b/SettingPresets/Platforms/tvOS.yml
@@ -1,3 +1,3 @@
-TARGETED_DEVICE_FAMILY: 3
-LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/Frameworks"
+LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks"]
SDKROOT: appletvos
+TARGETED_DEVICE_FAMILY: 3
diff --git a/SettingPresets/Platforms/visionOS.yml b/SettingPresets/Platforms/visionOS.yml
new file mode 100644
index 000000000..d969bf799
--- /dev/null
+++ b/SettingPresets/Platforms/visionOS.yml
@@ -0,0 +1,3 @@
+LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks"]
+SDKROOT: xros
+TARGETED_DEVICE_FAMILY: 7
diff --git a/SettingPresets/Product_Platform/app-extension_macOS.yml b/SettingPresets/Product_Platform/app-extension_macOS.yml
new file mode 100644
index 000000000..65b36ca7d
--- /dev/null
+++ b/SettingPresets/Product_Platform/app-extension_macOS.yml
@@ -0,0 +1 @@
+LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/../Frameworks", "@executable_path/../../../../Frameworks"]
diff --git a/SettingPresets/Product_Platform/application_visionOS.yml b/SettingPresets/Product_Platform/application_visionOS.yml
new file mode 100644
index 000000000..e538b2380
--- /dev/null
+++ b/SettingPresets/Product_Platform/application_visionOS.yml
@@ -0,0 +1 @@
+ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
diff --git a/SettingPresets/Product_Platform/bundle.unit-test_macOS.yml b/SettingPresets/Product_Platform/bundle.unit-test_macOS.yml
index aa36c95c7..dbbe27c0b 100644
--- a/SettingPresets/Product_Platform/bundle.unit-test_macOS.yml
+++ b/SettingPresets/Product_Platform/bundle.unit-test_macOS.yml
@@ -1 +1 @@
-LD_RUNPATH_SEARCH_PATHS: $(inherited) @executable_path/../Frameworks @loader_path/../Frameworks
+LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks"]
diff --git a/SettingPresets/Products/app-extension.intents-service.yml b/SettingPresets/Products/app-extension.intents-service.yml
new file mode 100644
index 000000000..e7cc3c373
--- /dev/null
+++ b/SettingPresets/Products/app-extension.intents-service.yml
@@ -0,0 +1 @@
+LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", "@executable_path/../../../../Frameworks"]
diff --git a/SettingPresets/Products/app-extension.messages.yml b/SettingPresets/Products/app-extension.messages.yml
index 4b0d2ed61..6d4b8a6fb 100644
--- a/SettingPresets/Products/app-extension.messages.yml
+++ b/SettingPresets/Products/app-extension.messages.yml
@@ -1,2 +1,2 @@
ASSETCATALOG_COMPILER_APPICON_NAME: iMessage App Icon
-LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"
+LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks"]
diff --git a/SettingPresets/Products/app-extension.yml b/SettingPresets/Products/app-extension.yml
index a075856d9..dfec667e7 100644
--- a/SettingPresets/Products/app-extension.yml
+++ b/SettingPresets/Products/app-extension.yml
@@ -1 +1 @@
-LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"
+LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks"]
diff --git a/SettingPresets/Products/application.watchapp2.yml b/SettingPresets/Products/application.watchapp2.yml
deleted file mode 100644
index 19d40c814..000000000
--- a/SettingPresets/Products/application.watchapp2.yml
+++ /dev/null
@@ -1 +0,0 @@
-ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES: YES
diff --git a/SettingPresets/Products/application.yml b/SettingPresets/Products/application.yml
deleted file mode 100644
index 19d40c814..000000000
--- a/SettingPresets/Products/application.yml
+++ /dev/null
@@ -1 +0,0 @@
-ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES: YES
diff --git a/SettingPresets/Products/bundle.ui-testing.yml b/SettingPresets/Products/bundle.ui-testing.yml
index 5b7271f2d..bfa317f14 100644
--- a/SettingPresets/Products/bundle.ui-testing.yml
+++ b/SettingPresets/Products/bundle.ui-testing.yml
@@ -1,2 +1,2 @@
BUNDLE_LOADER: $(TEST_HOST)
-LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"
+LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks"]
diff --git a/SettingPresets/Products/bundle.unit-test.yml b/SettingPresets/Products/bundle.unit-test.yml
index 5b7271f2d..bfa317f14 100644
--- a/SettingPresets/Products/bundle.unit-test.yml
+++ b/SettingPresets/Products/bundle.unit-test.yml
@@ -1,2 +1,2 @@
BUNDLE_LOADER: $(TEST_HOST)
-LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"
+LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks"]
diff --git a/SettingPresets/Products/framework.static.yml b/SettingPresets/Products/framework.static.yml
new file mode 100644
index 000000000..df100760b
--- /dev/null
+++ b/SettingPresets/Products/framework.static.yml
@@ -0,0 +1,9 @@
+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'
diff --git a/SettingPresets/Products/tv-app-extension.yml b/SettingPresets/Products/tv-app-extension.yml
index 72261c41c..b27996cbe 100644
--- a/SettingPresets/Products/tv-app-extension.yml
+++ b/SettingPresets/Products/tv-app-extension.yml
@@ -1,2 +1,2 @@
SKIP_INSTALL: 'YES'
-LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"
+LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks"]
diff --git a/SettingPresets/Products/watchkit2-extension.yml b/SettingPresets/Products/watchkit2-extension.yml
index cb41cd362..f77f27a52 100644
--- a/SettingPresets/Products/watchkit2-extension.yml
+++ b/SettingPresets/Products/watchkit2-extension.yml
@@ -1,2 +1,2 @@
-LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"
+LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks"]
ASSETCATALOG_COMPILER_COMPLICATION_NAME: Complication
diff --git a/SettingPresets/SupportedDestinations/iOS.yml b/SettingPresets/SupportedDestinations/iOS.yml
new file mode 100644
index 000000000..91bfa0b47
--- /dev/null
+++ b/SettingPresets/SupportedDestinations/iOS.yml
@@ -0,0 +1,5 @@
+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
diff --git a/SettingPresets/SupportedDestinations/macCatalyst.yml b/SettingPresets/SupportedDestinations/macCatalyst.yml
new file mode 100644
index 000000000..953af2680
--- /dev/null
+++ b/SettingPresets/SupportedDestinations/macCatalyst.yml
@@ -0,0 +1,2 @@
+SUPPORTS_MACCATALYST: YES
+SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD: NO
diff --git a/SettingPresets/SupportedDestinations/macOS.yml b/SettingPresets/SupportedDestinations/macOS.yml
new file mode 100644
index 000000000..e59a8f69d
--- /dev/null
+++ b/SettingPresets/SupportedDestinations/macOS.yml
@@ -0,0 +1,3 @@
+SUPPORTED_PLATFORMS: macosx
+SUPPORTS_MACCATALYST: NO
+SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD: NO
diff --git a/SettingPresets/SupportedDestinations/tvOS.yml b/SettingPresets/SupportedDestinations/tvOS.yml
new file mode 100644
index 000000000..a40ce2666
--- /dev/null
+++ b/SettingPresets/SupportedDestinations/tvOS.yml
@@ -0,0 +1,2 @@
+SUPPORTED_PLATFORMS: appletvos appletvsimulator
+TARGETED_DEVICE_FAMILY: '3'
diff --git a/SettingPresets/SupportedDestinations/visionOS.yml b/SettingPresets/SupportedDestinations/visionOS.yml
new file mode 100644
index 000000000..1353d7191
--- /dev/null
+++ b/SettingPresets/SupportedDestinations/visionOS.yml
@@ -0,0 +1,3 @@
+SUPPORTED_PLATFORMS: xros xrsimulator
+TARGETED_DEVICE_FAMILY: '7'
+SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD: NO
diff --git a/SettingPresets/SupportedDestinations/watchOS.yml b/SettingPresets/SupportedDestinations/watchOS.yml
new file mode 100644
index 000000000..95ba0836d
--- /dev/null
+++ b/SettingPresets/SupportedDestinations/watchOS.yml
@@ -0,0 +1,2 @@
+SUPPORTED_PLATFORMS: watchos watchsimulator
+TARGETED_DEVICE_FAMILY: '4'
diff --git a/SettingPresets/base.yml b/SettingPresets/base.yml
index 3722c640c..d7533a841 100644
--- a/SettingPresets/base.yml
+++ b/SettingPresets/base.yml
@@ -8,6 +8,7 @@ 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
@@ -23,6 +24,7 @@ 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
@@ -39,7 +41,10 @@ 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
-# Swift Settings
+# Target Settings
PRODUCT_NAME: $(TARGET_NAME)
-SWIFT_VERSION: '4.0'
+
+# Swift Settings
+SWIFT_VERSION: '5.0'
diff --git a/Sources/ProjectSpec/AggregateTarget.swift b/Sources/ProjectSpec/AggregateTarget.swift
index 45fcba146..9bea7098c 100644
--- a/Sources/ProjectSpec/AggregateTarget.swift
+++ b/Sources/ProjectSpec/AggregateTarget.swift
@@ -1,11 +1,14 @@
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]
@@ -16,6 +19,7 @@ public struct AggregateTarget: ProjectTarget {
settings: Settings = .empty,
configFiles: [String: String] = [:],
buildScripts: [BuildScript] = [],
+ buildToolPlugins: [BuildToolPlugin] = [],
scheme: TargetScheme? = nil,
attributes: [String: Any] = [:]
) {
@@ -24,6 +28,7 @@ public struct AggregateTarget: ProjectTarget {
self.settings = settings
self.configFiles = configFiles
self.buildScripts = buildScripts
+ self.buildToolPlugins = buildToolPlugins
self.scheme = scheme
self.attributes = attributes
}
@@ -32,18 +37,19 @@ public struct AggregateTarget: ProjectTarget {
extension AggregateTarget: CustomStringConvertible {
public var description: String {
- return "\(name)\(targets.isEmpty ? "" : ": \(targets.joined(separator: ", "))")"
+ "\(name)\(targets.isEmpty ? "" : ": \(targets.joined(separator: ", "))")"
}
}
extension AggregateTarget: Equatable {
public static func == (lhs: AggregateTarget, rhs: AggregateTarget) -> Bool {
- return lhs.name == rhs.name &&
+ 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)
}
@@ -54,10 +60,37 @@ extension AggregateTarget: NamedJSONDictionaryConvertible {
public init(name: String, jsonDictionary: JSONDictionary) throws {
self.name = jsonDictionary.json(atKeyPath: "name") ?? name
targets = jsonDictionary.json(atKeyPath: "targets") ?? []
- settings = jsonDictionary.json(atKeyPath: "settings") ?? .empty
+ 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),
+ ]),
+ ]
+ }
+}
diff --git a/Sources/ProjectSpec/Array+Extension.swift b/Sources/ProjectSpec/Array+Extension.swift
new file mode 100644
index 000000000..907d23589
--- /dev/null
+++ b/Sources/ProjectSpec/Array+Extension.swift
@@ -0,0 +1,9 @@
+extension Array where Element == [String: Any?] {
+ func removingEmptyArraysDictionariesAndNils() -> [[String: Any]] {
+ var new: [[String: Any]] = []
+ forEach { element in
+ new.append(element.removingEmptyArraysDictionariesAndNils())
+ }
+ return new
+ }
+}
diff --git a/Sources/ProjectSpec/Breakpoint.swift b/Sources/ProjectSpec/Breakpoint.swift
new file mode 100644
index 000000000..7877a6956
--- /dev/null
+++ b/Sources/ProjectSpec/Breakpoint.swift
@@ -0,0 +1,264 @@
+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 = []
+ }
+ }
+}
diff --git a/Sources/ProjectSpec/BuildPhaseSpec.swift b/Sources/ProjectSpec/BuildPhaseSpec.swift
new file mode 100644
index 000000000..6227ced02
--- /dev/null
+++ b/Sources/ProjectSpec/BuildPhaseSpec.swift
@@ -0,0 +1,154 @@
+//
+// 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,
+ ]
+ }
+}
diff --git a/Sources/ProjectSpec/BuildRule.swift b/Sources/ProjectSpec/BuildRule.swift
index 442dda248..f5fefc013 100644
--- a/Sources/ProjectSpec/BuildRule.swift
+++ b/Sources/ProjectSpec/BuildRule.swift
@@ -5,6 +5,7 @@ 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)
@@ -49,13 +50,22 @@ public struct BuildRule: Equatable {
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] = []) {
+ 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
}
}
@@ -78,5 +88,36 @@ extension BuildRule: JSONObjectConvertible {
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
}
}
diff --git a/Sources/ProjectSpec/BuildScript.swift b/Sources/ProjectSpec/BuildScript.swift
index 30b634046..f22211bc4 100644
--- a/Sources/ProjectSpec/BuildScript.swift
+++ b/Sources/ProjectSpec/BuildScript.swift
@@ -2,14 +2,21 @@ 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)
@@ -21,17 +28,25 @@ public struct BuildScript: Equatable {
name: String? = nil,
inputFiles: [String] = [],
outputFiles: [String] = [],
+ inputFileLists: [String] = [],
+ outputFileLists: [String] = [],
shell: String? = nil,
- runOnlyWhenInstalling: Bool = false,
- showEnvVars: Bool = true
+ 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
}
}
@@ -41,6 +56,8 @@ extension BuildScript: JSONObjectConvertible {
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)
@@ -49,7 +66,53 @@ extension BuildScript: JSONObjectConvertible {
script = .path(path)
}
shell = jsonDictionary.json(atKeyPath: "shell")
- runOnlyWhenInstalling = jsonDictionary.json(atKeyPath: "runOnlyWhenInstalling") ?? false
- showEnvVars = jsonDictionary.json(atKeyPath: "showEnvVars") ?? true
+ 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"),
+ ]
}
}
diff --git a/Sources/ProjectSpec/BuildSettingsExtractor.swift b/Sources/ProjectSpec/BuildSettingsExtractor.swift
new file mode 100644
index 000000000..1dfeb93de
--- /dev/null
+++ b/Sources/ProjectSpec/BuildSettingsExtractor.swift
@@ -0,0 +1,37 @@
+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") ?? [:]
+ }
+ }
+}
diff --git a/Sources/ProjectSpec/BuildToolPlugin.swift b/Sources/ProjectSpec/BuildToolPlugin.swift
new file mode 100644
index 000000000..76de6f0d8
--- /dev/null
+++ b/Sources/ProjectSpec/BuildToolPlugin.swift
@@ -0,0 +1,58 @@
+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
+ ]
+ }
+}
diff --git a/Sources/ProjectSpec/CacheFile.swift b/Sources/ProjectSpec/CacheFile.swift
new file mode 100644
index 000000000..95e87014f
--- /dev/null
+++ b/Sources/ProjectSpec/CacheFile.swift
@@ -0,0 +1,33 @@
+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)"
+
+ """
+ }
+}
diff --git a/Sources/ProjectSpec/Config.swift b/Sources/ProjectSpec/Config.swift
index 2e343e578..49e5d6d2d 100644
--- a/Sources/ProjectSpec/Config.swift
+++ b/Sources/ProjectSpec/Config.swift
@@ -1,7 +1,7 @@
import Foundation
import JSONUtilities
-public struct Config: Equatable {
+public struct Config: Hashable {
public var name: String
public var type: ConfigType?
@@ -10,10 +10,32 @@ public struct Config: Equatable {
self.type = type
}
- public static var defaultConfigs: [Config] = [Config(name: "Debug", type: .debug), Config(name: "Release", type: .release)]
+ public static var defaultConfigs: [Config] = [Config(name: ConfigType.debug.name, type: .debug), Config(name: ConfigType.release.name, type: .release)]
}
-public enum ConfigType: String {
+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) }
+ }
+}
+
diff --git a/Sources/ProjectSpec/Decoding.swift b/Sources/ProjectSpec/Decoding.swift
index 6d7879434..e193a7f10 100644
--- a/Sources/ProjectSpec/Decoding.swift
+++ b/Sources/ProjectSpec/Decoding.swift
@@ -4,18 +4,37 @@ import PathKit
import Yams
extension Dictionary where Key: JSONKey {
-
- public func json(atKeyPath keyPath: JSONUtilities.KeyPath, invalidItemBehaviour: InvalidItemBehaviour = .remove) throws -> [T] {
+ 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 []
}
- 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)
+ 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
}
- return items
}
public func json(atKeyPath keyPath: JSONUtilities.KeyPath, invalidItemBehaviour: InvalidItemBehaviour = .remove) throws -> [T] {
@@ -31,6 +50,14 @@ extension Dictionary where Key: JSONKey {
}
}
+private final class BufferWrapper: @unchecked Sendable {
+ var buffer: UnsafeMutableBufferPointer
+
+ init(buffer: UnsafeMutableBufferPointer) {
+ self.buffer = buffer
+ }
+}
+
public protocol NamedJSONDictionaryConvertible {
init(name: String, jsonDictionary: JSONDictionary) throws
diff --git a/Sources/ProjectSpec/Dependency.swift b/Sources/ProjectSpec/Dependency.swift
index 1e01b5f50..f7a72c0a9 100644
--- a/Sources/ProjectSpec/Dependency.swift
+++ b/Sources/ProjectSpec/Dependency.swift
@@ -2,15 +2,23 @@ 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 = true
+ public var removeHeaders: Bool = removeHeadersDefault
public var link: Bool?
- public var implicit: Bool = false
- public var weakLink: Bool = false
+ 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,
@@ -18,8 +26,12 @@ public struct Dependency: Equatable {
embed: Bool? = nil,
codeSign: Bool? = nil,
link: Bool? = nil,
- implicit: Bool = false,
- weakLink: Bool = false
+ 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
@@ -28,12 +40,53 @@ public struct Dependency: Equatable {
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 {
+ public enum DependencyType: Hashable {
case target
case framework
- case carthage
+ 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)
}
}
@@ -47,8 +100,28 @@ extension Dependency: JSONObjectConvertible {
type = .framework
reference = framework
} else if let carthage: String = jsonDictionary.json(atKeyPath: "carthage") {
- type = .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)
}
@@ -66,5 +139,76 @@ extension Dependency: JSONObjectConvertible {
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"),
+ ]
}
}
diff --git a/Sources/ProjectSpec/DeploymentTarget.swift b/Sources/ProjectSpec/DeploymentTarget.swift
index e8640d138..a22f25ce0 100644
--- a/Sources/ProjectSpec/DeploymentTarget.swift
+++ b/Sources/ProjectSpec/DeploymentTarget.swift
@@ -1,5 +1,6 @@
import Foundation
import JSONUtilities
+import Version
public struct DeploymentTarget: Equatable {
@@ -7,25 +8,30 @@ public struct DeploymentTarget: Equatable {
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
+ 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
}
}
}
@@ -34,10 +40,23 @@ 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"
}
}
}
@@ -46,7 +65,7 @@ extension Version {
/// doesn't print patch if 0
public var deploymentTarget: String {
- return "\(major).\(minor)\(patch > 0 ? ".\(patch)" : "")"
+ "\(major).\(minor)\(patch > 0 ? ".\(patch)" : "")"
}
}
@@ -56,9 +75,9 @@ extension DeploymentTarget: JSONObjectConvertible {
func parseVersion(_ platform: String) throws -> Version? {
if let string: String = jsonDictionary.json(atKeyPath: .key(platform)) {
- return try Version(string)
+ return try Version.parse(string)
} else if let double: Double = jsonDictionary.json(atKeyPath: .key(platform)) {
- return try Version(double)
+ return try Version.parse(double)
} else {
return nil
}
@@ -67,5 +86,18 @@ extension DeploymentTarget: JSONObjectConvertible {
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,
+ ]
}
}
diff --git a/Sources/ProjectSpec/Dictionary+Extension.swift b/Sources/ProjectSpec/Dictionary+Extension.swift
new file mode 100644
index 000000000..ed2939b46
--- /dev/null
+++ b/Sources/ProjectSpec/Dictionary+Extension.swift
@@ -0,0 +1,33 @@
+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
+ }
+}
diff --git a/Sources/ProjectSpec/Encoding.swift b/Sources/ProjectSpec/Encoding.swift
new file mode 100644
index 000000000..6934f0b2d
--- /dev/null
+++ b/Sources/ProjectSpec/Encoding.swift
@@ -0,0 +1,7 @@
+import Foundation
+import JSONUtilities
+
+public protocol JSONEncodable {
+ // returns JSONDictionary or JSONArray or JSONRawType or nil
+ func toJSONValue() -> Any
+}
diff --git a/Sources/ProjectSpec/FileType.swift b/Sources/ProjectSpec/FileType.swift
new file mode 100644
index 000000000..5e5a6575f
--- /dev/null
+++ b/Sources/ProjectSpec/FileType.swift
@@ -0,0 +1,126 @@
+//
+// 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),
+ ]
+}
diff --git a/Sources/ProjectSpec/GroupOrdering.swift b/Sources/ProjectSpec/GroupOrdering.swift
new file mode 100644
index 000000000..634d944c1
--- /dev/null
+++ b/Sources/ProjectSpec/GroupOrdering.swift
@@ -0,0 +1,32 @@
+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") ?? []
+ }
+
+}
diff --git a/Sources/ProjectSpec/Linkage.swift b/Sources/ProjectSpec/Linkage.swift
index 80409c4f3..e42cac5cf 100644
--- a/Sources/ProjectSpec/Linkage.swift
+++ b/Sources/ProjectSpec/Linkage.swift
@@ -1,4 +1,5 @@
import Foundation
+import XcodeProj
public enum Linkage {
case dynamic
@@ -15,9 +16,13 @@ extension Target {
.application,
.bundle,
.commandLineTool,
+ .instrumentsPackage,
+ .intentsServiceExtension,
.messagesApplication,
.messagesExtension,
+ .metalLibrary,
.ocUnitTestBundle,
+ .onDemandInstallCapableApplication,
.stickerPack,
.tvExtension,
.uiTestBundle,
@@ -25,17 +30,32 @@ extension Target {
.watchApp,
.watchExtension,
.watch2App,
+ .watch2AppContainer,
.watch2Extension,
.xcodeExtension,
- .xpcService:
+ .xpcService,
+ .systemExtension,
+ .driverExtension,
+ .extensionKitExtension:
return .none
- case .framework:
- // TODO: This should check `MACH_O_TYPE` in case this is a "Static Framework"
- return .dynamic
+ 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:
+ case .staticLibrary, .staticFramework:
return .static
}
}
}
+
+private extension BuildSettings {
+
+ var machOType: String? {
+ self["MACH_O_TYPE"]?.stringValue
+ }
+}
diff --git a/Sources/ProjectSpec/NSRegularExpressionExtensions.swift b/Sources/ProjectSpec/NSRegularExpressionExtensions.swift
new file mode 100644
index 000000000..5ca47b7cd
--- /dev/null
+++ b/Sources/ProjectSpec/NSRegularExpressionExtensions.swift
@@ -0,0 +1,10 @@
+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
+ }
+
+}
diff --git a/Sources/ProjectSpec/PathContainer.swift b/Sources/ProjectSpec/PathContainer.swift
new file mode 100644
index 000000000..4ffdbe8ce
--- /dev/null
+++ b/Sources/ProjectSpec/PathContainer.swift
@@ -0,0 +1,62 @@
+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
+ }
+}
diff --git a/Sources/ProjectSpec/Platform.swift b/Sources/ProjectSpec/Platform.swift
index 6bba35f02..5f7613b41 100644
--- a/Sources/ProjectSpec/Platform.swift
+++ b/Sources/ProjectSpec/Platform.swift
@@ -1,18 +1,10 @@
import Foundation
-public enum Platform: String {
+public enum Platform: String, Hashable, CaseIterable {
+ case auto
case iOS
- case watchOS
case tvOS
case macOS
- public var carthageDirectoryName: String {
- switch self {
- case .macOS:
- return "Mac"
- default:
- return rawValue
- }
- }
-
- public static var all: [Platform] = [.iOS, .tvOS, .watchOS, .macOS]
+ case watchOS
+ case visionOS
}
diff --git a/Sources/ProjectSpec/Plist.swift b/Sources/ProjectSpec/Plist.swift
new file mode 100644
index 000000000..124b4bfbf
--- /dev/null
+++ b/Sources/ProjectSpec/Plist.swift
@@ -0,0 +1,44 @@
+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"),
+ ]
+ }
+}
diff --git a/Sources/ProjectSpec/Project.swift b/Sources/ProjectSpec/Project.swift
index 4526140de..e872a27bf 100644
--- a/Sources/ProjectSpec/Project.swift
+++ b/Sources/ProjectSpec/Project.swift
@@ -19,21 +19,30 @@ public struct Project: BuildSettingsContainer {
}
}
+ 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,
+ basePath: Path = "",
name: String,
configs: [Config] = Config.defaultConfigs,
targets: [Target] = [],
@@ -41,10 +50,13 @@ public struct Project: BuildSettingsContainer {
settings: Settings = .empty,
settingGroups: [String: Settings] = [:],
schemes: [Scheme] = [],
+ breakpoints: [Breakpoint] = [],
+ packages: [String: SwiftPackage] = [:],
options: SpecOptions = SpecOptions(),
fileGroups: [String] = [],
configFiles: [String: String] = [:],
- attributes: [String: Any] = [:]
+ attributes: [String: Any] = [:],
+ projectReferences: [ProjectReference] = []
) {
self.basePath = basePath
self.name = name
@@ -56,30 +68,42 @@ public struct Project: BuildSettingsContainer {
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? {
- return targetsMap[targetName]
+ targetsMap[targetName]
+ }
+
+ public func getPackage(_ packageName: String) -> SwiftPackage? {
+ packages[packageName]
}
public func getAggregateTarget(_ targetName: String) -> AggregateTarget? {
- return aggregateTargetsMap[targetName]
+ aggregateTargetsMap[targetName]
}
public func getProjectTarget(_ targetName: String) -> ProjectTarget? {
- return targetsMap[targetName] ?? aggregateTargetsMap[targetName]
+ targetsMap[targetName] ?? aggregateTargetsMap[targetName]
}
public func getConfig(_ configName: String) -> Config? {
- return configs.first { $0.name == configName }
+ configs.first { $0.name == configName }
}
- public var projectPath: Path {
- return basePath + "\(name).xcodeproj"
+ public var defaultProjectPath: Path {
+ basePath + "\(name).xcodeproj"
}
}
@@ -89,13 +113,12 @@ extension Project: CustomDebugStringConvertible {
var string = "Name: \(name)"
let indent = " "
if !include.isEmpty {
- string += "\nInclude:\n\(indent)" + include.map { "📄 \($0)" }.joined(separator: "\n\(indent)")
+ string += "\nInclude:\n\(indent)" + include.map { $0.description }.joined(separator: "\n\(indent)")
}
if !settingGroups.isEmpty {
string += "\nSetting Groups:\n\(indent)" + settingGroups.keys
.sorted()
- .map { "⚙️ \($0)" }
.joined(separator: "\n\(indent)")
}
@@ -105,6 +128,10 @@ extension Project: CustomDebugStringConvertible {
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
}
@@ -113,39 +140,74 @@ extension Project: CustomDebugStringConvertible {
extension Project: Equatable {
public static func == (lhs: Project, rhs: Project) -> Bool {
- return lhs.name == rhs.name &&
+ 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(basePath: Path, jsonDictionary: JSONDictionary) throws {
+ 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 = try Project.resolveProject(jsonDictionary: jsonDictionary)
+
+ let jsonDictionary = Project.resolveProject(jsonDictionary: jsonDictionary)
+ let buildSettingsParser = BuildSettingsParser(jsonDictionary: jsonDictionary)
+
name = try jsonDictionary.json(atKeyPath: "name")
- settings = jsonDictionary.json(atKeyPath: "settings") ?? .empty
- settingGroups = jsonDictionary.json(atKeyPath: "settingGroups")
- ?? jsonDictionary.json(atKeyPath: "settingPresets") ?? [:]
+
+ 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").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 {
@@ -153,12 +215,121 @@ extension Project {
}
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) throws -> JSONDictionary {
+ static func resolveProject(jsonDictionary: JSONDictionary) -> JSONDictionary {
var jsonDictionary = jsonDictionary
- jsonDictionary = try Target.resolveTargetTemplates(jsonDictionary: jsonDictionary)
- jsonDictionary = try Target.resolveMultiplatformTargets(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
+ }
+}
diff --git a/Sources/ProjectSpec/ProjectReference.swift b/Sources/ProjectSpec/ProjectReference.swift
new file mode 100644
index 000000000..9ddc29306
--- /dev/null
+++ b/Sources/ProjectSpec/ProjectReference.swift
@@ -0,0 +1,38 @@
+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,
+ ]
+ }
+}
diff --git a/Sources/ProjectSpec/ProjectTarget.swift b/Sources/ProjectSpec/ProjectTarget.swift
index c864f9906..6539f3e84 100644
--- a/Sources/ProjectSpec/ProjectTarget.swift
+++ b/Sources/ProjectSpec/ProjectTarget.swift
@@ -1,9 +1,12 @@
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 }
}
@@ -11,13 +14,13 @@ public protocol ProjectTarget: BuildSettingsContainer {
extension Target {
public var buildScripts: [BuildScript] {
- return prebuildScripts + postbuildScripts
+ preBuildScripts + postCompileScripts + postBuildScripts
}
}
extension Project {
public var projectTargets: [ProjectTarget] {
- return targets.map { $0 as ProjectTarget } + aggregateTargets.map { $0 as ProjectTarget }
+ targets.map { $0 as ProjectTarget } + aggregateTargets.map { $0 as ProjectTarget }
}
}
diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift
index 1bd83431f..a19951fe8 100644
--- a/Sources/ProjectSpec/Scheme.swift
+++ b/Sources/ProjectSpec/Scheme.swift
@@ -1,6 +1,7 @@
import Foundation
import JSONUtilities
-import xcodeproj
+import PathKit
+import XcodeProj
public typealias BuildType = XCScheme.BuildAction.Entry.BuildFor
@@ -13,6 +14,7 @@ public struct Scheme: Equatable {
public var analyze: Analyze?
public var test: Test?
public var profile: Profile?
+ public var management: Management?
public init(
name: String,
@@ -21,7 +23,8 @@ public struct Scheme: Equatable {
test: Test? = nil,
profile: Profile? = nil,
analyze: Analyze? = nil,
- archive: Archive? = nil
+ archive: Archive? = nil,
+ management: Management? = nil
) {
self.name = name
self.build = build
@@ -30,89 +33,324 @@ public struct Scheme: Equatable {
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 init(name: String, script: String, settingsTarget: String? = nil) {
+ 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 = true,
- buildImplicitDependencies: Bool = true,
+ parallelizeBuild: Bool = parallelizeBuildDefault,
+ buildImplicitDependencies: Bool = buildImplicitDependenciesDefault,
preActions: [ExecutionAction] = [],
- postActions: [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,
+ config: String? = nil,
+ executable: String? = nil,
commandLineArguments: [String: Bool] = [:],
preActions: [ExecutionAction] = [],
postActions: [ExecutionAction] = [],
- environmentVariables: [XCScheme.EnvironmentVariable] = []
+ 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: [String]
+ 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,
- gatherCoverageData: Bool = false,
+ 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: [String] = [],
+ targets: [TestTarget] = [],
preActions: [ExecutionAction] = [],
postActions: [ExecutionAction] = [],
- environmentVariables: [XCScheme.EnvironmentVariable] = []
+ 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 {
- return commandLineArguments.isEmpty && environmentVariables.isEmpty
+ commandLineArguments.isEmpty && environmentVariables.isEmpty
}
}
@@ -129,35 +367,41 @@ public struct Scheme: Equatable {
public var preActions: [ExecutionAction]
public var postActions: [ExecutionAction]
public var environmentVariables: [XCScheme.EnvironmentVariable]
+ public var askForAppToLaunch: Bool?
+
public init(
- config: String,
+ config: String? = nil,
commandLineArguments: [String: Bool] = [:],
preActions: [ExecutionAction] = [],
postActions: [ExecutionAction] = [],
- environmentVariables: [XCScheme.EnvironmentVariable] = []
+ 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 {
- return commandLineArguments.isEmpty && environmentVariables.isEmpty
+ 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,
+ config: String? = nil,
customArchiveName: String? = nil,
- revealArchiveInOrganizer: Bool = true,
+ revealArchiveInOrganizer: Bool = revealArchiveInOrganizerDefault,
preActions: [ExecutionAction] = [],
postActions: [ExecutionAction] = []
) {
@@ -169,17 +413,28 @@ public struct Scheme: Equatable {
}
}
- public struct BuildTarget: Equatable {
- public var target: String
+ public struct BuildTarget: Equatable, Hashable {
+ public var target: TestableTargetReference
public var buildTypes: [BuildType]
- public init(target: String, buildTypes: [BuildType] = BuildType.all) {
+ 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 }
}
@@ -190,6 +445,69 @@ extension Scheme.ExecutionAction: JSONObjectConvertible {
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
}
}
@@ -201,6 +519,131 @@ extension Scheme.Run: JSONObjectConvertible {
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),
+ ]
}
}
@@ -208,12 +651,169 @@ extension Scheme.Test: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
config = jsonDictionary.json(atKeyPath: "config")
- gatherCoverageData = jsonDictionary.json(atKeyPath: "gatherCoverageData") ?? false
+ 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") ?? [:]
- targets = jsonDictionary.json(atKeyPath: "targets") ?? []
+ 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
}
}
@@ -225,6 +825,22 @@ extension Scheme.Profile: JSONObjectConvertible {
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?]
}
}
@@ -235,17 +851,42 @@ extension Scheme.Analyze: JSONObjectConvertible {
}
}
+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") ?? true
+ 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 {
@@ -256,6 +897,21 @@ extension Scheme: NamedJSONDictionaryConvertible {
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?]
}
}
@@ -264,7 +920,7 @@ extension Scheme.Build: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
let targetDictionary: JSONDictionary = try jsonDictionary.json(atKeyPath: "targets")
var targets: [Scheme.BuildTarget] = []
- for (target, possibleBuildTypes) in targetDictionary {
+ for (targetRepr, possibleBuildTypes) in targetDictionary {
let buildTypes: [BuildType]
if let string = possibleBuildTypes as? String {
switch string {
@@ -281,17 +937,43 @@ extension Scheme.Build: JSONObjectConvertible {
} else {
buildTypes = BuildType.all
}
+ let target = try TestableTargetReference(targetRepr)
targets.append(Scheme.BuildTarget(target: target, buildTypes: buildTypes))
}
- self.targets = targets.sorted { $0.target < $1.target }
+ 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") ?? true
- buildImplicitDependencies = jsonDictionary.json(atKeyPath: "buildImplicitDependencies") ?? true
+ 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: JSONPrimitiveConvertible {
+extension BuildType: JSONUtilities.JSONPrimitiveConvertible {
public typealias JSONType = String
@@ -307,11 +989,24 @@ extension BuildType: JSONPrimitiveConvertible {
}
public static var all: [BuildType] {
- return [.running, .testing, .profiling, .analyzing, .archiving]
+ [.running, .testing, .profiling, .analyzing, .archiving]
}
}
-extension XCScheme.EnvironmentVariable: JSONObjectConvertible {
+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 {
@@ -331,7 +1026,7 @@ extension XCScheme.EnvironmentVariable: JSONObjectConvertible {
value = try jsonDictionary.json(atKeyPath: "value")
}
let variable: String = try jsonDictionary.json(atKeyPath: "variable")
- let enabled: Bool = (try? jsonDictionary.json(atKeyPath: "isEnabled")) ?? true
+ let enabled: Bool = jsonDictionary.json(atKeyPath: "isEnabled") ?? XCScheme.EnvironmentVariable.enabledDefault
self.init(variable: variable, value: value, enabled: enabled)
}
@@ -347,3 +1042,54 @@ extension XCScheme.EnvironmentVariable: JSONObjectConvertible {
}
}
}
+
+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
+ }
+}
diff --git a/Sources/ProjectSpec/Settings.swift b/Sources/ProjectSpec/Settings.swift
index 26d061bf8..f0a5344ad 100644
--- a/Sources/ProjectSpec/Settings.swift
+++ b/Sources/ProjectSpec/Settings.swift
@@ -1,7 +1,7 @@
import Foundation
import JSONUtilities
import PathKit
-import xcodeproj
+import XcodeProj
public struct Settings: Equatable, JSONObjectConvertible, CustomStringConvertible {
@@ -15,29 +15,44 @@ public struct Settings: Equatable, JSONObjectConvertible, CustomStringConvertibl
self.groups = groups
}
- public init(dictionary: [String: Any]) {
- buildSettings = dictionary
- configSettings = [:]
- groups = []
- }
-
- public static let empty: Settings = Settings(dictionary: [:])
+ 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
- configSettings = jsonDictionary.json(atKeyPath: "configs") ?? [:]
+ buildSettings = buildSettingsDictionary.mapValues { BuildSetting(any: $0) }
+
+ self.configSettings = try Self.extractValidConfigs(from: jsonDictionary)
} else {
- buildSettings = jsonDictionary
+ 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 {
- return NSDictionary(dictionary: lhs.buildSettings).isEqual(to: rhs.buildSettings) &&
+ lhs.buildSettings == rhs.buildSettings &&
lhs.configSettings == rhs.configSettings &&
lhs.groups == rhs.groups
}
@@ -75,14 +90,14 @@ public struct Settings: Equatable, JSONObjectConvertible, CustomStringConvertibl
extension Settings: ExpressibleByDictionaryLiteral {
- public init(dictionaryLiteral elements: (String, Any)...) {
- var dictionary: [String: Any] = [:]
- elements.forEach { dictionary[$0.0] = $0.1 }
- self.init(dictionary: dictionary)
+ 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, Value: Any {
+extension Dictionary where Key == String {
public func merged(_ dictionary: [Key: Value]) -> [Key: Value] {
var mergedDictionary = self
@@ -95,31 +110,56 @@ extension Dictionary where Key == String, Value: Any {
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 equals(_ dictionary: BuildSettings) -> Bool {
- return NSDictionary(dictionary: self).isEqual(to: dictionary)
+ public func toAny() -> Any {
+ switch self {
+ case let .string(value): return value
+ case let .array(value): return value
+ }
}
}
-func merge(dictionary: JSONDictionary, onto base: JSONDictionary) -> JSONDictionary {
- var merged = base
-
- for (key, value) in dictionary {
- if key.hasSuffix(":REPLACE") {
- let newKey = key.replacingOccurrences(of: ":REPLACE", with: "")
- merged[newKey] = value
- } else if let dictionary = value as? JSONDictionary, let base = merged[key] as? JSONDictionary {
- merged[key] = merge(dictionary: dictionary, onto: base)
- } else if let array = value as? [Any], let base = merged[key] as? [Any] {
- merged[key] = base + array
+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 {
- merged[key] = value
+ self = .string("\(value)")
}
}
- return merged
}
-public func += (lhs: inout BuildSettings, rhs: BuildSettings?) {
- guard let rhs = rhs else { return }
- lhs.merge(rhs)
+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
+ }
}
diff --git a/Sources/ProjectSpec/SourceType.swift b/Sources/ProjectSpec/SourceType.swift
new file mode 100644
index 000000000..9fe009321
--- /dev/null
+++ b/Sources/ProjectSpec/SourceType.swift
@@ -0,0 +1,15 @@
+//
+// File.swift
+//
+//
+// Created by Yonas Kolb on 1/5/20.
+//
+
+import Foundation
+
+public enum SourceType: String {
+ case group
+ case file
+ case folder
+ case syncedFolder
+}
diff --git a/Sources/ProjectSpec/SpecFile.swift b/Sources/ProjectSpec/SpecFile.swift
new file mode 100644
index 000000000..18ea15628
--- /dev/null
+++ b/Sources/ProjectSpec/SpecFile.swift
@@ -0,0 +1,261 @@
+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
+ }
+}
diff --git a/Sources/ProjectSpec/SpecLoader.swift b/Sources/ProjectSpec/SpecLoader.swift
index ca49d6200..fe93e8403 100644
--- a/Sources/ProjectSpec/SpecLoader.swift
+++ b/Sources/ProjectSpec/SpecLoader.swift
@@ -1,48 +1,73 @@
import Foundation
import JSONUtilities
import PathKit
+import XcodeProj
import Yams
+import Version
-extension Project {
+public class SpecLoader {
- public init(path: Path) throws {
- let dictionary = try Project.loadDictionary(path: path)
- try self.init(basePath: path.parent(), jsonDictionary: dictionary)
+ var project: Project!
+ public private(set) var projectDictionary: [String: Any]?
+ let version: Version
+
+ public init(version: Version) {
+ self.version = version
}
- private static func loadDictionary(path: Path) throws -> JSONDictionary {
+ 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)
- // Depending on the extension we will either load the file as YAML or JSON
- var json: [String: Any]
- 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)")
- }
- json = jsonDictionary
- } else {
- json = try loadYamlDictionary(path: path)
+ 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
+ )
+ }
+}
- var includes: [String]
- if let includeString = json["include"] as? String {
- includes = [includeString]
- } else if let includeArray = json["include"] as? [String] {
- includes = includeArray
- } else {
- includes = []
+private extension Dictionary where Key == String, Value: Any {
+
+ func validateWarnings() throws {
+ let errors: [SpecValidationError.ValidationError] = []
+
+ if !errors.isEmpty {
+ throw SpecValidationError(errors: errors)
}
+ }
- if !includes.isEmpty {
- var includeDictionary: JSONDictionary = [:]
- for include in includes {
- let includePath = path.parent() + include
- let dictionary = try loadDictionary(path: includePath)
- includeDictionary = merge(dictionary: dictionary, onto: includeDictionary)
+ 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
}
- json = merge(dictionary: json, onto: includeDictionary)
}
- return json
}
}
diff --git a/Sources/ProjectSpec/SpecOptions.swift b/Sources/ProjectSpec/SpecOptions.swift
index 9d5595784..07582d424 100644
--- a/Sources/ProjectSpec/SpecOptions.swift
+++ b/Sources/ProjectSpec/SpecOptions.swift
@@ -1,7 +1,16 @@
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?
@@ -15,13 +24,26 @@ public struct SpecOptions: Equatable {
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 {
@@ -59,19 +81,30 @@ public struct SpecOptions: Equatable {
minimumXcodeGenVersion: Version? = nil,
carthageBuildPath: String? = nil,
carthageExecutablePath: String? = nil,
- createIntermediateGroups: Bool = false,
+ createIntermediateGroups: Bool = createIntermediateGroupsDefault,
bundleIdPrefix: String? = nil,
- settingPresets: SettingPresets = .all,
+ 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 = false,
- groupSortPosition: GroupSortPosition = .bottom
+ 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
@@ -84,11 +117,22 @@ public struct SpecOptions: Equatable {
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
}
}
@@ -96,23 +140,94 @@ extension SpecOptions: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
if let string: String = jsonDictionary.json(atKeyPath: "minimumXcodeGenVersion") {
- minimumXcodeGenVersion = try Version(string)
+ 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") ?? .all
- createIntermediateGroups = jsonDictionary.json(atKeyPath: "createIntermediateGroups") ?? false
+ 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") ?? false
- groupSortPosition = jsonDictionary.json(atKeyPath: "groupSortPosition") ?? .bottom
+ 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"),
+ ]
}
}
diff --git a/Sources/ProjectSpec/SpecParsingError.swift b/Sources/ProjectSpec/SpecParsingError.swift
index 5d48d4d0b..57db99f2e 100644
--- a/Sources/ProjectSpec/SpecParsingError.swift
+++ b/Sources/ProjectSpec/SpecParsingError.swift
@@ -4,8 +4,18 @@ 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 {
@@ -17,8 +27,28 @@ public enum SpecParsingError: Error, CustomStringConvertible {
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"
}
}
}
diff --git a/Sources/ProjectSpec/SpecValidation.swift b/Sources/ProjectSpec/SpecValidation.swift
index 43f4a4190..76ee603f5 100644
--- a/Sources/ProjectSpec/SpecValidation.swift
+++ b/Sources/ProjectSpec/SpecValidation.swift
@@ -1,5 +1,7 @@
import Foundation
+import JSONUtilities
import PathKit
+import Version
extension Project {
@@ -15,11 +17,32 @@ extension Project {
errors.append(.invalidSettingsGroup(group))
}
}
+
for config in settings.configSettings.keys {
- if !configs.contains(where: { $0.name.lowercased().contains(config.lowercased()) }) {
- if !options.disabledValidations.contains(.missingConfigs) {
- errors.append(.invalidBuildSettingConfig(config))
+ 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
@@ -33,8 +56,14 @@ extension Project {
}
}
+ 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 !(basePath + configFile).exists {
+ if !options.disabledValidations.contains(.missingConfigFiles) && !(basePath + configFile).exists {
errors.append(.invalidConfigFile(configFile: configFile, config: config))
}
if !options.disabledValidations.contains(.missingConfigs) && getConfig(config) == nil {
@@ -55,8 +84,9 @@ extension Project {
for target in projectTargets {
for (config, configFile) in target.configFiles {
- if !(basePath + configFile).exists {
- errors.append(.invalidTargetConfigFile(target: target.name, configFile: configFile, config: config))
+ 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))
@@ -64,16 +94,16 @@ extension Project {
}
if let scheme = target.scheme {
-
+
for configVariant in scheme.configVariants {
- if !configs.contains(where: { $0.name.contains(configVariant) && $0.type == .debug }) {
+ if configs.first(including: configVariant, for: .debug) == nil {
errors.append(.invalidTargetSchemeConfigVariant(
target: target.name,
configVariant: configVariant,
configType: .debug
))
}
- if !configs.contains(where: { $0.name.contains(configVariant) && $0.type == .release }) {
+ if configs.first(including: configVariant, for: .release) == nil {
errors.append(.invalidTargetSchemeConfigVariant(
target: target.name,
configVariant: configVariant,
@@ -92,22 +122,37 @@ extension Project {
}
for testTarget in scheme.testTargets {
- if getTarget(testTarget) == nil {
- errors.append(.invalidTargetSchemeTest(target: target.name, testTarget: testTarget))
+ 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: pathString))
+ 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 {
@@ -119,32 +164,93 @@ extension Project {
}
for target in targets {
+ var uniqueDependencies = Set()
+
for dependency in target.dependencies {
- if dependency.type == .target, getProjectTarget(dependency.reference) == nil {
- errors.append(.invalidTargetDependency(target: target.name, dependency: dependency.reference))
+ 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 scheme in schemes {
- for buildTarget in scheme.build.targets {
- if getProjectTarget(buildTarget.target) == nil {
- errors.append(.invalidSchemeTarget(scheme: scheme.name, target: buildTarget.target))
- }
+ 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))
}
@@ -160,4 +266,76 @@ extension Project {
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
+ }
+ }
}
diff --git a/Sources/ProjectSpec/SpecValidationError.swift b/Sources/ProjectSpec/SpecValidationError.swift
index d44fc19ab..85fd07e24 100644
--- a/Sources/ProjectSpec/SpecValidationError.swift
+++ b/Sources/ProjectSpec/SpecValidationError.swift
@@ -1,18 +1,32 @@
import Foundation
+import Version
public struct SpecValidationError: Error, CustomStringConvertible {
public var errors: [ValidationError]
- public enum ValidationError: Error, CustomStringConvertible {
+ 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 invalidSchemeTarget(scheme: String, target: 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)
@@ -21,25 +35,45 @@ public struct SpecValidationError: Error, CustomStringConvertible {
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 \(configFile.quoted) for config \(config.quoted)"
+ 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):
- return "Scheme \(scheme.quoted) has invalid build target \(target.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):
@@ -52,10 +86,32 @@ public struct SpecValidationError: Error, CustomStringConvertible {
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"
}
}
}
diff --git a/Sources/ProjectSpec/SupportedDestination.swift b/Sources/ProjectSpec/SupportedDestination.swift
new file mode 100644
index 000000000..6c7ed11a0
--- /dev/null
+++ b/Sources/ProjectSpec/SupportedDestination.swift
@@ -0,0 +1,50 @@
+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
+ }
+ }
+}
diff --git a/Sources/ProjectSpec/SwiftPackage.swift b/Sources/ProjectSpec/SwiftPackage.swift
new file mode 100644
index 000000000..2ec58f6c3
--- /dev/null
+++ b/Sources/ProjectSpec/SwiftPackage.swift
@@ -0,0 +1,155 @@
+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"),
+ ]),
+ ]
+ }
+}
diff --git a/Sources/ProjectSpec/Target.swift b/Sources/ProjectSpec/Target.swift
index f4118c3c9..781994f42 100644
--- a/Sources/ProjectSpec/Target.swift
+++ b/Sources/ProjectSpec/Target.swift
@@ -1,8 +1,11 @@
import Foundation
import JSONUtilities
-import xcodeproj
+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
@@ -10,7 +13,7 @@ public struct LegacyTarget: Equatable {
public init(
toolPath: String,
- passSettings: Bool = false,
+ passSettings: Bool = passSettingsDefault,
arguments: String? = nil,
workingDirectory: String? = nil
) {
@@ -21,18 +24,32 @@ public struct LegacyTarget: Equatable {
}
}
+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 postbuildScripts: [BuildScript]
+ 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?
@@ -40,9 +57,11 @@ public struct Target: ProjectTarget {
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 {
- return legacy != nil
+ legacy != nil
}
public var filename: String {
@@ -50,6 +69,9 @@ public struct Target: ProjectTarget {
if let fileExtension = type.fileExtension {
filename += ".\(fileExtension)"
}
+ if type == .staticLibrary {
+ filename = "lib\(filename)"
+ }
return filename
}
@@ -57,112 +79,105 @@ public struct Target: ProjectTarget {
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] = [],
- postbuildScripts: [BuildScript] = [],
+ preBuildScripts: [BuildScript] = [],
+ buildToolPlugins: [BuildToolPlugin] = [],
+ postCompileScripts: [BuildScript] = [],
+ postBuildScripts: [BuildScript] = [],
buildRules: [BuildRule] = [],
scheme: TargetScheme? = nil,
legacy: LegacyTarget? = nil,
- attributes: [String: Any] = [:]
+ 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.postbuildScripts = postbuildScripts
+ 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 {
- return "\(platform.emoji) \(name): \(type)"
+ "\(name): \(platform.rawValue) \(type)"
}
}
-extension Target {
-
- static func resolveTargetTemplates(jsonDictionary: JSONDictionary) throws -> JSONDictionary {
- guard var targetsDictionary: [String: JSONDictionary] = jsonDictionary["targets"] as? [String: JSONDictionary] else {
- return jsonDictionary
- }
-
- let targetTemplatesDictionary: [String: JSONDictionary] = jsonDictionary["targetTemplates"] as? [String: JSONDictionary] ?? [:]
-
- for (targetName, var target) in targetsDictionary {
-
- if let templates = target["templates"] as? [String] {
- var mergedDictionary: JSONDictionary = [:]
- for template in templates {
- if let templateDictionary = targetTemplatesDictionary[template] {
- mergedDictionary = merge(dictionary: templateDictionary, onto: mergedDictionary)
- }
- }
- target = merge(dictionary: target, onto: mergedDictionary)
- }
- targetsDictionary[targetName] = target
- }
-
- var jsonDictionary = jsonDictionary
- jsonDictionary["targets"] = targetsDictionary
- return jsonDictionary
+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) throws -> JSONDictionary {
+ static func resolveMultiplatformTargets(jsonDictionary: JSONDictionary) -> JSONDictionary {
guard let targetsDictionary: [String: JSONDictionary] = jsonDictionary["targets"] as? [String: JSONDictionary] else {
return jsonDictionary
}
-
- let platformReplacement = "$platform"
+
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
- func replacePlatform(_ dictionary: JSONDictionary) -> JSONDictionary {
- var replaced = dictionary
- for (key, value) in dictionary {
- switch value {
- case let dictionary as JSONDictionary:
- replaced[key] = replacePlatform(dictionary)
- case let string as String:
- replaced[key] = string.replacingOccurrences(of: platformReplacement, with: platform)
- case let array as [JSONDictionary]:
- replaced[key] = array.map(replacePlatform)
- case let array as [String]:
- replaced[key] = array.map { $0.replacingOccurrences(of: platformReplacement, with: platform) }
- default: break
- }
- }
- return replaced
- }
-
- platformTarget = replacePlatform(platformTarget)
+ platformTarget = platformTarget.expand(variables: ["platform": platform])
platformTarget["platform"] = platform
let platformSuffix = platformTarget["platformSuffix"] as? String ?? "_\(platform)"
@@ -183,14 +198,17 @@ extension Target {
}
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
}
@@ -199,7 +217,7 @@ extension Target {
extension Target: Equatable {
public static func == (lhs: Target, rhs: Target) -> Bool {
- return lhs.name == rhs.name &&
+ lhs.name == rhs.name &&
lhs.type == rhs.type &&
lhs.platform == rhs.platform &&
lhs.deploymentTarget == rhs.deploymentTarget &&
@@ -209,9 +227,13 @@ extension Target: Equatable {
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.postbuildScripts == rhs.postbuildScripts &&
+ 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 &&
@@ -224,39 +246,77 @@ extension LegacyTarget: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
toolPath = try jsonDictionary.json(atKeyPath: "toolPath")
arguments = jsonDictionary.json(atKeyPath: "arguments")
- passSettings = jsonDictionary.json(atKeyPath: "passSettings") ?? false
+ 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
- self.productName = jsonDictionary.json(atKeyPath: "productName") ?? resolvedName
- let typeString: String = try jsonDictionary.json(atKeyPath: "type")
+ 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)
}
- let platformString: String = try jsonDictionary.json(atKeyPath: "platform")
+
+ 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(string)
+ deploymentTarget = try Version.parse(string)
} else if let double: Double = jsonDictionary.json(atKeyPath: "deploymentTarget") {
- deploymentTarget = try Version(double)
+ deploymentTarget = try Version.parse(String(double))
} else {
deploymentTarget = nil
}
- settings = jsonDictionary.json(atKeyPath: "settings") ?? .empty
+ settings = try BuildSettingsParser(jsonDictionary: jsonDictionary).parse()
configFiles = jsonDictionary.json(atKeyPath: "configFiles") ?? [:]
if let source: String = jsonDictionary.json(atKeyPath: "sources") {
sources = [TargetSource(path: source)]
@@ -276,17 +336,81 @@ extension Target: NamedJSONDictionaryConvertible {
if jsonDictionary["dependencies"] == nil {
dependencies = []
} else {
- dependencies = try jsonDictionary.json(atKeyPath: "dependencies", invalidItemBehaviour: .fail)
+ 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") ?? []
- postbuildScripts = jsonDictionary.json(atKeyPath: "postbuildScripts") ?? []
+ 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
}
}
diff --git a/Sources/ProjectSpec/TargetReference.swift b/Sources/ProjectSpec/TargetReference.swift
new file mode 100644
index 000000000..22da0a2e1
--- /dev/null
+++ b/Sources/ProjectSpec/TargetReference.swift
@@ -0,0 +1,57 @@
+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
+ }
+}
diff --git a/Sources/ProjectSpec/TargetScheme.swift b/Sources/ProjectSpec/TargetScheme.swift
index 50d49f88c..967417b55 100644
--- a/Sources/ProjectSpec/TargetScheme.swift
+++ b/Sources/ProjectSpec/TargetScheme.swift
@@ -1,44 +1,181 @@
import Foundation
import JSONUtilities
-import xcodeproj
+import XcodeProj
public struct TargetScheme: Equatable {
- public var testTargets: [String]
+ 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: [String] = [],
+ testTargets: [Scheme.Test.TestTarget] = [],
+ testPlans: [TestPlan] = [],
configVariants: [String] = [],
- gatherCoverageData: Bool = false,
+ 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] = []
+ 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 {
- testTargets = jsonDictionary.json(atKeyPath: "testTargets") ?? []
+ 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") ?? false
+ 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),
+ ]
}
}
diff --git a/Sources/ProjectSpec/TargetSource.swift b/Sources/ProjectSpec/TargetSource.swift
index a0f8e5e43..6cfe7e7cc 100644
--- a/Sources/ProjectSpec/TargetSource.swift
+++ b/Sources/ProjectSpec/TargetSource.swift
@@ -1,18 +1,31 @@
import Foundation
import JSONUtilities
import PathKit
-import xcodeproj
public struct TargetSource: Equatable {
-
- public var path: String
+ 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: BuildPhase?
+ 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`
@@ -28,98 +41,40 @@ public struct TargetSource: Equatable {
}
}
- public enum BuildPhase: 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"
- )
-
- 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: xcodeproj.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 var destination: Destination
- public var subpath: String
-
- public init(destination: Destination, subpath: String) {
- self.destination = destination
- self.subpath = subpath
- }
- }
-
- public var buildPhase: xcodeproj.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
- }
- }
- }
-
- public enum SourceType: String {
- case group
- case file
- case folder
- }
-
public init(
path: String,
name: String? = nil,
+ group: String? = nil,
compilerFlags: [String] = [],
excludes: [String] = [],
+ includes: [String] = [],
+ explicitFolders: [String] = [],
type: SourceType? = nil,
- optional: Bool = false,
- buildPhase: BuildPhase? = nil,
- headerVisibility: HeaderVisibility? = 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
+ 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
}
}
@@ -142,7 +97,9 @@ 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")
@@ -151,44 +108,61 @@ extension TargetSource: JSONObjectConvertible {
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") ?? false
+ optional = jsonDictionary.json(atKeyPath: "optional") ?? TargetSource.optionalDefault
if let string: String = jsonDictionary.json(atKeyPath: "buildPhase") {
- buildPhase = try BuildPhase(string: string)
+ buildPhase = try BuildPhaseSpec(string: string)
} else if let dict: JSONDictionary = jsonDictionary.json(atKeyPath: "buildPhase") {
- buildPhase = try BuildPhase(jsonDictionary: dict)
+ buildPhase = try BuildPhaseSpec(jsonDictionary: dict)
}
- }
-}
-extension TargetSource.BuildPhase {
-
- 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)
+ 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.BuildPhase: JSONObjectConvertible {
+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
+ }
- public init(jsonDictionary: JSONDictionary) throws {
- self = .copyFiles(try jsonDictionary.json(atKeyPath: "copyFiles"))
+ return dict
}
}
-extension TargetSource.BuildPhase.CopyFilesSettings: JSONObjectConvertible {
+extension TargetSource: PathContainer {
- public init(jsonDictionary: JSONDictionary) throws {
- destination = try jsonDictionary.json(atKeyPath: "destination")
- subpath = jsonDictionary.json(atKeyPath: "subpath") ?? ""
+ static var pathProperties: [PathProperty] {
+ [
+ .string("path"),
+ ]
}
}
diff --git a/Sources/ProjectSpec/Template.swift b/Sources/ProjectSpec/Template.swift
new file mode 100644
index 000000000..71f16b1f2
--- /dev/null
+++ b/Sources/ProjectSpec/Template.swift
@@ -0,0 +1,76 @@
+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
+}
diff --git a/Sources/ProjectSpec/TestPlan.swift b/Sources/ProjectSpec/TestPlan.swift
new file mode 100644
index 000000000..c47b17cab
--- /dev/null
+++ b/Sources/ProjectSpec/TestPlan.swift
@@ -0,0 +1,39 @@
+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"),
+ ]
+ }
+}
diff --git a/Sources/ProjectSpec/TestTargeReference.swift b/Sources/ProjectSpec/TestTargeReference.swift
new file mode 100644
index 000000000..17ea557bb
--- /dev/null
+++ b/Sources/ProjectSpec/TestTargeReference.swift
@@ -0,0 +1,112 @@
+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
+ }
+}
diff --git a/Sources/ProjectSpec/Version.swift b/Sources/ProjectSpec/Version.swift
deleted file mode 100644
index a2c4f7130..000000000
--- a/Sources/ProjectSpec/Version.swift
+++ /dev/null
@@ -1,61 +0,0 @@
-import Foundation
-
-public struct Version: CustomStringConvertible, Equatable, Comparable {
-
- public var major: UInt
- public var minor: UInt
- public var patch: UInt
-
- public init(_ string: String) throws {
- let components = try string.split(separator: ".").map { (componentString) -> UInt in
- guard let uint = UInt(componentString) else {
- throw SpecParsingError.invalidVersion(string)
- }
- return uint
- }
-
- guard components.count <= 3 else {
- throw SpecParsingError.invalidVersion(string)
- }
-
- major = components[0]
- minor = (components.count >= 2) ? components[1] : 0
- patch = (components.count == 3) ? components[2] : 0
- }
-
- public init(_ double: Double) throws {
- try self.init(String(double))
- }
-
- public init(major: UInt, minor: UInt? = 0, patch: UInt? = 0) {
- self.major = major
- self.minor = minor ?? 0
- self.patch = patch ?? 0
- }
-
- public var string: String {
- return "\(major).\(minor).\(patch)"
- }
-
- public var description: String {
- return string
- }
-
- public func bumpingMajor() -> Version {
- return Version(major: major + 1, minor: 0, patch: 0)
- }
-
- public func bumpingMinor() -> Version {
- return Version(major: major, minor: minor + 1, patch: 0)
- }
-
- public func bumpingPatch() -> Version {
- return Version(major: major, minor: minor, patch: patch + 1)
- }
-
- public static func < (lhs: Version, rhs: Version) -> Bool {
- guard lhs.major == rhs.major else { return lhs.major < rhs.major }
- guard lhs.minor == rhs.minor else { return lhs.minor < rhs.minor }
- return lhs.patch < rhs.patch
- }
-}
diff --git a/Sources/ProjectSpec/VersionExtensions.swift b/Sources/ProjectSpec/VersionExtensions.swift
new file mode 100644
index 000000000..3ab52d65a
--- /dev/null
+++ b/Sources/ProjectSpec/VersionExtensions.swift
@@ -0,0 +1,28 @@
+//
+// 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)!
+ }
+}
diff --git a/Sources/ProjectSpec/XCProjExtensions.swift b/Sources/ProjectSpec/XCProjExtensions.swift
index bd3541a12..e1da8a3b0 100644
--- a/Sources/ProjectSpec/XCProjExtensions.swift
+++ b/Sources/ProjectSpec/XCProjExtensions.swift
@@ -1,6 +1,6 @@
import Foundation
import PathKit
-import xcodeproj
+import XcodeProj
extension PBXProductType {
@@ -15,31 +15,66 @@ extension PBXProductType {
}
public var isFramework: Bool {
- return self == .framework
+ self == .framework || self == .staticFramework
}
public var isLibrary: Bool {
- return self == .staticLibrary || self == .dynamicLibrary
+ self == .staticLibrary || self == .dynamicLibrary
}
public var isExtension: Bool {
- return fileExtension == "appex"
+ fileExtension == "appex"
+ }
+
+ public var isSystemExtension: Bool {
+ fileExtension == "dext" || fileExtension == "systemextension"
}
public var isApp: Bool {
- return fileExtension == "app"
+ fileExtension == "app"
}
public var isTest: Bool {
- return fileExtension == "xctest"
+ fileExtension == "xctest"
}
public var isExecutable: Bool {
- return isApp || isExtension || isTest
+ isApp || isExtension || isSystemExtension || isTest || self == .commandLineTool
}
public var name: String {
- return rawValue.replacingOccurrences(of: "com.apple.product-type.", with: "")
+ 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
+ }
+ }
}
}
@@ -47,14 +82,23 @@ 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]) {
@@ -64,3 +108,30 @@ extension XCScheme.CommandLineArguments {
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)
+ }
+ }
+}
+
diff --git a/Sources/ProjectSpec/Yaml.swift b/Sources/ProjectSpec/Yaml.swift
index b03717116..89014f664 100644
--- a/Sources/ProjectSpec/Yaml.swift
+++ b/Sources/ProjectSpec/Yaml.swift
@@ -16,3 +16,9 @@ public func loadYamlDictionary(path: Path) throws -> [String: Any] {
}
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)
+}
diff --git a/Tests/XcodeGenKitTests/TestHelpers.swift b/Sources/TestSupport/TestHelpers.swift
similarity index 72%
rename from Tests/XcodeGenKitTests/TestHelpers.swift
rename to Sources/TestSupport/TestHelpers.swift
index 54a7b700b..757856dff 100644
--- a/Tests/XcodeGenKitTests/TestHelpers.swift
+++ b/Sources/TestSupport/TestHelpers.swift
@@ -1,13 +1,12 @@
import Foundation
import PathKit
-import ProjectSpec
import Spectre
-import xcodeproj
+import XcodeProj
import XCTest
-let fixturePath = Path(#file).parent().parent() + "Fixtures"
+public let fixturePath = Path(#file).parent().parent().parent() + "Tests/Fixtures"
-func doThrowing(file: String = #file, line: Int = #line, _ closure: () throws -> T) throws -> T {
+public func doThrowing(file: String = #file, line: Int = #line, _ closure: () throws -> T) throws -> T {
do {
return try closure()
} catch {
@@ -15,7 +14,15 @@ func doThrowing(file: String = #file, line: Int = #line, _ closure: () throws
}
}
-func expectError(_ expectedError: T, function: String = #function, file: String = #file, line: Int = #line, _ closure: () throws -> Void) throws where T: CustomStringConvertible {
+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 {
@@ -43,7 +50,7 @@ struct ExpectationFailure: FailureType {
}
open class ArrayExpectation: ExpectationType {
- public typealias ValueType = Array
+ public typealias ValueType = [T]
public let expression: () throws -> ValueType?
let file: String
@@ -51,7 +58,7 @@ open class ArrayExpectation: ExpectationType {
let function: String
open var to: ArrayExpectation {
- return self
+ self
}
init(file: String, line: Int, function: String, expression: @escaping () throws -> ValueType?) {
@@ -62,12 +69,12 @@ open class ArrayExpectation: ExpectationType {
}
open func failure(_ reason: String) -> FailureType {
- return ExpectationFailure(reason: reason, file: file, line: line, function: function)
+ 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 {
- return ArrayExpectation(file: file, line: line, function: function, expression: expression)
+ ArrayExpectation(file: file, line: line, function: function, expression: expression)
}
extension ArrayExpectation {
@@ -102,14 +109,6 @@ extension XCBuildConfiguration: Named {}
extension PBXNativeTarget: Named {}
extension XCScheme: Named {}
-extension Version: ExpressibleByStringLiteral {
-
- /// Will return nil literal not Semver
- public init(stringLiteral value: String) {
- try! self.init(value)
- }
-}
-
extension XCTestCase {
public func describe(_ name: StaticString = #function, _ test: (ContextType) -> Void) {
@@ -125,3 +124,11 @@ extension XCTestCase {
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
+}
\ No newline at end of file
diff --git a/Sources/XcodeGen/Logger.swift b/Sources/XcodeGen/Logger.swift
deleted file mode 100644
index 21a5c655b..000000000
--- a/Sources/XcodeGen/Logger.swift
+++ /dev/null
@@ -1,39 +0,0 @@
-import Foundation
-import Rainbow
-
-struct Logger {
-
- // MARK: - Properties
-
- let isQuiet: Bool
- let isColored: Bool
-
- // MARK: - Initializers
-
- init(isQuiet: Bool = false, isColored: Bool = true) {
- self.isQuiet = isQuiet
- self.isColored = isColored
- }
-
- // MARK: - Logging
-
- func error(_ message: String) {
- print(isColored ? message.red : message)
- }
-
- func info(_ message: String) {
- if isQuiet {
- return
- }
-
- print(message)
- }
-
- func success(_ message: String) {
- if isQuiet {
- return
- }
-
- print(isColored ? message.green : message)
- }
-}
diff --git a/Sources/XcodeGen/main.swift b/Sources/XcodeGen/main.swift
index 9d732054d..8ba3f8798 100644
--- a/Sources/XcodeGen/main.swift
+++ b/Sources/XcodeGen/main.swift
@@ -1,96 +1,8 @@
-import Commander
import Foundation
-import JSONUtilities
-import PathKit
import ProjectSpec
-import XcodeGenKit
-import xcodeproj
+import XcodeGenCLI
+import Version
-let version = try Version("1.11.2")
-
-func generate(spec: String, project: String, isQuiet: Bool, justVersion: Bool) {
- if justVersion {
- print(version)
- exit(EXIT_SUCCESS)
- }
-
- let logger = Logger(isQuiet: isQuiet)
-
- func fatalError(_ message: String) -> Never {
- logger.error(message)
- exit(1)
- }
-
- let projectSpecPath = Path(spec).absolute()
- let projectPath = project == "" ? projectSpecPath.parent() : Path(project).absolute()
-
- if !projectSpecPath.exists {
- fatalError("No project spec found at \(projectSpecPath.absolute())")
- }
-
- let project: Project
- do {
- project = try Project(path: projectSpecPath)
- logger.info("📋 Loaded project:\n \(project.debugDescription.replacingOccurrences(of: "\n", with: "\n "))")
- } catch let error as CustomStringConvertible {
- fatalError("Parsing project spec failed: \(error)")
- } catch {
- fatalError("Parsing project spec failed: \(error.localizedDescription)")
- }
-
- do {
- logger.info("⚙️ Generating project...")
-
- try project.validateMinimumXcodeGenVersion(version)
-
- let projectGenerator = ProjectGenerator(project: project)
- let xcodeProject = try projectGenerator.generateXcodeProject()
-
- logger.info("⚙️ Writing project...")
-
- let projectFile = projectPath + "\(project.name).xcodeproj"
- let tempPath = Path.temporary + "XcodeGen_\(Int(NSTimeIntervalSince1970))"
- try? tempPath.delete()
- if projectFile.exists {
- try projectFile.copy(tempPath)
- }
- try xcodeProject.write(path: tempPath, override: true)
- try? projectFile.delete()
- try tempPath.copy(projectFile)
- try? tempPath.delete()
-
- logger.success("💾 Saved project to \(projectFile.string)")
- } catch let error as SpecValidationError {
- fatalError(error.description)
- } catch {
- fatalError("Generation failed: \(error.localizedDescription)")
- }
-}
-
-command(
- Option(
- "spec",
- default: "project.yml",
- flag: "s",
- description: "The path to the project spec file"
- ),
- Option(
- "project",
- default: "",
- flag: "p",
- description: "The path to the folder where the project should be generated"
- ),
- Flag(
- "quiet",
- default: false,
- flag: "q",
- description: "Suppress printing of informational and success messages"
- ),
- Flag(
- "version",
- default: false,
- flag: "v",
- description: "Show XcodeGen version"
- ),
- generate
-).run(version.description)
+let version = Version("2.45.4")
+let cli = XcodeGenCLI(version: version)
+cli.execute()
diff --git a/Sources/XcodeGenCLI/Arguments.swift b/Sources/XcodeGenCLI/Arguments.swift
new file mode 100644
index 000000000..88f6c34f7
--- /dev/null
+++ b/Sources/XcodeGenCLI/Arguments.swift
@@ -0,0 +1,10 @@
+import Foundation
+import PathKit
+import SwiftCLI
+
+extension Path: SwiftCLI.ConvertibleFromString {
+
+ public init?(input: String) {
+ self.init(input)
+ }
+}
diff --git a/Sources/XcodeGenCLI/Commands/CacheCommand.swift b/Sources/XcodeGenCLI/Commands/CacheCommand.swift
new file mode 100644
index 000000000..70a14a241
--- /dev/null
+++ b/Sources/XcodeGenCLI/Commands/CacheCommand.swift
@@ -0,0 +1,44 @@
+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)")
+ }
+ }
+ }
+}
diff --git a/Sources/XcodeGenCLI/Commands/DumpCommand.swift b/Sources/XcodeGenCLI/Commands/DumpCommand.swift
new file mode 100644
index 000000000..b0d59a612
--- /dev/null
+++ b/Sources/XcodeGenCLI/Commands/DumpCommand.swift
@@ -0,0 +1,64 @@
+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 }
+}
diff --git a/Sources/XcodeGenCLI/Commands/GenerateCommand.swift b/Sources/XcodeGenCLI/Commands/GenerateCommand.swift
new file mode 100644
index 000000000..d0e4eabe9
--- /dev/null
+++ b/Sources/XcodeGenCLI/Commands/GenerateCommand.swift
@@ -0,0 +1,138 @@
+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)
+ }
+ }
+}
diff --git a/Sources/XcodeGenCLI/Commands/ProjectCommand.swift b/Sources/XcodeGenCLI/Commands/ProjectCommand.swift
new file mode 100644
index 000000000..9f3b013c8
--- /dev/null
+++ b/Sources/XcodeGenCLI/Commands/ProjectCommand.swift
@@ -0,0 +1,82 @@
+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)
+ }
+ }
+}
diff --git a/Sources/XcodeGenCLI/GenerationError.swift b/Sources/XcodeGenCLI/GenerationError.swift
new file mode 100644
index 000000000..b9470a93c
--- /dev/null
+++ b/Sources/XcodeGenCLI/GenerationError.swift
@@ -0,0 +1,42 @@
+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
+ }
+}
diff --git a/Sources/XcodeGenCLI/XcodeGenCLI.swift b/Sources/XcodeGenCLI/XcodeGenCLI.swift
new file mode 100644
index 000000000..8d6a69c59
--- /dev/null
+++ b/Sources/XcodeGenCLI/XcodeGenCLI.swift
@@ -0,0 +1,34 @@
+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)
+ }
+}
diff --git a/Sources/XcodeGenCore/ArrayExtensions.swift b/Sources/XcodeGenCore/ArrayExtensions.swift
new file mode 100644
index 000000000..52c87ff77
--- /dev/null
+++ b/Sources/XcodeGenCore/ArrayExtensions.swift
@@ -0,0 +1,79 @@
+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
+ }
+}
diff --git a/Sources/XcodeGenCore/Atomic.swift b/Sources/XcodeGenCore/Atomic.swift
new file mode 100644
index 000000000..b3b1b2940
--- /dev/null
+++ b/Sources/XcodeGenCore/Atomic.swift
@@ -0,0 +1,52 @@
+//
+// 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)
+ }
+ }
+}
diff --git a/Sources/XcodeGenCore/Glob.swift b/Sources/XcodeGenCore/Glob.swift
new file mode 100644
index 000000000..4cee0a801
--- /dev/null
+++ b/Sources/XcodeGenCore/Glob.swift
@@ -0,0 +1,252 @@
+//
+// 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)
+ }
+}
diff --git a/Sources/XcodeGenCore/MD5.swift b/Sources/XcodeGenCore/MD5.swift
new file mode 100644
index 000000000..bffb6d370
--- /dev/null
+++ b/Sources/XcodeGenCore/MD5.swift
@@ -0,0 +1,273 @@
+// 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
diff --git a/Sources/XcodeGenCore/PathExtensions.swift b/Sources/XcodeGenCore/PathExtensions.swift
new file mode 100644
index 000000000..6b72a56f7
--- /dev/null
+++ b/Sources/XcodeGenCore/PathExtensions.swift
@@ -0,0 +1,84 @@
+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 != ".." }
+ }
+}
diff --git a/Tests/XcodeGenKitTests/StringDiff.swift b/Sources/XcodeGenCore/StringDiff.swift
similarity index 99%
rename from Tests/XcodeGenKitTests/StringDiff.swift
rename to Sources/XcodeGenCore/StringDiff.swift
index 88ebdff74..b7a8ce261 100644
--- a/Tests/XcodeGenKitTests/StringDiff.swift
+++ b/Sources/XcodeGenCore/StringDiff.swift
@@ -14,7 +14,7 @@ public func firstDifferenceBetweenStrings(_ s1: String, _ s2: String) -> FirstDi
let lenMin = min(len1, len2)
- for i in 0 ..< lenMin {
+ for i in 0.. 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
+ )
+ }
+}
diff --git a/Sources/XcodeGenKit/CarthageDependencyResolver.swift b/Sources/XcodeGenKit/CarthageDependencyResolver.swift
new file mode 100644
index 000000000..46b83ac90
--- /dev/null
+++ b/Sources/XcodeGenKit/CarthageDependencyResolver.swift
@@ -0,0 +1,170 @@
+//
+// 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"
+ }
+ }
+}
diff --git a/Sources/XcodeGenKit/CarthageVersionLoader.swift b/Sources/XcodeGenKit/CarthageVersionLoader.swift
new file mode 100644
index 000000000..20eb26079
--- /dev/null
+++ b/Sources/XcodeGenKit/CarthageVersionLoader.swift
@@ -0,0 +1,87 @@
+//
+// 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] ?? []
+ }
+}
diff --git a/Sources/XcodeGenKit/FileWriter.swift b/Sources/XcodeGenKit/FileWriter.swift
new file mode 100644
index 000000000..17af96c5d
--- /dev/null
+++ b/Sources/XcodeGenKit/FileWriter.swift
@@ -0,0 +1,56 @@
+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)
+ }
+}
diff --git a/Sources/XcodeGenKit/InfoPlistGenerator.swift b/Sources/XcodeGenKit/InfoPlistGenerator.swift
new file mode 100644
index 000000000..165916f22
--- /dev/null
+++ b/Sources/XcodeGenKit/InfoPlistGenerator.swift
@@ -0,0 +1,49 @@
+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
+ }
+}
diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift
index be7072f14..1f20e850a 100644
--- a/Sources/XcodeGenKit/PBXProjGenerator.swift
+++ b/Sources/XcodeGenKit/PBXProjGenerator.swift
@@ -1,39 +1,51 @@
import Foundation
import PathKit
import ProjectSpec
-import xcodeproj
+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
- var carthageBuildPath: String {
- return project.options.carthageBuildPath ?? "Carthage/Build"
- }
+ private var projects: [ProjectReference: PBXProj] = [:]
- public init(project: Project) {
+ public init(project: Project, projectDirectory: Path? = nil) {
self.project = project
- pbxProj = PBXProj(rootObject: nil, objectVersion: 46)
- sourceGenerator = SourceGenerator(project: project) { [unowned self] object in
- _ = self.addObject(object)
- }
+ carthageResolver = CarthageDependencyResolver(project: project)
+ pbxProj = PBXProj(rootObject: nil, objectVersion: project.objectVersion)
+ self.projectDirectory = projectDirectory
+ sourceGenerator = SourceGenerator(project: project,
+ pbxProj: pbxProj,
+ projectDirectory: projectDirectory)
}
- func addObject(_ object: T) -> T {
+ @discardableResult
+ func addObject(_ object: T, context: String? = nil) -> T {
pbxProj.add(object: object)
+ object.context = context
return object
}
@@ -84,13 +96,16 @@ public class PBXProjGenerator {
)
)
+ let developmentRegion = project.options.developmentLanguage ?? "en"
let pbxProject = addObject(
PBXProject(
name: project.name,
buildConfigurationList: buildConfigList,
- compatibilityVersion: "Xcode 3.2",
+ compatibilityVersion: project.compatibilityVersion,
+ preferredProjectObjectVersion: project.preferredProjectObjectVersion.map { Int($0) },
+ minimizedProjectReferenceProxies: project.minimizedProjectReferenceProxies,
mainGroup: mainGroup,
- developmentRegion: project.options.developmentLanguage ?? "en"
+ developmentRegion: developmentRegion
)
)
@@ -116,14 +131,14 @@ public class PBXProjGenerator {
var explicitFileType: String?
var lastKnownFileType: String?
- let fileType = Xcode.fileType(path: Path(target.filename))
- if target.platform == .macOS || target.platform == .watchOS || target.type == .framework {
+ 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 && targetFileReferences[target.productName] == nil {
+ if !target.isLegacy {
let fileReference = addObject(
PBXFileReference(
sourceTree: .buildProductsDir,
@@ -131,10 +146,11 @@ public class PBXProjGenerator {
lastKnownFileType: lastKnownFileType,
path: target.filename,
includeInIndex: false
- )
+ ),
+ context: target.name
)
- targetFileReferences[target.productName] = fileReference
+ targetFileReferences[target.name] = fileReference
}
}
@@ -149,8 +165,22 @@ public class PBXProjGenerator {
targetAggregateObjects[target.name] = aggregateTarget
}
- try project.targets.forEach(generateTarget)
- try project.aggregateTargets.forEach(generateAggregateTarget)
+ 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(
@@ -160,13 +190,57 @@ public class PBXProjGenerator {
)
)
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: files.sorted { $0.nameOrPath < $1.nameOrPath },
+ children: Array(files),
sourceTree: .group,
path: platform
)
@@ -178,7 +252,7 @@ public class PBXProjGenerator {
children: platforms,
sourceTree: .group,
name: "Carthage",
- path: carthageBuildPath
+ path: carthageResolver.buildPath
)
)
frameworkFiles.append(carthageGroup)
@@ -195,21 +269,60 @@ public class PBXProjGenerator {
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 { $0.nameOrPath.localizedStandardCompare($1.nameOrPath) == .orderedAscending }
+ .sorted(by: PBXFileElement.sortByNamePath)
.map { $0 }
- let projectAttributes: [String: Any] = ["LastUpgradeCheck": project.xcodeVersion]
- .merged(project.attributes)
+ 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()
- let knownRegions = sourceGenerator.knownRegions.sorted()
- pbxProject.knownRegions = knownRegions.isEmpty ? ["en"] : knownRegions
+ pbxProject.remotePackages = packageReferences.sorted { $0.key < $1.key }.map { $1 }
+ pbxProject.localPackages = localPackageReferences.sorted { $0.key < $1.key }.map { $1 }
- let allTargets: [PBXTarget] = Array(targetObjects.values) + Array(targetAggregateObjects.values)
+ let allTargets: [PBXTarget] = targetObjects.valueArray + targetAggregateObjects.valueArray
pbxProject.targets = allTargets
.sorted { $0.name < $1.name }
pbxProject.attributes = projectAttributes
@@ -237,29 +350,34 @@ public class PBXProjGenerator {
return addObject(buildConfig)
}
- let dependencies = target.targets.map { generateTargetDependency(from: target.name, to: $0) }
+ 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: 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) -> PBXTargetDependency {
+ func generateTargetDependency(from: String, to target: String, platform: String?, platforms: [String]?) -> PBXTargetDependency {
guard let targetObject = targetObjects[target] ?? targetAggregateObjects[target] else {
- fatalError("target not found")
+ fatalError("Target dependency not found: from ( \(from) ) to ( \(target) )")
}
+
let targetProxy = addObject(
PBXContainerItemProxy(
- containerPortal: pbxProj.rootObject!,
- remoteGlobalID: targetObject,
+ containerPortal: .project(pbxProj.rootObject!),
+ remoteGlobalID: .object(targetObject),
proxyType: .nativeTarget,
remoteInfo: target
)
@@ -267,6 +385,8 @@ public class PBXProjGenerator {
let targetDependency = addObject(
PBXTargetDependency(
+ platformFilter: platform,
+ platformFilters: platforms,
target: targetObject,
targetProxy: targetProxy
)
@@ -274,6 +394,112 @@ public class PBXProjGenerator {
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
@@ -288,45 +514,57 @@ public class PBXProjGenerator {
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
+ showEnvVarsInLog: buildScript.showEnvVars,
+ alwaysOutOfDate: !buildScript.basedOnDependencyAnalysis,
+ dependencyFile: buildScript.discoveredDependencyFile
)
return addObject(shellScriptPhase)
}
- func generateTargetAttributes() -> [PBXTarget: [String: Any]] {
+ 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)
+ }
- var targetAttributes: [PBXTarget: [String: Any]] = [:]
+ func generateTargetAttributes() -> [PBXTarget: [String: ProjectAttribute]] {
- let uiTestTargets = pbxProj.nativeTargets.filter { $0.productType == .uiTestBundle }
- for uiTestTarget in uiTestTargets {
+ 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"] as? String }
+ .compactMap { $0.buildSettings["TEST_TARGET_NAME"]?.stringValue }
.first
}
- guard let name = testTargetName(uiTestTarget) else { continue }
+ guard let name = testTargetName(testTarget) else { continue }
guard let target = self.pbxProj.targets(named: name).first else { continue }
- // FIX: Can't set in xcproj 5.0+
- targetAttributes[uiTestTarget, default: [:]].merge(["TestTargetID": target])
+ targetAttributes[testTarget, default: [:]].merge(["TestTargetID": .targetReference(target)])
}
func generateTargetAttributes(_ target: ProjectTarget, pbxTarget: PBXTarget) {
if !target.attributes.isEmpty {
- targetAttributes[pbxTarget, default: [:]].merge(target.attributes)
+ 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) as? String
+ project.getCombinedBuildSetting(setting, target: target, config: $0)?.stringValue
}
guard settings.count == project.configs.count,
let firstSetting = settings.first,
@@ -338,7 +576,7 @@ public class PBXProjGenerator {
func setTargetAttribute(attribute: String, buildSetting: String) {
if let setting = getSingleBuildSetting(buildSetting) {
- targetAttributes[pbxTarget, default: [:]].merge([attribute: setting])
+ targetAttributes[pbxTarget, default: [:]].merge([attribute: .string(setting)])
}
}
@@ -370,10 +608,14 @@ public class PBXProjGenerator {
let sortOrder1 = child1.getSortOrder(groupSortPosition: project.options.groupSortPosition)
let sortOrder2 = child2.getSortOrder(groupSortPosition: project.options.groupSortPosition)
- if sortOrder1 == sortOrder2 {
- return child1.nameOrPath.localizedStandardCompare(child2.nameOrPath) == .orderedAscending
- } else {
+ 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 }
@@ -383,34 +625,98 @@ public class PBXProjGenerator {
childGroups.forEach(sortGroups)
}
- func generateTarget(_ target: Target) throws {
+ 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
+ }
- sourceGenerator.targetName = target.name
- let carthageDependencies = getAllCarthageDependencies(target: target)
+ func generateTarget(_ target: Target) throws {
+ let carthageDependencies = carthageResolver.dependencies(for: target)
- let sourceFiles = try sourceGenerator.getAllSourceFiles(targetType: target.type, sources: target.sources)
+ 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 plistPath: Path?
- var searchForPlist = true
var anyDependencyRequiresObjCLinking = false
var dependencies: [PBXTargetDependency] = []
var targetFrameworkBuildFiles: [PBXBuildFile] = []
var frameworkBuildPaths = Set()
- var copyFilesBuildPhasesFiles: [TargetSource.BuildPhase.CopyFilesSettings: [PBXBuildFile]] = [:]
+ 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 directlyEmbedCarthage = target.directlyEmbedCarthageDependencies ?? !(target.platform.requiresSimulatorStripping && target.type.isApp)
+ let targetSupportsDirectEmbed = !(target.platform.requiresSimulatorStripping &&
+ (target.type.isApp || target.type == .watch2Extension))
+ let directlyEmbedCarthage = target.directlyEmbedCarthageDependencies ?? targetSupportsDirectEmbed
- func getEmbedSettings(dependency: Dependency, codeSign: Bool) -> [String: Any] {
+ func getEmbedSettings(dependency: Dependency, codeSign: Bool) -> [String: BuildFileSetting] {
var embedAttributes: [String] = []
if codeSign {
embedAttributes.append("CodeSignOnCopy")
@@ -418,77 +724,105 @@ public class PBXProjGenerator {
if dependency.removeHeaders {
embedAttributes.append("RemoveHeadersOnCopy")
}
- return ["ATTRIBUTES": embedAttributes]
+ return ["ATTRIBUTES": .array(embedAttributes)]
}
- func getDependencyFrameworkSettings(dependency: Dependency) -> [String: Any]? {
+ func getDependencyFrameworkSettings(dependency: Dependency) -> [String: BuildFileSetting]? {
var linkingAttributes: [String] = []
if dependency.weakLink {
linkingAttributes.append("Weak")
}
- return !linkingAttributes.isEmpty ? ["ATTRIBUTES": linkingAttributes] : nil
+ return !linkingAttributes.isEmpty ? ["ATTRIBUTES": .array(linkingAttributes)] : nil
}
- for dependency in targetDependencies {
-
- let embed = dependency.embed ?? target.shouldEmbedDependencies
-
- switch dependency.type {
- case .target:
- let dependencyTargetName = dependency.reference
- let targetDependency = generateTargetDependency(from: target.name, to: dependencyTargetName)
- dependencies.append(targetDependency)
-
- guard let dependencyTarget = project.getTarget(dependencyTargetName) else { continue }
+ 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)
- let dependencyFileReference = targetFileReferences[dependencyTarget.productName]!
+ if !anyDependencyRequiresObjCLinking
+ && dependencyTarget.requiresObjCLinking ?? (dependencyTarget.type == .staticLibrary) {
+ anyDependencyRequiresObjCLinking = true
+ }
+ }
- let dependecyLinkage = dependencyTarget.defaultLinkage
- let link = dependency.link ?? (
- (dependecyLinkage == .dynamic && target.type != .staticLibrary)
- || (dependecyLinkage == .static && target.type.isExecutable)
+ 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)
)
- if link {
- let dependencyFile = targetFileReferences[dependencyTarget.productName]!
- let buildFile = addObject(
- PBXBuildFile(file: dependencyFile, settings: getDependencyFrameworkSettings(dependency: dependency))
- )
- targetFrameworkBuildFiles.append(buildFile)
-
- if !anyDependencyRequiresObjCLinking
- && dependencyTarget.requiresObjCLinking ?? (dependencyTarget.type == .staticLibrary) {
- anyDependencyRequiresObjCLinking = true
+ 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)
}
+ }
+ }
- let embed = dependency.embed ?? (!dependencyTarget.type.isLibrary && (
- target.type.isApp
- || (target.type.isTest && (dependencyTarget.type.isFramework || dependencyTarget.type == .bundle))
- ))
- if embed {
- let embedFile = addObject(
- PBXBuildFile(
- file: dependencyFileReference,
- settings: getEmbedSettings(dependency: dependency, codeSign: dependency.codeSign ?? !dependencyTarget.type.isExecutable)
- )
- )
+ for dependency in targetDependencies {
- if dependencyTarget.type.isExtension {
- // embed app extension
- extensions.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)
- }
+ 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:
- guard target.type != .staticLibrary else { break }
+ if !dependency.implicit {
+ let buildPath = Path(dependency.reference).parent().string.quoted
+ frameworkBuildPaths.insert(buildPath)
+ }
let fileReference: PBXFileElement
if dependency.implicit {
@@ -504,76 +838,243 @@ public class PBXProjGenerator {
)
}
- let buildFile = addObject(
- PBXBuildFile(file: fileReference, settings: getDependencyFrameworkSettings(dependency: dependency))
- )
+ 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)
+ }
- targetFrameworkBuildFiles.append(buildFile)
if !frameworkFiles.contains(fileReference) {
frameworkFiles.append(fileReference)
}
if embed {
- let embedFile = addObject(
- PBXBuildFile(file: fileReference, settings: getEmbedSettings(dependency: dependency, codeSign: dependency.codeSign ?? 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 .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
+ )
)
- copyFrameworksReferences.append(embedFile)
+ 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
}
- let buildPath = Path(dependency.reference).parent().string.quoted
- frameworkBuildPaths.insert(buildPath)
+ 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)
+ }
- case .carthage:
- guard target.type != .staticLibrary else { break }
+ 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)
+ }
- var platformPath = Path(getCarthageBuildPath(platform: target.platform))
- var frameworkPath = platformPath + dependency.reference
- if frameworkPath.extension == nil {
- frameworkPath = Path(frameworkPath.string + ".framework")
+ 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)
+ }
+ }
}
- let fileReference = sourceGenerator.getFileReference(path: frameworkPath, inPath: platformPath)
- let buildFile = addObject(
- PBXBuildFile(file: fileReference, settings: getDependencyFrameworkSettings(dependency: dependency))
+ 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
)
- carthageFrameworksByPlatform[target.platform.carthageDirectoryName, default: []].insert(fileReference)
-
- targetFrameworkBuildFiles.append(buildFile)
+ 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)
- // Embedding handled by iterating over `carthageDependencies` below
+ if !bundleFiles.contains(fileReference) {
+ bundleFiles.append(fileReference)
+ }
}
}
- for dependency in carthageDependencies {
- guard target.type != .staticLibrary else { break }
-
- let embed = dependency.embed ?? target.shouldEmbedDependencies
+ for carthageDependency in carthageDependencies {
+ let dependency = carthageDependency.dependency
+ let isFromTopLevelTarget = carthageDependency.isFromTopLevelTarget
+ let embed = dependency.embed ?? target.shouldEmbedCarthageDependencies
- var platformPath = Path(getCarthageBuildPath(platform: target.platform))
+ 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 embed {
+ 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))
)
- copyFrameworksReferences.append(embedFile)
+ if let copyPhase = dependency.copyPhase {
+ buildFileCopyPhases[embedFile] = copyPhase
+ customCopyDependenciesReferences.append(embedFile)
+ } else {
+ copyFrameworksReferences.append(embedFile)
+ }
} else {
carthageFrameworksToEmbed.append(dependency.reference)
}
}
}
+
+ carthageFrameworksToEmbed = carthageFrameworksToEmbed.uniqued()
- let fileReference = targetFileReferences[target.productName]
+ let packagePluginDependencies = makePackagePluginDependency(for: target)
+ dependencies.append(contentsOf: packagePluginDependencies)
+
var buildPhases: [PBXBuildPhase] = []
func getBuildFilesForSourceFiles(_ sourceFiles: [SourceFile]) -> [PBXBuildFile] {
- return sourceFiles
+ sourceFiles
.reduce(into: [SourceFile]()) { output, sourceFile in
if !output.contains(where: { $0.fileReference === sourceFile.fileReference }) {
output.append(sourceFile)
@@ -588,8 +1089,8 @@ public class PBXProjGenerator {
return getBuildFilesForSourceFiles(filteredSourceFiles)
}
- func getBuildFilesForCopyFilesPhases() -> [TargetSource.BuildPhase.CopyFilesSettings: [PBXBuildFile]] {
- var sourceFilesByCopyFiles: [TargetSource.BuildPhase.CopyFilesSettings: [SourceFile]] = [:]
+ 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)
@@ -597,11 +1098,41 @@ public class PBXProjGenerator {
return sourceFilesByCopyFiles.mapValues { getBuildFilesForSourceFiles($0) }
}
- buildPhases += try target.prebuildScripts.map { try generateBuildScript(targetName: target.name, buildScript: $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 == .framework || target.type == .dynamicLibrary) {
+ if target.type.isFramework || target.type == .dynamicLibrary {
let headersBuildPhase = addObject(PBXHeadersBuildPhase(files: headersBuildPhaseFiles))
buildPhases.append(headersBuildPhase)
} else {
@@ -609,20 +1140,38 @@ public class PBXProjGenerator {
}
}
+ 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 sourcesBuildPhase = addObject(PBXSourcesBuildPhase(files: sourcesBuildPhaseFiles))
- buildPhases.append(sourcesBuildPhase)
+ 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) }
- let resourcesBuildPhaseFiles = getBuildFilesForPhase(.resources) + copyResourcesReferences
- if !resourcesBuildPhaseFiles.isEmpty {
- let resourcesBuildPhase = addObject(PBXResourcesBuildPhase(files: resourcesBuildPhaseFiles))
- buildPhases.append(resourcesBuildPhase)
+ if !target.putResourcesBeforeSourcesBuildPhase {
+ addResourcesBuildPhase()
}
- let swiftObjCInterfaceHeader = project.getCombinedBuildSetting("SWIFT_OBJC_INTERFACE_HEADER_NAME", target: target, config: project.configs[0]) as? String
+ 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)"]
@@ -639,19 +1188,27 @@ public class PBXProjGenerator {
buildPhases.append(script)
}
- copyFilesBuildPhasesFiles.merge(getBuildFilesForCopyFilesPhases()) { $0 + $1 }
- if !copyFilesBuildPhasesFiles.isEmpty {
- for (copyFiles, buildPhaseFiles) in copyFilesBuildPhasesFiles {
- let copyFilesBuildPhase = addObject(
- PBXCopyFilesBuildPhase(
- dstPath: copyFiles.subpath,
- dstSubfolderSpec: copyFiles.destination.destination,
- files: buildPhaseFiles
- )
- )
+ buildPhases += copyFilesBuildPhasesFiles
+ .filter { $0.key.phaseOrder == .postCompile }
+ .map { generateCopyFiles(targetName: target.name, copyFiles: $0, buildPhaseFiles: $1) }
- buildPhases.append(copyFilesBuildPhase)
- }
+ 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 {
@@ -662,14 +1219,50 @@ public class PBXProjGenerator {
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: "",
- dstSubfolderSpec: .plugins,
- name: "Embed App Extensions",
- files: extensions
+ dstPath: "$(CONTENTS_FOLDER_PATH)/AppClips",
+ dstSubfolderSpec: .productsDirectory,
+ name: "Embed App Clips",
+ files: appClips
)
)
@@ -680,17 +1273,27 @@ public class PBXProjGenerator {
if !copyFrameworksReferences.isEmpty {
let copyFilesPhase = addObject(
- PBXCopyFilesBuildPhase(
- dstPath: "",
- dstSubfolderSpec: .frameworks,
- name: "Embed Frameworks",
- files: copyFrameworksReferences
- )
+ 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(
@@ -705,25 +1308,6 @@ public class PBXProjGenerator {
buildPhases.append(copyFilesPhase)
}
- if !carthageFrameworksToEmbed.isEmpty {
-
- let inputPaths = carthageFrameworksToEmbed
- .map { "$(SRCROOT)/\(carthageBuildPath)/\(target.platform)/\($0)\($0.contains(".") ? "" : ".framework")" }
- let outputPaths = carthageFrameworksToEmbed
- .map { "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/\($0)\($0.contains(".") ? "" : ".framework")" }
- let carthageExecutable = project.options.carthageExecutablePath ?? "carthage"
- let carthageScript = addObject(
- PBXShellScriptBuildPhase(
- name: "Carthage",
- inputPaths: inputPaths,
- outputPaths: outputPaths,
- shellPath: "/bin/sh",
- shellScript: "\(carthageExecutable) copy-frameworks\n"
- )
- )
- buildPhases.append(carthageScript)
- }
-
let buildRules = target.buildRules.map { buildRule in
addObject(
PBXBuildRule(
@@ -734,25 +1318,25 @@ public class PBXProjGenerator {
name: buildRule.name ?? "Build Rule",
outputFiles: buildRule.outputFiles,
outputFilesCompilerFlags: buildRule.outputFilesCompilerFlags,
- script: buildRule.action.script
+ script: buildRule.action.script,
+ runOncePerArchitecture: buildRule.runOncePerArchitecture
)
)
}
- buildPhases += try target.postbuildScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) }
+ 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)
- // automatically set INFOPLIST_FILE path
- if !project.targetHasBuildSetting("INFOPLIST_FILE", target: target, config: config) {
- if searchForPlist {
- plistPath = getInfoPlist(target.sources)
- searchForPlist = false
- }
- if let plistPath = plistPath {
- buildSettings["INFOPLIST_FILE"] = plistPath.byRemovingBase(path: project.basePath)
- }
+ // 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
@@ -763,7 +1347,7 @@ public class PBXProjGenerator {
.replacingOccurrences(of: "_", with: "-")
.components(separatedBy: characterSet)
.joined(separator: "")
- buildSettings["PRODUCT_BUNDLE_IDENTIFIER"] = bundleIdPrefix + "." + escapedTargetName
+ buildSettings["PRODUCT_BUNDLE_IDENTIFIER"] = .string(bundleIdPrefix + "." + escapedTargetName)
}
// automatically set test target name
@@ -772,8 +1356,25 @@ public class PBXProjGenerator {
for dependency in target.dependencies {
if dependency.type == .target,
let dependencyTarget = project.getTarget(dependency.reference),
- dependencyTarget.type == .application {
- buildSettings["TEST_TARGET_NAME"] = dependencyTarget.name
+ 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
}
}
@@ -783,35 +1384,43 @@ public class PBXProjGenerator {
if anyDependencyRequiresObjCLinking {
let otherLinkingFlags = "OTHER_LDFLAGS"
let objCLinking = "-ObjC"
- if var array = buildSettings[otherLinkingFlags] as? [String] {
+ if var array = buildSettings[otherLinkingFlags]?.arrayValue {
array.append(objCLinking)
- buildSettings[otherLinkingFlags] = array
- } else if let string = buildSettings[otherLinkingFlags] as? String {
- buildSettings[otherLinkingFlags] = [string, objCLinking]
+ buildSettings[otherLinkingFlags] = .array(array)
+ } else if let string = buildSettings[otherLinkingFlags]?.stringValue {
+ buildSettings[otherLinkingFlags] = .array([string, objCLinking])
} else {
- buildSettings[otherLinkingFlags] = ["$(inherited)", objCLinking]
+ buildSettings[otherLinkingFlags] = .array(["$(inherited)", objCLinking])
}
}
// set Carthage search paths
let configFrameworkBuildPaths: [String]
if !carthageDependencies.isEmpty {
- let carthagePlatformBuildPath = "$(PROJECT_DIR)/" + getCarthageBuildPath(platform: target.platform)
- configFrameworkBuildPaths = [carthagePlatformBuildPath] + Array(frameworkBuildPaths).sorted()
+ 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 = Array(frameworkBuildPaths).sorted()
+ configFrameworkBuildPaths = frameworkBuildPaths.sorted()
}
// set framework search paths
if !configFrameworkBuildPaths.isEmpty {
let frameworkSearchPaths = "FRAMEWORK_SEARCH_PATHS"
- if var array = buildSettings[frameworkSearchPaths] as? [String] {
+ if var array = buildSettings[frameworkSearchPaths]?.arrayValue {
array.append(contentsOf: configFrameworkBuildPaths)
- buildSettings[frameworkSearchPaths] = array
- } else if let string = buildSettings[frameworkSearchPaths] as? String {
- buildSettings[frameworkSearchPaths] = [string] + configFrameworkBuildPaths
+ buildSettings[frameworkSearchPaths] = .array(array)
+ } else if let string = buildSettings[frameworkSearchPaths]?.stringValue {
+ buildSettings[frameworkSearchPaths] = .array([string] + configFrameworkBuildPaths)
} else {
- buildSettings[frameworkSearchPaths] = ["$(inherited)"] + configFrameworkBuildPaths
+ buildSettings[frameworkSearchPaths] = .array(["$(inherited)"] + configFrameworkBuildPaths)
}
}
@@ -828,88 +1437,170 @@ public class PBXProjGenerator {
return addObject(buildConfig)
}
+ let defaultConfigurationName = project.options.defaultConfig ?? project.configs.first?.name ?? ""
let buildConfigList = addObject(XCConfigurationList(
buildConfigurations: configs,
- defaultConfigurationName: ""
+ 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.product = fileReference
+ 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
+ }
}
- func getInfoPlist(_ sources: [TargetSource]) -> Path? {
- return 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" })
- }
+ 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)
}
- .first
- }
+ }
- func getCarthageBuildPath(platform: Platform) -> String {
+ guard !exceptions.isEmpty else { return }
- let carthagePath = Path(carthageBuildPath)
- let platformName = platform.carthageDirectoryName
- return "\(carthagePath)/\(platformName)"
+ 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
+ }
- func getAllCarthageDependencies(target topLevelTarget: Target) -> [Dependency] {
- // this is used to resolve cyclical target dependencies
- var visitedTargets: Set = []
- var frameworks: [String: Dependency] = [:]
+ let packageDependency = addObject(
+ XCSwiftPackageProductDependency(productName: buildToolPlugin.plugin, package: packageReference, isPlugin: true)
+ )
+ let targetDependency = addObject(
+ PBXTargetDependency(product: packageDependency)
+ )
- var queue: [ProjectTarget] = [topLevelTarget]
- while !queue.isEmpty {
- let projectTarget = queue.removeFirst()
- if visitedTargets.contains(projectTarget.name) {
- continue
+ 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)
}
- if let target = projectTarget as? Target {
- for dependency in target.dependencies {
- // don't overwrite frameworks, to allow top level ones to rule
- if frameworks[dependency.reference] != nil {
- continue
- }
+ // Otherwise check if the path was defined as part of the `info` spec
+ if let value = target.info?.path {
+ return (config, value)
+ }
- switch dependency.type {
- case .carthage:
- frameworks[dependency.reference] = dependency
- case .target:
- if let projectTarget = project.getProjectTarget(dependency.reference) {
- 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)
- }
+ // 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
}
}
- visitedTargets.update(with: projectTarget.name)
+ // Return the default plist if there was one
+ if let value = defaultInfoPlist {
+ return (config, value)
+ }
+ return nil
}
- return frameworks.sorted(by: { $0.key < $1.key }).map { $0.value }
+ 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] {
@@ -927,30 +1618,46 @@ public class PBXProjGenerator {
for dependency in target.dependencies {
// don't overwrite dependencies, to allow top level ones to rule
- if dependencies[dependency.reference] != nil {
+ 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 .framework, .carthage:
- if isTopLevel || dependency.embed == nil {
- dependencies[dependency.reference] = dependency
+ case .sdk:
+ dependencies[dependency.uniqueID] = dependency
+ case .framework, .carthage, .package:
+ if isTopLevel || dependency.embed != true {
+ dependencies[dependency.uniqueID] = dependency
}
case .target:
- if isTopLevel || dependency.embed == nil {
- if let dependencyTarget = project.getTarget(dependency.reference) {
- dependencies[dependency.reference] = dependency
- if !dependencyTarget.shouldEmbedDependencies {
- // traverse target's dependencies if it doesn't embed them itself
- queue.append(dependencyTarget)
+ 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
}
- } else if project.getAggregateTarget(dependency.reference) != nil {
- // Aggregate targets should be included
- dependencies[dependency.reference] = dependency
+ }
+ case .project:
+ if isTopLevel || dependency.embed != true {
+ dependencies[dependency.uniqueID] = dependency
}
}
+ case .bundle:
+ if isTopLevel {
+ dependencies[dependency.uniqueID] = dependency
+ }
}
}
@@ -964,7 +1671,13 @@ public class PBXProjGenerator {
extension Target {
var shouldEmbedDependencies: Bool {
- return type.isApp || type.isTest
+ type.isApp || type.isTest
+ }
+
+ var shouldEmbedCarthageDependencies: Bool {
+ (type.isApp && platform != .watchOS)
+ || type == .watch2Extension
+ || type.isTest
}
}
@@ -972,7 +1685,7 @@ extension Platform {
/// - returns: `true` for platforms that the app store requires simulator slices to be stripped.
public var requiresSimulatorStripping: Bool {
switch self {
- case .iOS, .tvOS, .watchOS:
+ case .auto, .iOS, .tvOS, .watchOS, .visionOS:
return true
case .macOS:
return false
@@ -981,9 +1694,13 @@ extension Platform {
}
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 type(of: self).isa == "PBXGroup" {
+ if self is PBXGroup || self is PBXFileSystemSynchronizedRootGroup {
switch groupSortPosition {
case .top: return -1
case .bottom: return 1
@@ -994,3 +1711,14 @@ extension PBXFileElement {
}
}
}
+
+private extension Dependency {
+ var carthageLinkType: Dependency.CarthageLinkType? {
+ switch type {
+ case .carthage(_, let linkType):
+ return linkType
+ default:
+ return nil
+ }
+ }
+}
diff --git a/Sources/XcodeGenKit/PathExtensions.swift b/Sources/XcodeGenKit/PathExtensions.swift
deleted file mode 100644
index e029844ae..000000000
--- a/Sources/XcodeGenKit/PathExtensions.swift
+++ /dev/null
@@ -1,9 +0,0 @@
-import Foundation
-import PathKit
-
-extension Path {
-
- func byRemovingBase(path: Path) -> Path {
- return Path(normalize().string.replacingOccurrences(of: "\(path.normalize().string)/", with: ""))
- }
-}
diff --git a/Sources/XcodeGenKit/ProjectFormat.swift b/Sources/XcodeGenKit/ProjectFormat.swift
new file mode 100644
index 000000000..0405518f3
--- /dev/null
+++ b/Sources/XcodeGenKit/ProjectFormat.swift
@@ -0,0 +1,37 @@
+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"
+ }
+ }
+}
diff --git a/Sources/XcodeGenKit/ProjectGenerator.swift b/Sources/XcodeGenKit/ProjectGenerator.swift
index b9561e45f..35a4d1bd1 100644
--- a/Sources/XcodeGenKit/ProjectGenerator.swift
+++ b/Sources/XcodeGenKit/ProjectGenerator.swift
@@ -2,7 +2,7 @@ import Foundation
import JSONUtilities
import PathKit
import ProjectSpec
-import xcodeproj
+import XcodeProj
import Yams
public class ProjectGenerator {
@@ -13,243 +13,44 @@ public class ProjectGenerator {
self.project = project
}
- var defaultDebugConfig: Config {
- return project.configs.first { $0.type == .debug }!
- }
+ public func generateXcodeProject(in projectDirectory: Path? = nil, userName: String) throws -> XcodeProj {
- var defaultReleaseConfig: Config {
- return project.configs.first { $0.type == .release }!
- }
+ // generate PBXProj
+ let pbxProjGenerator = PBXProjGenerator(project: project,
+ projectDirectory: projectDirectory)
+ let pbxProj = try pbxProjGenerator.generate()
- public func generateXcodeProject(validate: Bool = true) throws -> XcodeProj {
- if validate {
- try project.validate()
- }
- let pbxProjGenerator = PBXProjGenerator(project: project)
- let pbxProject = try pbxProjGenerator.generate()
+ // generate Workspace
let workspace = try generateWorkspace()
- let sharedData = try generateSharedData(pbxProject: pbxProject)
- return XcodeProj(workspace: workspace, pbxproj: pbxProject, sharedData: sharedData)
- }
-
- func generateWorkspace() throws -> XCWorkspace {
- let dataElement: XCWorkspaceDataElement = .file(XCWorkspaceDataFileRef(location: .self("")))
- let workspaceData = XCWorkspaceData(children: [dataElement])
- return XCWorkspace(data: workspaceData)
- }
-
- func generateScheme(_ scheme: Scheme, pbxProject: PBXProj) throws -> XCScheme {
-
- func getBuildEntry(_ buildTarget: Scheme.BuildTarget) -> XCScheme.BuildAction.Entry {
-
- guard let pbxTarget = pbxProject.targets(named: buildTarget.target).first else {
- fatalError("Unable to find target named \"\(buildTarget.target)\" in \"PBXProj.targets\"")
- }
-
- guard let buildableName =
- project.getTarget(buildTarget.target)?.filename ??
- project.getAggregateTarget(buildTarget.target)?.name else {
- fatalError("Unable to determinate \"buildableName\" for build target: \(buildTarget.target)")
- }
- let buildableReference = XCScheme.BuildableReference(
- referencedContainer: "container:\(project.name).xcodeproj",
- blueprint: pbxTarget,
- buildableName: buildableName,
- blueprintName: buildTarget.target
- )
- return XCScheme.BuildAction.Entry(buildableReference: buildableReference, buildFor: buildTarget.buildTypes)
- }
-
- let testTargetNames = scheme.test?.targets ?? []
- let testBuildTargets = testTargetNames.map {
- Scheme.BuildTarget(target: $0, buildTypes: BuildType.testOnly)
- }
-
- let testBuildTargetEntries = testBuildTargets.map(getBuildEntry)
-
- let buildActionEntries: [XCScheme.BuildAction.Entry] = 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
- return (buildActionEntries + testBuildTargetEntries)
- .first { settingsTarget == $0.buildableReference.blueprintName }?
- .buildableReference
- }
- return XCScheme.ExecutionAction(scriptText: action.script, title: action.name, environmentBuildable: environmentBuildable)
- }
-
- let target = project.getTarget(scheme.build.targets.first!.target)
- let shouldExecuteOnLaunch = target?.type.isExecutable == true
-
- let buildableReference = buildActionEntries.first!.buildableReference
- let productRunable = XCScheme.BuildableProductRunnable(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
- )
-
- let testables = testBuildTargetEntries.map {
- XCScheme.TestableReference(skipped: false, buildableReference: $0.buildableReference)
- }
-
- 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 }
+ // generate Schemes
+ let schemeGenerator = SchemeGenerator(project: project, pbxProj: pbxProj)
+ let (sharedSchemes, userSchemes, schemeManagement) = try schemeGenerator.generateSchemes()
- let testAction = XCScheme.TestAction(
- buildConfiguration: scheme.test?.config ?? defaultDebugConfig.name,
- macroExpansion: buildableReference,
- testables: testables,
- preActions: scheme.test?.preActions.map(getExecutionAction) ?? [],
- postActions: scheme.test?.postActions.map(getExecutionAction) ?? [],
- shouldUseLaunchSchemeArgsEnv: scheme.test?.shouldUseLaunchSchemeArgsEnv ?? true,
- codeCoverageEnabled: scheme.test?.gatherCoverageData ?? false,
- commandlineArguments: testCommandLineArgs,
- environmentVariables: testVariables
- )
-
- let launchAction = XCScheme.LaunchAction(
- buildableProductRunnable: shouldExecuteOnLaunch ? productRunable : nil,
- buildConfiguration: scheme.run?.config ?? defaultDebugConfig.name,
- preActions: scheme.run?.preActions.map(getExecutionAction) ?? [],
- postActions: scheme.run?.postActions.map(getExecutionAction) ?? [],
- macroExpansion: shouldExecuteOnLaunch ? nil : buildableReference,
- commandlineArguments: launchCommandLineArgs,
- environmentVariables: launchVariables
- )
+ // generate Breakpoints
+ let breakpointGenerator = BreakpointGenerator(project: project)
+ let xcbreakpointlist = try breakpointGenerator.generateBreakpointList()
- let profileAction = XCScheme.ProfileAction(
- buildableProductRunnable: productRunable,
- buildConfiguration: scheme.profile?.config ?? defaultReleaseConfig.name,
- preActions: scheme.profile?.preActions.map(getExecutionAction) ?? [],
- postActions: scheme.profile?.postActions.map(getExecutionAction) ?? [],
- shouldUseLaunchSchemeArgsEnv: scheme.profile?.shouldUseLaunchSchemeArgsEnv ?? true,
- commandlineArguments: profileCommandLineArgs,
- environmentVariables: profileVariables
- )
-
- let analyzeAction = XCScheme.AnalyzeAction(buildConfiguration: scheme.analyze?.config ?? defaultDebugConfig.name)
+ // generate shared data
+ let sharedData = XCSharedData(schemes: sharedSchemes, breakpoints: xcbreakpointlist)
- 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) ?? []
- )
+ // generate user data
+ let userData = userSchemes.isEmpty && schemeManagement == nil ? [] : [
+ XCUserData(userName: userName, schemes: userSchemes, schemeManagement: schemeManagement)
+ ]
- return XCScheme(
- name: scheme.name,
- lastUpgradeVersion: project.xcodeVersion,
- version: project.schemeVersion,
- buildAction: buildAction,
- testAction: testAction,
- launchAction: launchAction,
- profileAction: profileAction,
- analyzeAction: analyzeAction,
- archiveAction: archiveAction
+ return XcodeProj(
+ workspace: workspace,
+ pbxproj: pbxProj,
+ sharedData: sharedData,
+ userData: userData
)
}
- func generateSharedData(pbxProject: PBXProj) throws -> XCSharedData {
- var xcschemes: [XCScheme] = []
-
- for scheme in project.schemes {
- let xcscheme = try generateScheme(scheme, pbxProject: pbxProject)
- xcschemes.append(xcscheme)
- }
-
- for target in project.targets {
- if let targetScheme = target.scheme {
-
- if targetScheme.configVariants.isEmpty {
- let schemeName = target.name
-
- let debugConfig = project.configs.first { $0.type == .debug }!
- let releaseConfig = project.configs.first { $0.type == .release }!
-
- let scheme = Scheme(
- name: schemeName,
- target: target,
- targetScheme: targetScheme,
- debugConfig: debugConfig.name,
- releaseConfig: releaseConfig.name
- )
- let xcscheme = try generateScheme(scheme, pbxProject: pbxProject)
- xcschemes.append(xcscheme)
- } else {
- for configVariant in targetScheme.configVariants {
-
- let schemeName = "\(target.name) \(configVariant)"
-
- let debugConfig = project.configs
- .first { $0.type == .debug && $0.name.contains(configVariant) }!
- let releaseConfig = project.configs
- .first { $0.type == .release && $0.name.contains(configVariant) }!
-
- let scheme = Scheme(
- name: schemeName,
- target: target,
- targetScheme: targetScheme,
- debugConfig: debugConfig.name,
- releaseConfig: releaseConfig.name
- )
- let xcscheme = try generateScheme(scheme, pbxProject: pbxProject)
- xcschemes.append(xcscheme)
- }
- }
- }
- }
-
- return XCSharedData(schemes: xcschemes)
- }
-}
-
-extension Scheme {
- public init(name: String, target: Target, targetScheme: TargetScheme, debugConfig: String, releaseConfig: String) {
- self.init(
- name: name,
- build: .init(targets: [Scheme.BuildTarget(target: target.name)]),
- run: .init(
- config: debugConfig,
- commandLineArguments: targetScheme.commandLineArguments,
- preActions: targetScheme.preActions,
- postActions: targetScheme.postActions,
- environmentVariables: targetScheme.environmentVariables
- ),
- test: .init(
- config: debugConfig,
- gatherCoverageData: targetScheme.gatherCoverageData,
- commandLineArguments: targetScheme.commandLineArguments,
- targets: targetScheme.testTargets,
- preActions: targetScheme.preActions,
- postActions: targetScheme.postActions,
- environmentVariables: targetScheme.environmentVariables
- ),
- profile: .init(
- config: releaseConfig,
- commandLineArguments: targetScheme.commandLineArguments,
- preActions: targetScheme.preActions,
- postActions: targetScheme.postActions,
- environmentVariables: targetScheme.environmentVariables
- ),
- analyze: .init(
- config: debugConfig
- ),
- archive: .init(
- config: releaseConfig,
- preActions: targetScheme.preActions,
- postActions: targetScheme.postActions
- )
- )
+ func generateWorkspace() throws -> XCWorkspace {
+ let selfReference = XCWorkspaceDataFileRef(location: .current(""))
+ let dataElement = XCWorkspaceDataElement.file(selfReference)
+ let workspaceData = XCWorkspaceData(children: [dataElement])
+ return XCWorkspace(data: workspaceData)
}
}
diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift
new file mode 100644
index 000000000..c29faac4b
--- /dev/null
+++ b/Sources/XcodeGenKit/SchemeGenerator.swift
@@ -0,0 +1,574 @@
+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
+ }
+ }
+}
diff --git a/Sources/XcodeGenKit/SettingPresets b/Sources/XcodeGenKit/SettingPresets
new file mode 120000
index 000000000..1137a5ea8
--- /dev/null
+++ b/Sources/XcodeGenKit/SettingPresets
@@ -0,0 +1 @@
+../../SettingPresets/
\ No newline at end of file
diff --git a/Sources/XcodeGenKit/SettingsBuilder.swift b/Sources/XcodeGenKit/SettingsBuilder.swift
index d63cf4f29..573c960dd 100644
--- a/Sources/XcodeGenKit/SettingsBuilder.swift
+++ b/Sources/XcodeGenKit/SettingsBuilder.swift
@@ -2,7 +2,7 @@ import Foundation
import JSONUtilities
import PathKit
import ProjectSpec
-import xcodeproj
+import XcodeProj
import Yams
extension Project {
@@ -10,19 +10,26 @@ 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.all {
+ for platform in Platform.allCases {
if let version = options.deploymentTarget.version(for: platform) {
- buildSettings[platform.deploymentTargetSetting] = version.deploymentTarget
+ buildSettings[platform.deploymentTargetSetting] = .string(version.deploymentTarget)
}
}
- // Prevent setting presets from overrwriting settings in project xcconfig files
+ // Prevent setting presets from overwriting settings in project xcconfig files
if let configPath = configFiles[config.name] {
buildSettings = removeConfigFileSettings(from: buildSettings, configPath: configPath)
}
@@ -34,16 +41,63 @@ extension Project {
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 {
- buildSettings += SettingsPresetFile.platform(target.platform).getBuildSettings()
+ 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, target.platform).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 {
- buildSettings[target.platform.deploymentTargetSetting] = version.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
@@ -72,8 +126,13 @@ extension Project {
buildSettings += settings.buildSettings
for (configVariant, settings) in settings.configSettings {
- if config.name.lowercased().contains(configVariant.lowercased()) {
- buildSettings += getBuildSettings(settings: settings, config: config)
+ 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)
+ }
}
}
@@ -81,7 +140,7 @@ extension Project {
}
// combines all levels of a target's settings: target, target config, project, project config
- public func getCombinedBuildSetting(_ setting: String, target: ProjectTarget, config: Config) -> Any? {
+ 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
@@ -100,8 +159,12 @@ extension Project {
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 {
- return getCombinedBuildSetting(setting, target: target, config: config) != nil
+ getCombinedBuildSetting(setting, target: target, config: config) != nil
}
/// Removes values from build settings if they are defined in an xcconfig file
@@ -169,15 +232,22 @@ extension SettingsPresetFile {
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:
+ case .base, .config, .platform, .supportedDestination:
print("No \"\(name)\" settings found")
case .product, .productPlatform:
break
@@ -186,11 +256,57 @@ extension SettingsPresetFile {
return nil
}
- guard let buildSettings = try? loadYamlDictionary(path: settingsPath) else {
+ 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
+ }()
+}
diff --git a/Sources/XcodeGenKit/SettingsPresetFile.swift b/Sources/XcodeGenKit/SettingsPresetFile.swift
index 5122bc961..30a1f4103 100644
--- a/Sources/XcodeGenKit/SettingsPresetFile.swift
+++ b/Sources/XcodeGenKit/SettingsPresetFile.swift
@@ -1,10 +1,11 @@
import Foundation
import ProjectSpec
-import xcodeproj
+import XcodeProj
public enum SettingsPresetFile {
case config(ConfigType)
case platform(Platform)
+ case supportedDestination(SupportedDestination)
case product(PBXProductType)
case productPlatform(PBXProductType, Platform)
case base
@@ -13,6 +14,7 @@ public enum SettingsPresetFile {
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"
@@ -23,6 +25,7 @@ public enum SettingsPresetFile {
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"
diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift
index cf2f23c00..59b285887 100644
--- a/Sources/XcodeGenKit/SourceGenerator.swift
+++ b/Sources/XcodeGenKit/SourceGenerator.swift
@@ -1,63 +1,147 @@
import Foundation
import PathKit
import ProjectSpec
-import xcodeproj
+import XcodeProj
+import XcodeGenCore
struct SourceFile {
let path: Path
let fileReference: PBXFileElement
let buildFile: PBXBuildFile
- let buildPhase: TargetSource.BuildPhase?
+ 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
- var addObjectClosure: (PBXObject) -> Void
- var targetSourceExcludePaths: Set = []
- var defaultExcludedFiles = [
+ let pbxProj: PBXProj
+
+ private var defaultExcludedFiles = [
".DS_Store",
]
-
- var targetName: String = ""
+ private let defaultExcludedExtensions = [
+ "orig",
+ ]
private(set) var knownRegions: Set = []
- init(project: Project, addObjectClosure: @escaping (PBXObject) -> Void) {
+ /// 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.addObjectClosure = addObjectClosure
+ self.pbxProj = pbxProj
+ self.projectDirectory = projectDirectory
}
- func addObject(_ object: T) -> T {
- addObjectClosure(object)
+ 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 getAllSourceFiles(targetType: PBXProductType, sources: [TargetSource]) throws -> [SourceFile] {
- return try sources.flatMap { try getSourceFiles(targetType: targetType, targetSource: $0, path: project.basePath + $0.path) }
+ 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 {
- let fullPath = project.basePath + path
- _ = try getSourceFiles(targetType: .none, targetSource: TargetSource(path: path), path: fullPath)
+ _ = try getSourceFiles(targetType: .none, targetSource: TargetSource(path: path), buildPhases: [:])
}
- func generateSourceFile(targetType: PBXProductType, targetSource: TargetSource, path: Path, buildPhase: TargetSource.BuildPhase? = nil) -> SourceFile {
- let fileReference = fileReferencesByPath[path.string.lowercased()]!
- var settings: [String: Any] = [:]
- var chosenBuildPhase: TargetSource.BuildPhase?
+ 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 = buildPhase {
+ if let buildPhase = targetSource.buildPhase {
chosenBuildPhase = buildPhase
- } else if let buildPhase = targetSource.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)
@@ -67,9 +151,10 @@ class SourceGenerator {
// Static libraries don't support the header build phase
// For public headers they need to be copied
if headerVisibility == .public {
- chosenBuildPhase = .copyFiles(TargetSource.BuildPhase.CopyFilesSettings(
+ chosenBuildPhase = .copyFiles(BuildPhaseSpec.CopyFilesSettings(
destination: .productsDirectory,
- subpath: "include/$(PRODUCT_NAME)"
+ subpath: "include/$(PRODUCT_NAME)",
+ phaseOrder: .preCompile
))
} else {
chosenBuildPhase = nil
@@ -79,14 +164,36 @@ class SourceGenerator {
if chosenBuildPhase == .headers {
if headerVisibility != .project {
// Xcode doesn't write the default of project
- settings["ATTRIBUTES"] = [headerVisibility.settingName]
+ attributes.append(headerVisibility.settingName)
}
}
- if chosenBuildPhase == .sources && targetSource.compilerFlags.count > 0 {
- settings["COMPILER_FLAGS"] = targetSource.compilerFlags.joined(separator: " ")
+
+ 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)
}
- let buildFile = PBXBuildFile(file: fileReference, settings: settings.isEmpty ? nil : settings)
+ 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,
@@ -99,11 +206,18 @@ class SourceGenerator {
let createIntermediateGroups = project.options.createIntermediateGroups
let parentPath = path.parent()
+
+ guard !isInsideSyncedFolder(path: path) else {
+ return getFileReference(path: path, inPath: basePath, sourceTree: .sourceRoot)
+ }
+
let fileReference = getFileReference(path: path, inPath: parentPath)
+
let parentGroup = getGroup(
path: parentPath,
mergingChildren: [fileReference],
createIntermediateGroups: createIntermediateGroups,
+ hasCustomParent: false,
isBaseGroup: true
)
@@ -118,7 +232,7 @@ class SourceGenerator {
if let fileReference = fileReferencesByPath[fileReferenceKey] {
return fileReference
} else {
- let fileReferencePath = path.byRemovingBase(path: inPath)
+ let fileReferencePath = (try? path.relativePath(from: inPath)) ?? path
var fileReferenceName: String? = name ?? fileReferencePath.lastComponent
if fileReferencePath.string == fileReferenceName {
fileReferenceName = nil
@@ -147,7 +261,7 @@ class SourceGenerator {
// order by taking the last item in the sortedPaths array
let currentVersionPath = findCurrentCoreDataModelVersionPath(using: versionedModels) ?? sortedPaths.last
let currentVersion: PBXFileReference? = {
- guard let indexOf = sortedPaths.index(where: { $0 == currentVersionPath }) else { return nil }
+ guard let indexOf = sortedPaths.firstIndex(where: { $0 == currentVersionPath }) else { return nil }
return modelFileReferences[indexOf]
}()
let versionGroup = addObject(XCVersionGroup(
@@ -175,25 +289,42 @@ class SourceGenerator {
}
}
+ /// Whether the given path falls inside a target source configured as a synced folder.
+ /// Checks the project spec directly because configFiles are resolved before target sources
+ /// populate `syncedGroupsByPath`.
+ private func isInsideSyncedFolder(path: Path) -> Bool {
+ let relativePath = (try? path.relativePath(from: basePath)) ?? path
+ return project.targets.contains { target in
+ target.sources.contains { source in
+ let type = source.type ?? (project.options.defaultSourceDirectoryType ?? .group)
+ return type == .syncedFolder && relativePath.string.hasPrefix(source.path + "/")
+ }
+ }
+ }
+
/// returns a default build phase for a given path. This is based off the filename
- private func getDefaultBuildPhase(for path: Path, targetType: PBXProductType) -> TargetSource.BuildPhase? {
- if path.lastComponent == "Info.plist" {
- return nil
+ 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 "swift", "m", "mm", "cpp", "c", "cc", "S", "xcdatamodeld", "metal": return .sources
- case "h", "hh", "hpp", "ipp", "tpp", "hxx", "def": return .headers
case "modulemap":
guard targetType == .staticLibrary else { return nil }
- return .copyFiles(TargetSource.BuildPhase.CopyFilesSettings(
+ return .copyFiles(BuildPhaseSpec.CopyFilesSettings(
destination: .productsDirectory,
- subpath: "include/$(PRODUCT_NAME)"
+ subpath: "include/$(PRODUCT_NAME)",
+ phaseOrder: .preCompile
))
- case "framework": return .frameworks
- case "xpc": return .copyFiles(.xpcServices)
- case "xcconfig", "entitlements", "gpx", "lproj", "apns": return nil
- default: return .resources
+ 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
@@ -201,32 +332,40 @@ class SourceGenerator {
/// 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, isBaseGroup: Bool) -> PBXGroup {
+ 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
- if !cachedGroup.children.contains(child) {
- cachedGroup.children.append(child)
+ // 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 = isOutOfBasePath || path.parent() == project.basePath
+ let isRootPath = (isBaseGroup && isOutOfBasePath && isParentOfBasePath) || path.parent() == project.basePath
// is a top level group in the project
- let isTopLevelGroup = (isBaseGroup && !createIntermediateGroups) || isRootPath
+ let isTopLevelGroup = !hasCustomParent && ((isBaseGroup && !createIntermediateGroups) || isRootPath || isParentOfBasePath)
let groupName = name ?? path.lastComponent
- let groupPath = isTopLevelGroup ?
- path.byRemovingBase(path: project.basePath).string :
- path.lastComponent
+
+ let groupPath = resolveGroupPath(path, isTopLevelGroup: hasCustomParent || isTopLevelGroup)
+
let group = PBXGroup(
children: children,
sourceTree: .group,
@@ -236,7 +375,7 @@ class SourceGenerator {
groupReference = addObject(group)
groupsByPath[path] = groupReference
- if isTopLevelGroup {
+ if isTopLevelGroup && !isInsideSyncedFolder(path: path) {
rootGroups.insert(groupReference)
}
}
@@ -259,13 +398,50 @@ class SourceGenerator {
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 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 getSourceExcludes(targetSource: TargetSource) -> Set {
+ private func getSourceMatches(targetSource: TargetSource, patterns: [String]) -> Set {
let rootSourcePath = project.basePath + targetSource.path
return Set(
- targetSource.excludes.map {
- Path.glob("\(rootSourcePath)/\($0)")
+ patterns.parallelMap { pattern in
+ guard !pattern.isEmpty else { return [] }
+ return Glob(pattern: "\(rootSourcePath)/\(pattern)")
+ .map { Path($0) }
.map {
guard $0.isDirectory else {
return [$0]
@@ -279,21 +455,52 @@ class SourceGenerator {
)
}
+ /// 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) -> Bool {
- return !defaultExcludedFiles.contains(where: { path.lastComponent.contains($0) })
- && !targetSourceExcludePaths.contains(path)
+ 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) throws -> [Path] {
- return try dirPath.children()
+ 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().filter(isIncludedPath)
- return !children.isEmpty
+ 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 isIncludedPath($0)
+ return self.isIncludedPath($0, excludePaths: excludePaths, includePaths: includePaths)
} else {
return false
}
@@ -301,71 +508,114 @@ class SourceGenerator {
}
/// 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)
- throws -> (sourceFiles: [SourceFile], groups: [PBXGroup]) {
-
- let children = try getSourceChildren(targetSource: targetSource, dirPath: path)
-
- let directories = children
- .filter { $0.isDirectory && $0.extension == nil && $0.extension != "lproj" }
+ 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 = children
- .filter { $0.isFile || $0.extension != nil && $0.extension != "lproj" }
+ 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)
+ 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)
- guard !subGroups.sourceFiles.isEmpty else {
+ 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
- guard let firstGroup = subGroups.groups.first else {
- continue
+ if let firstGroup = subGroups.groups.first {
+ groupChildren.append(firstGroup)
+ groups += subGroups.groups
+ } else if project.options.generateEmptyDirectories {
+ groups += subGroups.groups
}
-
- groupChildren.append(firstGroup)
- groups += subGroups.groups
}
// find the base localised directory
let baseLocalisedDirectory: Path? = {
func findLocalisedDirectory(by languageId: String) -> Path? {
- return localisedDirectories.first { $0.lastComponent == "\(languageId).lproj" }
+ 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 {
- for filePath in try baseLocalisedDirectory.children()
- .filter(isIncludedPath)
- .sorted() {
+ 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 = SourceFile(
- path: filePath,
- fileReference: variantGroup,
- buildFile: PBXBuildFile(file: variantGroup),
- buildPhase: .resources
- )
+ let sourceFile = generateSourceFile(targetType: targetType,
+ targetSource: targetSource,
+ path: filePath,
+ fileReference: variantGroup,
+ buildPhases: buildPhases)
allSourceFiles.append(sourceFile)
}
}
@@ -373,9 +623,10 @@ class SourceGenerator {
// add references to localised resources into base localisation variant groups
for localisedDirectory in localisedDirectories {
let localisationName = localisedDirectory.lastComponentWithoutExtension
- for filePath in try localisedDirectory.children()
- .filter(isIncludedPath)
- .sorted { $0.lastComponent < $1.lastComponent } {
+ 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
@@ -398,12 +649,11 @@ class SourceGenerator {
}
} else {
// add SourceFile to group if there is no Base.lproj directory
- let sourceFile = SourceFile(
- path: filePath,
- fileReference: fileReference,
- buildFile: PBXBuildFile(file: fileReference),
- buildPhase: .resources
- )
+ let sourceFile = generateSourceFile(targetType: targetType,
+ targetSource: targetSource,
+ path: filePath,
+ fileReference: fileReference,
+ buildPhases: buildPhases)
allSourceFiles.append(sourceFile)
groupChildren.append(fileReference)
}
@@ -413,10 +663,11 @@ class SourceGenerator {
let group = getGroup(
path: path,
mergingChildren: groupChildren,
- createIntermediateGroups: project.options.createIntermediateGroups,
+ createIntermediateGroups: createIntermediateGroups,
+ hasCustomParent: hasCustomParent,
isBaseGroup: isBaseGroup
)
- if project.options.createIntermediateGroups {
+ if createIntermediateGroups {
createIntermediaGroups(for: group, at: path)
}
@@ -425,40 +676,39 @@ class SourceGenerator {
}
/// creates source files
- private func getSourceFiles(targetType: PBXProductType, targetSource: TargetSource, path: Path) throws -> [SourceFile] {
+ private func getSourceFiles(targetType: PBXProductType, targetSource: TargetSource, buildPhases: [Path: BuildPhaseSpec]) throws -> [SourceFile] {
// generate excluded paths
- targetSourceExcludePaths = getSourceExcludes(targetSource: targetSource)
+ 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 = targetSource.type ?? (path.isFile || path.extension != nil ? .file : .group)
- let createIntermediateGroups = project.options.createIntermediateGroups
+ 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 folderPath = Path(targetSource.path)
let fileReference = getFileReference(
- path: folderPath,
+ path: path,
inPath: project.basePath,
- name: targetSource.name ?? folderPath.lastComponent,
+ name: targetSource.name ?? path.lastComponent,
sourceTree: .sourceRoot,
lastKnownFileType: "folder"
)
- if !createIntermediateGroups || path.parent() == project.basePath {
+ if !(createIntermediateGroups || hasCustomParent) || path.parent() == project.basePath {
rootGroups.insert(fileReference)
}
- let buildPhase: TargetSource.BuildPhase?
- if let targetBuildPhase = targetSource.buildPhase {
- buildPhase = targetBuildPhase
- } else {
- buildPhase = .resources
- }
-
- let sourceFile = generateSourceFile(targetType: targetType, targetSource: targetSource, path: folderPath, buildPhase: buildPhase)
+ let sourceFile = generateSourceFile(targetType: targetType, targetSource: targetSource, path: path, buildPhases: buildPhases)
sourceFiles.append(sourceFile)
sourceReference = fileReference
@@ -466,21 +716,45 @@ class SourceGenerator {
let parentPath = path.parent()
let fileReference = getFileReference(path: path, inPath: parentPath, name: targetSource.name)
- let sourceFile = generateSourceFile(targetType: targetType, targetSource: targetSource, path: path)
+ let sourceFile = generateSourceFile(targetType: targetType, targetSource: targetSource, path: path, buildPhases: buildPhases)
- if parentPath == project.basePath {
+ 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, isBaseGroup: true)
+ let parentGroup = getGroup(
+ path: parentPath,
+ mergingChildren: [fileReference],
+ createIntermediateGroups: createIntermediateGroups,
+ hasCustomParent: hasCustomParent,
+ isBaseGroup: true
+ )
sourcePath = parentPath
sourceReference = parentGroup
}
sourceFiles.append(sourceFile)
case .group:
- let (groupSourceFiles, groups) = try getGroupSources(targetType: targetType, targetSource: targetSource, path: path, isBaseGroup: true)
+ 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
@@ -488,39 +762,174 @@ class SourceGenerator {
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 createIntermediateGroups {
+ 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 && path.string.contains(project.basePath.string) else {
- // we've reached the top or are out of the root directory
+ guard parentPath != project.basePath else {
+ // we've reached the top
return
}
let hasParentGroup = groupsByPath[parentPath] != nil
- let parentGroup = getGroup(path: parentPath, mergingChildren: [fileElement], createIntermediateGroups: true, isBaseGroup: false)
+ 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 {
+ let versionString = plist["_XCCurrentVersionName"] as? String else {
return nil
}
return versionedModels.first(where: { $0.lastComponent == versionString })
diff --git a/Sources/XcodeGenKit/StringCatalogDecoding.swift b/Sources/XcodeGenKit/StringCatalogDecoding.swift
new file mode 100644
index 000000000..da66c2596
--- /dev/null
+++ b/Sources/XcodeGenKit/StringCatalogDecoding.swift
@@ -0,0 +1,82 @@
+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)
+ })
+ }
+}
diff --git a/Sources/XcodeGenKit/Version.swift b/Sources/XcodeGenKit/Version.swift
index 7fdeee10e..b5ba54d48 100644
--- a/Sources/XcodeGenKit/Version.swift
+++ b/Sources/XcodeGenKit/Version.swift
@@ -3,18 +3,32 @@ import ProjectSpec
extension Project {
- var xcodeVersion: String {
- return XCodeVersion.parse(options.xcodeVersion ?? "9.3")
+ public var xcodeVersion: String {
+ XCodeVersion.parse(options.xcodeVersion ?? "14.3")
+ }
+
+ public var projectFormat: ProjectFormat {
+ options.projectFormat.flatMap(ProjectFormat.init) ?? .default
}
var schemeVersion: String {
- return "1.3"
+ "1.7"
}
- public func validateMinimumXcodeGenVersion(_ xcodeGenVersion: Version) throws {
- if let minimumXcodeGenVersion = options.minimumXcodeGenVersion, xcodeGenVersion < minimumXcodeGenVersion {
- throw SpecValidationError.ValidationError.invalidXcodeGenVersion(minimumVersion: minimumXcodeGenVersion, version: xcodeGenVersion)
- }
+ var compatibilityVersion: String? {
+ projectFormat.compatibilityVersion
+ }
+
+ var objectVersion: UInt {
+ projectFormat.objectVersion
+ }
+
+ var preferredProjectObjectVersion: UInt? {
+ projectFormat.preferredProjectObjectVersion
+ }
+
+ var minimizedProjectReferenceProxies: Int {
+ 1
}
}
diff --git a/Sources/XcodeGenKit/XCProjExtensions.swift b/Sources/XcodeGenKit/XCProjExtensions.swift
index df4390847..fb9509053 100644
--- a/Sources/XcodeGenKit/XCProjExtensions.swift
+++ b/Sources/XcodeGenKit/XCProjExtensions.swift
@@ -1,12 +1,20 @@
import Foundation
-import xcodeproj
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 {
@@ -23,13 +31,15 @@ extension PBXProj {
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 ")
+ string += "\n 📁 " + printGroup(group: group).replacingOccurrences(of: "\n ", with: "\n ")
} else if let fileReference = child as? PBXFileReference {
- string += "\n 📄 " + fileReference.nameOrPath
+ string += "\n 📄 " + fileReference.nameOrPath
} else if let variantGroup = child as? PBXVariantGroup {
- string += "\n 🌎 " + variantGroup.nameOrPath
+ string += "\n 🌎 " + variantGroup.nameOrPath
} else if let versionGroup = child as? XCVersionGroup {
- string += "\n 🔢 " + versionGroup.nameOrPath
+ string += "\n 🔢 " + versionGroup.nameOrPath
+ } else if let syncedFolder = child as? PBXFileSystemSynchronizedRootGroup {
+ string += "\n 📁 " + syncedFolder.nameOrPath
}
}
return string
@@ -38,14 +48,33 @@ extension PBXProj {
extension Dictionary {
- public var valueArray: Array {
- return Array(values)
+ public var valueArray: [Value] {
+ Array(values)
}
}
extension Xcode {
- public static func fileType(path: Path) -> String? {
- return path.extension.flatMap { Xcode.filetype(extension: $0) }
+ 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
}
}
diff --git a/Tests/FixtureTests/FixtureTests.swift b/Tests/FixtureTests/FixtureTests.swift
new file mode 100644
index 000000000..c638098f7
--- /dev/null
+++ b/Tests/FixtureTests/FixtureTests.swift
@@ -0,0 +1,35 @@
+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()
+}
diff --git a/Tests/Fixtures/CarthageProject/.gitignore b/Tests/Fixtures/CarthageProject/.gitignore
new file mode 100644
index 000000000..598b8c5fc
--- /dev/null
+++ b/Tests/Fixtures/CarthageProject/.gitignore
@@ -0,0 +1,5 @@
+Carthage/Build/iOS
+Carthage/Build/watchOS
+Carthage/Build/tvOS
+Carthage/Build/Mac
+Carthage/Checkouts
diff --git a/Tests/Fixtures/CarthageProject/Cartfile b/Tests/Fixtures/CarthageProject/Cartfile
new file mode 100644
index 000000000..688500517
--- /dev/null
+++ b/Tests/Fixtures/CarthageProject/Cartfile
@@ -0,0 +1,4 @@
+github "antitypical/Result"
+github "rpassis/CarthageTestFixture"
+github "ReactiveCocoa/ReactiveSwift" ~> 4.0
+github "ReactiveCocoa/ReactiveCocoa" ~> 8.0
diff --git a/Tests/Fixtures/CarthageProject/Cartfile.resolved b/Tests/Fixtures/CarthageProject/Cartfile.resolved
new file mode 100644
index 000000000..eb4bb7cc4
--- /dev/null
+++ b/Tests/Fixtures/CarthageProject/Cartfile.resolved
@@ -0,0 +1,4 @@
+github "ReactiveCocoa/ReactiveCocoa" "8.0.2"
+github "ReactiveCocoa/ReactiveSwift" "4.0.0"
+github "antitypical/Result" "4.1.0"
+github "rpassis/CarthageTestFixture" "1.0"
diff --git a/Tests/Fixtures/CarthageProject/Carthage/Build/.Alamofire.version b/Tests/Fixtures/CarthageProject/Carthage/Build/.Alamofire.version
new file mode 100644
index 000000000..4b1014a41
--- /dev/null
+++ b/Tests/Fixtures/CarthageProject/Carthage/Build/.Alamofire.version
@@ -0,0 +1,27 @@
+{
+ "Mac" : [
+ {
+ "name" : "Alamofire",
+ "hash" : "c8ee4910ecef2738eccd34a7abcae7d6561c0d709fbc301f660391018bf654b5"
+ }
+ ],
+ "watchOS" : [
+ {
+ "name" : "Alamofire",
+ "hash" : "960f25b2b83d186bf62a05e59af8a6f7c99ac20201f6d948106c52917f29b7da"
+ }
+ ],
+ "tvOS" : [
+ {
+ "name" : "Alamofire",
+ "hash" : "b8994794ee1378f98ceb7d294c84990d60fdabcc5f5a7d8386e71315ff583f1f"
+ }
+ ],
+ "commitish" : "4.7.2",
+ "iOS" : [
+ {
+ "name" : "Alamofire",
+ "hash" : "a000ec6f95d6fd12843007ba8c22e69c64e922b45ccfe34e1d38200b54e5e391"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Tests/Fixtures/CarthageProject/Carthage/Build/.CarthageTestFixture.version b/Tests/Fixtures/CarthageProject/Carthage/Build/.CarthageTestFixture.version
new file mode 100644
index 000000000..f41f6d5e7
--- /dev/null
+++ b/Tests/Fixtures/CarthageProject/Carthage/Build/.CarthageTestFixture.version
@@ -0,0 +1,59 @@
+{
+ "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"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Tests/Fixtures/CarthageProject/Carthage/Build/.ReactiveCocoa.version b/Tests/Fixtures/CarthageProject/Carthage/Build/.ReactiveCocoa.version
new file mode 100644
index 000000000..b57d861c5
--- /dev/null
+++ b/Tests/Fixtures/CarthageProject/Carthage/Build/.ReactiveCocoa.version
@@ -0,0 +1,63 @@
+{
+ "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"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Tests/Fixtures/CarthageProject/Carthage/Build/.ReactiveSwift.version b/Tests/Fixtures/CarthageProject/Carthage/Build/.ReactiveSwift.version
new file mode 100644
index 000000000..8681d9e84
--- /dev/null
+++ b/Tests/Fixtures/CarthageProject/Carthage/Build/.ReactiveSwift.version
@@ -0,0 +1,27 @@
+{
+ "Mac" : [
+ {
+ "name" : "ReactiveSwift",
+ "hash" : "ed8de3ce3e03c940ba43f99c5de552b9af89c2d850bb8afb6e5f9f493489f45d"
+ }
+ ],
+ "watchOS" : [
+ {
+ "name" : "ReactiveSwift",
+ "hash" : "c015d0bc75885b9358870ef16cb9e67b5ef8bcaa3a5ec90d513d623a1091f7eb"
+ }
+ ],
+ "tvOS" : [
+ {
+ "name" : "ReactiveSwift",
+ "hash" : "4d4c3af69ff5b35bec1b76638989b29cb2d785536b2e124c5cfd6660d3eb2640"
+ }
+ ],
+ "commitish" : "4.0.0",
+ "iOS" : [
+ {
+ "name" : "ReactiveSwift",
+ "hash" : "bdf6f01656281b630b9f323625fc5134bb4fe4e061ac1885e3dc6498f9cad7dc"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Tests/Fixtures/CarthageProject/Carthage/Build/.Result.version b/Tests/Fixtures/CarthageProject/Carthage/Build/.Result.version
new file mode 100644
index 000000000..cf16b3560
--- /dev/null
+++ b/Tests/Fixtures/CarthageProject/Carthage/Build/.Result.version
@@ -0,0 +1,27 @@
+{
+ "Mac" : [
+ {
+ "name" : "Result",
+ "hash" : "a608337c467d3ef16f53035c587dab84152fc14b3b4c8b597c5a973d3bfd0d63"
+ }
+ ],
+ "watchOS" : [
+ {
+ "name" : "Result",
+ "hash" : "24680c05790afb8bd5d1ae8f1960905210546943e13fdea8e2994c2f8efdc0a5"
+ }
+ ],
+ "tvOS" : [
+ {
+ "name" : "Result",
+ "hash" : "dca6d7e70e25cfa2b3363a0839dd13271b4da374f22d6cd357cf45b39299c17b"
+ }
+ ],
+ "commitish" : "4.1.0",
+ "iOS" : [
+ {
+ "name" : "Result",
+ "hash" : "5ad79b86004be535ee374685fbbd95db8d91f8c0458d3534aacf426930c7bbf6"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Tests/Fixtures/CarthageProject/Project.xcodeproj/project.pbxproj b/Tests/Fixtures/CarthageProject/Project.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..b1cbbabc8
--- /dev/null
+++ b/Tests/Fixtures/CarthageProject/Project.xcodeproj/project.pbxproj
@@ -0,0 +1,748 @@
+// !$*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 */;
+}
diff --git a/Tests/Fixtures/CarthageProject/Project.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Tests/Fixtures/CarthageProject/Project.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..919434a62
--- /dev/null
+++ b/Tests/Fixtures/CarthageProject/Project.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Tests/Fixtures/CarthageProject/project.yml b/Tests/Fixtures/CarthageProject/project.yml
new file mode 100644
index 000000000..9e315df93
--- /dev/null
+++ b/Tests/Fixtures/CarthageProject/project.yml
@@ -0,0 +1,13 @@
+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
diff --git a/Tests/Fixtures/SPM/FooFeature/Package.swift b/Tests/Fixtures/SPM/FooFeature/Package.swift
new file mode 100644
index 000000000..2540293c8
--- /dev/null
+++ b/Tests/Fixtures/SPM/FooFeature/Package.swift
@@ -0,0 +1,20 @@
+// 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")
+ ]
+)
diff --git a/Tests/Fixtures/SPM/FooFeature/Sources/FooDomain/FooDomain.swift b/Tests/Fixtures/SPM/FooFeature/Sources/FooDomain/FooDomain.swift
new file mode 100644
index 000000000..580e98166
--- /dev/null
+++ b/Tests/Fixtures/SPM/FooFeature/Sources/FooDomain/FooDomain.swift
@@ -0,0 +1 @@
+public struct FooDomain {}
diff --git a/Tests/Fixtures/SPM/FooFeature/Sources/FooUI/FooUI.swift b/Tests/Fixtures/SPM/FooFeature/Sources/FooUI/FooUI.swift
new file mode 100644
index 000000000..1951b94c7
--- /dev/null
+++ b/Tests/Fixtures/SPM/FooFeature/Sources/FooUI/FooUI.swift
@@ -0,0 +1 @@
+public struct FooUI {}
diff --git a/Tests/Fixtures/SPM/SPM.xcodeproj/project.pbxproj b/Tests/Fixtures/SPM/SPM.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..2799afcc0
--- /dev/null
+++ b/Tests/Fixtures/SPM/SPM.xcodeproj/project.pbxproj
@@ -0,0 +1,759 @@
+// !$*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 */;
+}
diff --git a/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..919434a62
--- /dev/null
+++ b/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 000000000..18d981003
--- /dev/null
+++ b/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 000000000..8d8accfc1
--- /dev/null
+++ b/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,122 @@
+{
+ "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
+}
diff --git a/Tests/Fixtures/SPM/SPM.xcodeproj/xcshareddata/xcschemes/App.xcscheme b/Tests/Fixtures/SPM/SPM.xcodeproj/xcshareddata/xcschemes/App.xcscheme
new file mode 100644
index 000000000..298045bf8
--- /dev/null
+++ b/Tests/Fixtures/SPM/SPM.xcodeproj/xcshareddata/xcschemes/App.xcscheme
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/Fixtures/SPM/SPM/App.xctestplan b/Tests/Fixtures/SPM/SPM/App.xctestplan
new file mode 100644
index 000000000..0a76fad88
--- /dev/null
+++ b/Tests/Fixtures/SPM/SPM/App.xctestplan
@@ -0,0 +1,23 @@
+{
+ "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
+}
diff --git a/Tests/Fixtures/SPM/SPM/AppDelegate.swift b/Tests/Fixtures/SPM/SPM/AppDelegate.swift
new file mode 100644
index 000000000..e5ea35984
--- /dev/null
+++ b/Tests/Fixtures/SPM/SPM/AppDelegate.swift
@@ -0,0 +1,20 @@
+//
+// 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
+ }
+
+}
diff --git a/Tests/Fixtures/TestProject/IMessage/Assets.xcassets/AppIcon.appiconset/Contents.json b/Tests/Fixtures/SPM/SPM/Assets.xcassets/AppIcon.appiconset/Contents.json
similarity index 100%
rename from Tests/Fixtures/TestProject/IMessage/Assets.xcassets/AppIcon.appiconset/Contents.json
rename to Tests/Fixtures/SPM/SPM/Assets.xcassets/AppIcon.appiconset/Contents.json
diff --git a/Tests/Fixtures/TestProject/IMessage MessagesExtension/Assets.xcassets/Contents.json b/Tests/Fixtures/SPM/SPM/Assets.xcassets/Contents.json
similarity index 100%
rename from Tests/Fixtures/TestProject/IMessage MessagesExtension/Assets.xcassets/Contents.json
rename to Tests/Fixtures/SPM/SPM/Assets.xcassets/Contents.json
diff --git a/Tests/Fixtures/SPM/SPM/Info.plist b/Tests/Fixtures/SPM/SPM/Info.plist
new file mode 100644
index 000000000..2a3483c0d
--- /dev/null
+++ b/Tests/Fixtures/SPM/SPM/Info.plist
@@ -0,0 +1,64 @@
+
+
+
+
+ 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
+
+
+
diff --git a/Tests/Fixtures/SPM/SPMTests/SPMTests.swift b/Tests/Fixtures/SPM/SPMTests/SPMTests.swift
new file mode 100644
index 000000000..0692da85e
--- /dev/null
+++ b/Tests/Fixtures/SPM/SPMTests/SPMTests.swift
@@ -0,0 +1,26 @@
+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.
+ }
+ }
+}
diff --git a/Tests/Fixtures/SPM/StaticLibrary/StaticLibrary.swift b/Tests/Fixtures/SPM/StaticLibrary/StaticLibrary.swift
new file mode 100644
index 000000000..be8fedcd2
--- /dev/null
+++ b/Tests/Fixtures/SPM/StaticLibrary/StaticLibrary.swift
@@ -0,0 +1,5 @@
+import Codability
+
+func doThing() {
+ _ = AnyCodable.self
+}
diff --git a/Tests/Fixtures/SPM/project.yml b/Tests/Fixtures/SPM/project.yml
new file mode 100644
index 000000000..7e8b3b064
--- /dev/null
+++ b/Tests/Fixtures/SPM/project.yml
@@ -0,0 +1,68 @@
+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
diff --git a/Tests/Fixtures/TestProject/.lldbinit b/Tests/Fixtures/TestProject/.lldbinit
new file mode 100644
index 000000000..e69de29bb
diff --git a/Tests/Fixtures/TestProject/AnotherProject/AnotherProject.xcodeproj/project.pbxproj b/Tests/Fixtures/TestProject/AnotherProject/AnotherProject.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..306e8a0dd
--- /dev/null
+++ b/Tests/Fixtures/TestProject/AnotherProject/AnotherProject.xcodeproj/project.pbxproj
@@ -0,0 +1,958 @@
+// !$*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 */;
+}
diff --git a/Tests/Fixtures/TestProject/AnotherProject/AnotherProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Tests/Fixtures/TestProject/AnotherProject/AnotherProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..919434a62
--- /dev/null
+++ b/Tests/Fixtures/TestProject/AnotherProject/AnotherProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Tests/Fixtures/TestProject/AnotherProject/AnotherProject.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Tests/Fixtures/TestProject/AnotherProject/AnotherProject.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 000000000..18d981003
--- /dev/null
+++ b/Tests/Fixtures/TestProject/AnotherProject/AnotherProject.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Tests/Fixtures/TestProject/AnotherProject/project.yml b/Tests/Fixtures/TestProject/AnotherProject/project.yml
new file mode 100644
index 000000000..f86b69aec
--- /dev/null
+++ b/Tests/Fixtures/TestProject/AnotherProject/project.yml
@@ -0,0 +1,30 @@
+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: .
+
diff --git a/Tests/Fixtures/TestProject/App_Clip/AppDelegate.swift b/Tests/Fixtures/TestProject/App_Clip/AppDelegate.swift
new file mode 100644
index 000000000..cbe822167
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_Clip/AppDelegate.swift
@@ -0,0 +1,14 @@
+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
+ }
+}
diff --git a/Tests/Fixtures/TestProject/App_Clip/Assets.xcassets/AppIcon.appiconset/Contents.json b/Tests/Fixtures/TestProject/App_Clip/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..d8db8d65f
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_Clip/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "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"
+ }
+}
\ No newline at end of file
diff --git a/Tests/Fixtures/TestProject/App_Clip/Base.lproj/LaunchScreen.storyboard b/Tests/Fixtures/TestProject/App_Clip/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 000000000..fdf3f97d1
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_Clip/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/Fixtures/TestProject/App_Clip/Base.lproj/Main.storyboard b/Tests/Fixtures/TestProject/App_Clip/Base.lproj/Main.storyboard
new file mode 100644
index 000000000..42545a50d
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_Clip/Base.lproj/Main.storyboard
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/Fixtures/TestProject/App_Clip/Clip.entitlements b/Tests/Fixtures/TestProject/App_Clip/Clip.entitlements
new file mode 100644
index 000000000..92112b0ee
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_Clip/Clip.entitlements
@@ -0,0 +1,12 @@
+
+
+
+
+ com.apple.developer.parent-application-identifiers
+
+ $(AppIdentifierPrefix)com.project.appwithclip
+
+ com.apple.security.application-groups
+ group.com.app
+
+
diff --git a/Tests/Fixtures/TestProject/App_Clip/Info.plist b/Tests/Fixtures/TestProject/App_Clip/Info.plist
new file mode 100644
index 000000000..2989640a8
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_Clip/Info.plist
@@ -0,0 +1,45 @@
+
+
+
+
+ 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
+
+
+
diff --git a/Tests/Fixtures/TestProject/App_Clip/ViewController.swift b/Tests/Fixtures/TestProject/App_Clip/ViewController.swift
new file mode 100644
index 000000000..a44b02a48
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_Clip/ViewController.swift
@@ -0,0 +1,15 @@
+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.
+ }
+}
diff --git a/Tests/Fixtures/TestProject/App_Clip_Tests/Info.plist b/Tests/Fixtures/TestProject/App_Clip_Tests/Info.plist
new file mode 100644
index 000000000..6c6c23c43
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_Clip_Tests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git a/Tests/Fixtures/TestProject/App_Clip_Tests/TestProjectTests.swift b/Tests/Fixtures/TestProject/App_Clip_Tests/TestProjectTests.swift
new file mode 100644
index 000000000..f5c77a544
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_Clip_Tests/TestProjectTests.swift
@@ -0,0 +1,26 @@
+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.
+ }
+ }
+}
diff --git a/Tests/Fixtures/TestProject/App_Clip_UITests/Info.plist b/Tests/Fixtures/TestProject/App_Clip_UITests/Info.plist
new file mode 100644
index 000000000..6c6c23c43
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_Clip_UITests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git a/Tests/Fixtures/TestProject/App_Clip_UITests/TestProjectUITests.swift b/Tests/Fixtures/TestProject/App_Clip_UITests/TestProjectUITests.swift
new file mode 100644
index 000000000..357046659
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_Clip_UITests/TestProjectUITests.swift
@@ -0,0 +1,26 @@
+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.
+ }
+ }
+}
diff --git a/Tests/Fixtures/TestProject/App_iOS/App.entitlements b/Tests/Fixtures/TestProject/App_iOS/App.entitlements
new file mode 100644
index 000000000..37d691efd
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_iOS/App.entitlements
@@ -0,0 +1,8 @@
+
+
+
+
+ com.apple.security.application-groups
+ group.com.app
+
+
diff --git a/Tests/Fixtures/TestProject/App_iOS/AppDelegate.swift b/Tests/Fixtures/TestProject/App_iOS/AppDelegate.swift
index 3116cdbfe..10c98bab3 100644
--- a/Tests/Fixtures/TestProject/App_iOS/AppDelegate.swift
+++ b/Tests/Fixtures/TestProject/App_iOS/AppDelegate.swift
@@ -6,11 +6,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
- func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
- // Override point for customization after application launch.
+ 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
}
}
diff --git a/Tests/Fixtures/TestProject/App_iOS/App_iOS.xctestplan b/Tests/Fixtures/TestProject/App_iOS/App_iOS.xctestplan
new file mode 100644
index 000000000..53056d698
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_iOS/App_iOS.xctestplan
@@ -0,0 +1,45 @@
+{
+ "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
+}
diff --git a/Tests/Fixtures/TestProject/App_iOS/Configuration.storekit b/Tests/Fixtures/TestProject/App_iOS/Configuration.storekit
new file mode 100644
index 000000000..aa066b3fe
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_iOS/Configuration.storekit
@@ -0,0 +1,29 @@
+{
+ "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
+ }
+}
diff --git a/Tests/Fixtures/TestProject/App_iOS/Documentation.docc/Documentation.md b/Tests/Fixtures/TestProject/App_iOS/Documentation.docc/Documentation.md
new file mode 100644
index 000000000..7afe5dd3d
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_iOS/Documentation.docc/Documentation.md
@@ -0,0 +1,7 @@
+# ``App_Clip``
+
+Test
+
+## Overview
+
+Test
diff --git a/Tests/Fixtures/TestProject/App_iOS/Documentation.docc/Resources/resource.png b/Tests/Fixtures/TestProject/App_iOS/Documentation.docc/Resources/resource.png
new file mode 100644
index 000000000..e69de29bb
diff --git a/Tests/Fixtures/TestProject/App_iOS/FolderWithDot2.0/SwiftFileInDotPath.swift b/Tests/Fixtures/TestProject/App_iOS/FolderWithDot2.0/SwiftFileInDotPath.swift
new file mode 100644
index 000000000..106c592c5
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_iOS/FolderWithDot2.0/SwiftFileInDotPath.swift
@@ -0,0 +1,7 @@
+import Foundation
+
+extension String {
+ func printHelloWorld() {
+ print("Hello World!")
+ }
+}
diff --git a/Tests/Fixtures/TestProject/App_iOS/Model.xcmappingmodel/xcmapping.xml b/Tests/Fixtures/TestProject/App_iOS/Model.xcmappingmodel/xcmapping.xml
new file mode 100644
index 000000000..d5461f9bd
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_iOS/Model.xcmappingmodel/xcmapping.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+ 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
+
+
+
+
+
\ No newline at end of file
diff --git a/Tests/Fixtures/TestProject/App_iOS/Resource.abc b/Tests/Fixtures/TestProject/App_iOS/Resource.abc
new file mode 100644
index 000000000..e69de29bb
diff --git a/Tests/Fixtures/TestProject/App_iOS/Resource.abcd/File.json b/Tests/Fixtures/TestProject/App_iOS/Resource.abcd/File.json
new file mode 100644
index 000000000..e69de29bb
diff --git a/Tests/Fixtures/TestProject/App_iOS/Settings.bundle/Root.plist b/Tests/Fixtures/TestProject/App_iOS/Settings.bundle/Root.plist
new file mode 100644
index 000000000..b1b6fea5d
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_iOS/Settings.bundle/Root.plist
@@ -0,0 +1,61 @@
+
+
+
+
+ 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
+
+
+
+
+
diff --git a/Tests/Fixtures/TestProject/App_iOS/Settings.bundle/en.lproj/Root.strings b/Tests/Fixtures/TestProject/App_iOS/Settings.bundle/en.lproj/Root.strings
new file mode 100644
index 000000000..8cd87b9d6
Binary files /dev/null and b/Tests/Fixtures/TestProject/App_iOS/Settings.bundle/en.lproj/Root.strings differ
diff --git a/Tests/Fixtures/TestProject/App_iOS/TestIcon.icon/Assets/back.svg b/Tests/Fixtures/TestProject/App_iOS/TestIcon.icon/Assets/back.svg
new file mode 100644
index 000000000..c05772100
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_iOS/TestIcon.icon/Assets/back.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/Tests/Fixtures/TestProject/App_iOS/TestIcon.icon/icon.json b/Tests/Fixtures/TestProject/App_iOS/TestIcon.icon/icon.json
new file mode 100644
index 000000000..818557e00
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_iOS/TestIcon.icon/icon.json
@@ -0,0 +1,30 @@
+{
+ "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"
+ }
+}
diff --git a/Tests/Fixtures/TestProject/App_iOS/ViewController.swift b/Tests/Fixtures/TestProject/App_iOS/ViewController.swift
index 4cdc9940d..a44b02a48 100644
--- a/Tests/Fixtures/TestProject/App_iOS/ViewController.swift
+++ b/Tests/Fixtures/TestProject/App_iOS/ViewController.swift
@@ -1,10 +1,11 @@
+import Contacts
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
- // Do any additional setup after loading the view, typically from a nib.
+ _ = CNContact()
}
override func didReceiveMemoryWarning() {
diff --git a/Tests/Fixtures/TestProject/App_iOS/inputList.xcfilelist b/Tests/Fixtures/TestProject/App_iOS/inputList.xcfilelist
new file mode 100644
index 000000000..e69de29bb
diff --git a/Tests/Fixtures/TestProject/App_iOS/outputList.xcfilelist b/Tests/Fixtures/TestProject/App_iOS/outputList.xcfilelist
new file mode 100644
index 000000000..e69de29bb
diff --git a/Tests/Fixtures/TestProject/App_macOS/Info.plist b/Tests/Fixtures/TestProject/App_macOS/App-Info.plist
similarity index 94%
rename from Tests/Fixtures/TestProject/App_macOS/Info.plist
rename to Tests/Fixtures/TestProject/App_macOS/App-Info.plist
index e40caef4f..cab4fbf41 100644
--- a/Tests/Fixtures/TestProject/App_macOS/Info.plist
+++ b/Tests/Fixtures/TestProject/App_macOS/App-Info.plist
@@ -20,6 +20,8 @@
1.0
CFBundleVersion
1
+ CustomSetting
+ $CUSTOM_SETTING
LSMinimumSystemVersion
$(MACOSX_DEPLOYMENT_TARGET)
NSMainStoryboardFile
diff --git a/Tests/Fixtures/TestProject/App_macOS/AppDelegate.swift b/Tests/Fixtures/TestProject/App_macOS/AppDelegate.swift
index c4068e8a5..1e3be7aab 100644
--- a/Tests/Fixtures/TestProject/App_macOS/AppDelegate.swift
+++ b/Tests/Fixtures/TestProject/App_macOS/AppDelegate.swift
@@ -1,17 +1,8 @@
-//
-// AppDelegate.swift
-// dfg
-//
-// Created by Yonas Kolb on 29/9/18.
-//
-
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
-
-
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
@@ -19,7 +10,4 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
-
-
}
-
diff --git a/Tests/Fixtures/TestProject/App_macOS/ViewController.swift b/Tests/Fixtures/TestProject/App_macOS/ViewController.swift
index bb6301bf3..ec0eea30e 100644
--- a/Tests/Fixtures/TestProject/App_macOS/ViewController.swift
+++ b/Tests/Fixtures/TestProject/App_macOS/ViewController.swift
@@ -1,10 +1,3 @@
-//
-// ViewController.swift
-// dfg
-//
-// Created by Yonas Kolb on 29/9/18.
-//
-
import Cocoa
class ViewController: NSViewController {
@@ -17,10 +10,7 @@ class ViewController: NSViewController {
override var representedObject: Any? {
didSet {
- // Update the view, if already loaded.
+ // Update the view, if already loaded.
}
}
-
-
}
-
diff --git a/Tests/Fixtures/TestProject/App_macOS_Tests/Info.plist b/Tests/Fixtures/TestProject/App_macOS_Tests/Info.plist
new file mode 100644
index 000000000..64d65ca49
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_macOS_Tests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ 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
+
+
diff --git a/Tests/Fixtures/TestProject/App_macOS_Tests/TestProjectTests.swift b/Tests/Fixtures/TestProject/App_macOS_Tests/TestProjectTests.swift
new file mode 100644
index 000000000..f5c77a544
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_macOS_Tests/TestProjectTests.swift
@@ -0,0 +1,26 @@
+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.
+ }
+ }
+}
diff --git a/Tests/Fixtures/TestProject/App_supportedDestinations/Info.generated.plist b/Tests/Fixtures/TestProject/App_supportedDestinations/Info.generated.plist
new file mode 100644
index 000000000..8bed04c06
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_supportedDestinations/Info.generated.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ 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
+
+
diff --git a/Tests/Fixtures/TestProject/App_supportedDestinations/Sources/MyAppApp.swift b/Tests/Fixtures/TestProject/App_supportedDestinations/Sources/MyAppApp.swift
new file mode 100644
index 000000000..2db0d8841
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_supportedDestinations/Sources/MyAppApp.swift
@@ -0,0 +1,10 @@
+import SwiftUI
+
+@main
+struct MyAppApp: App {
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ }
+ }
+}
diff --git a/Tests/Fixtures/TestProject/App_supportedDestinations/Sources/iOS/ContentView.swift b/Tests/Fixtures/TestProject/App_supportedDestinations/Sources/iOS/ContentView.swift
new file mode 100644
index 000000000..1e32a55a0
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_supportedDestinations/Sources/iOS/ContentView.swift
@@ -0,0 +1,13 @@
+import SwiftUI
+
+struct ContentView: View {
+ var body: some View {
+ VStack {
+ Image(systemName: "globe")
+ .imageScale(.large)
+ .foregroundColor(.accentColor)
+ Text("Hello, world! on iOS")
+ }
+ .padding()
+ }
+}
diff --git a/Tests/Fixtures/TestProject/App_supportedDestinations/Sources/tvOS/ContentView.swift b/Tests/Fixtures/TestProject/App_supportedDestinations/Sources/tvOS/ContentView.swift
new file mode 100644
index 000000000..f0512afc0
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_supportedDestinations/Sources/tvOS/ContentView.swift
@@ -0,0 +1,13 @@
+import SwiftUI
+
+struct ContentView: View {
+ var body: some View {
+ VStack {
+ Image(systemName: "house")
+ .imageScale(.large)
+ .foregroundColor(.accentColor)
+ Text("Hello, world! tvOS")
+ }
+ .padding()
+ }
+}
diff --git a/Tests/Fixtures/TestProject/App_supportedDestinations/Storyboards/LaunchScreen.storyboard b/Tests/Fixtures/TestProject/App_supportedDestinations/Storyboards/LaunchScreen.storyboard
new file mode 100644
index 000000000..865e9329f
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_supportedDestinations/Storyboards/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/File_MACCATALYST.swift b/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/File_MACCATALYST.swift
new file mode 100644
index 000000000..fecc4ab44
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/File_MACCATALYST.swift
@@ -0,0 +1 @@
+import Foundation
diff --git a/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/File_ios.swift b/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/File_ios.swift
new file mode 100644
index 000000000..fecc4ab44
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/File_ios.swift
@@ -0,0 +1 @@
+import Foundation
diff --git a/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/File_macOS.swift b/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/File_macOS.swift
new file mode 100644
index 000000000..fecc4ab44
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/File_macOS.swift
@@ -0,0 +1 @@
+import Foundation
diff --git a/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/File_tvOs.swift b/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/File_tvOs.swift
new file mode 100644
index 000000000..fecc4ab44
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/File_tvOs.swift
@@ -0,0 +1 @@
+import Foundation
diff --git a/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/TVOS/File_B.swift b/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/TVOS/File_B.swift
new file mode 100644
index 000000000..fecc4ab44
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/TVOS/File_B.swift
@@ -0,0 +1 @@
+import Foundation
diff --git a/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/iOs/File_A.swift b/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/iOs/File_A.swift
new file mode 100644
index 000000000..fecc4ab44
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/iOs/File_A.swift
@@ -0,0 +1 @@
+import Foundation
diff --git a/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/macCatalyst/File_D.swift b/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/macCatalyst/File_D.swift
new file mode 100644
index 000000000..fecc4ab44
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/macCatalyst/File_D.swift
@@ -0,0 +1 @@
+import Foundation
diff --git a/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/macos/File_C.swift b/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/macos/File_C.swift
new file mode 100644
index 000000000..fecc4ab44
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_supportedDestinations/TestResources/macos/File_C.swift
@@ -0,0 +1 @@
+import Foundation
diff --git a/Tests/Fixtures/TestProject/App_watchOS Extension/InterfaceController.swift b/Tests/Fixtures/TestProject/App_watchOS Extension/InterfaceController.swift
index 73cc697f4..fce05a061 100644
--- a/Tests/Fixtures/TestProject/App_watchOS Extension/InterfaceController.swift
+++ b/Tests/Fixtures/TestProject/App_watchOS Extension/InterfaceController.swift
@@ -1,4 +1,3 @@
-import Result
import Foundation
import WatchKit
@@ -6,7 +5,6 @@ class InterfaceController: WKInterfaceController {
override func awake(withContext context: Any?) {
super.awake(withContext: context)
- _ = Result.success("value")
// Configure interface objects here.
}
diff --git a/Tests/Fixtures/TestProject/Cartfile b/Tests/Fixtures/TestProject/Cartfile
index 27636fa12..ee558e8a6 100644
--- a/Tests/Fixtures/TestProject/Cartfile
+++ b/Tests/Fixtures/TestProject/Cartfile
@@ -1 +1,3 @@
-github "antitypical/Result"
\ No newline at end of file
+github "antitypical/Result"
+github "SwiftyJSON/SwiftyJSON"
+github "pointfreeco/swift-nonempty"
diff --git a/Tests/Fixtures/TestProject/Cartfile.resolved b/Tests/Fixtures/TestProject/Cartfile.resolved
index 23aa97090..8d4cd5d78 100644
--- a/Tests/Fixtures/TestProject/Cartfile.resolved
+++ b/Tests/Fixtures/TestProject/Cartfile.resolved
@@ -1 +1,3 @@
-github "antitypical/Result" "4.0.0"
+github "SwiftyJSON/SwiftyJSON" "5.0.0"
+github "antitypical/Result" "5.0.0"
+github "pointfreeco/swift-nonempty" "0.2.2"
diff --git a/Tests/Fixtures/TestProject/CrossOverlayFramework/CrossOverlayFramework.swiftcrossimport/Framework.swiftoverlay b/Tests/Fixtures/TestProject/CrossOverlayFramework/CrossOverlayFramework.swiftcrossimport/Framework.swiftoverlay
new file mode 100644
index 000000000..f186c2740
--- /dev/null
+++ b/Tests/Fixtures/TestProject/CrossOverlayFramework/CrossOverlayFramework.swiftcrossimport/Framework.swiftoverlay
@@ -0,0 +1,5 @@
+%YAML 1.2
+---
+version: 1
+modules:
+ - name: _CrossOverlayFramework_Framework
diff --git a/Tests/Fixtures/TestProject/CrossOverlayFramework/FrameworkFile.swift b/Tests/Fixtures/TestProject/CrossOverlayFramework/FrameworkFile.swift
new file mode 100644
index 000000000..f003efd55
--- /dev/null
+++ b/Tests/Fixtures/TestProject/CrossOverlayFramework/FrameworkFile.swift
@@ -0,0 +1,6 @@
+import Foundation
+
+public struct FrameworkStruct {
+
+ public init() {}
+}
diff --git a/Tests/Fixtures/TestProject/CrossOverlayFramework/Info.plist b/Tests/Fixtures/TestProject/CrossOverlayFramework/Info.plist
new file mode 100644
index 000000000..37c17ea7f
--- /dev/null
+++ b/Tests/Fixtures/TestProject/CrossOverlayFramework/Info.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.2
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSPrincipalClass
+
+
+
diff --git a/Tests/Fixtures/TestProject/CrossOverlayFramework/MyFramework.h b/Tests/Fixtures/TestProject/CrossOverlayFramework/MyFramework.h
new file mode 100644
index 000000000..739f2414b
--- /dev/null
+++ b/Tests/Fixtures/TestProject/CrossOverlayFramework/MyFramework.h
@@ -0,0 +1,9 @@
+//
+// MyFramework.h
+// MyFramework
+//
+// Created by Yonas Kolb on 21/7/17.
+// Copyright © 2017 Yonas Kolb. All rights reserved.
+//
+
+
diff --git a/Tests/Fixtures/TestProject/CrossOverlayFramework/Project.xcodeproj/project.pbxproj b/Tests/Fixtures/TestProject/CrossOverlayFramework/Project.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..e69de29bb
diff --git a/Tests/Fixtures/TestProject/DriverKit Driver/Driver.cpp b/Tests/Fixtures/TestProject/DriverKit Driver/Driver.cpp
new file mode 100644
index 000000000..875a03948
--- /dev/null
+++ b/Tests/Fixtures/TestProject/DriverKit Driver/Driver.cpp
@@ -0,0 +1,22 @@
+//
+// Driver.cpp
+// Driver
+//
+// Created by Vlad Gorlov on 18.06.21.
+//
+
+#include
+
+#include
+#include
+
+#include "Driver.h"
+
+kern_return_t
+IMPL(Driver, Start)
+{
+ kern_return_t ret;
+ ret = Start(provider, SUPERDISPATCH);
+ os_log(OS_LOG_DEFAULT, "Hello World");
+ return ret;
+}
diff --git a/Tests/Fixtures/TestProject/DriverKit Driver/Driver.entitlements b/Tests/Fixtures/TestProject/DriverKit Driver/Driver.entitlements
new file mode 100644
index 000000000..852fa1a47
--- /dev/null
+++ b/Tests/Fixtures/TestProject/DriverKit Driver/Driver.entitlements
@@ -0,0 +1,8 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+
+
diff --git a/Tests/Fixtures/TestProject/DriverKit Driver/Driver.iig b/Tests/Fixtures/TestProject/DriverKit Driver/Driver.iig
new file mode 100644
index 000000000..886a04c91
--- /dev/null
+++ b/Tests/Fixtures/TestProject/DriverKit Driver/Driver.iig
@@ -0,0 +1,21 @@
+//
+// Driver.iig
+// Driver
+//
+// Created by Vlad Gorlov on 18.06.21.
+//
+
+#ifndef Driver_h
+#define Driver_h
+
+#include
+#include
+
+class Driver: public IOService
+{
+public:
+ virtual kern_return_t
+ Start(IOService * provider) override;
+};
+
+#endif /* Driver_h */
diff --git a/Tests/Fixtures/TestProject/DriverKit Driver/Info.plist b/Tests/Fixtures/TestProject/DriverKit Driver/Info.plist
new file mode 100644
index 000000000..fd5d03af8
--- /dev/null
+++ b/Tests/Fixtures/TestProject/DriverKit Driver/Info.plist
@@ -0,0 +1,46 @@
+
+
+
+
+ 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
+ IOKitPersonalities
+
+ Driver
+
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleIdentifierKernel
+ com.apple.kpi.iokit
+ IOClass
+ IOUserService
+ IOMatchCategory
+ com.apple.null.driver
+ IOProviderClass
+ IOUserResources
+ IOResourceMatch
+ IOKit
+ IOUserClass
+ Driver
+ IOUserServerName
+ com.apple.null.driver
+
+
+ OSBundleUsageDescription
+
+
+
diff --git a/Tests/Fixtures/TestProject/EndpointSecurity Extension/EndpointSecurity.entitlements b/Tests/Fixtures/TestProject/EndpointSecurity Extension/EndpointSecurity.entitlements
new file mode 100644
index 000000000..03a3d8341
--- /dev/null
+++ b/Tests/Fixtures/TestProject/EndpointSecurity Extension/EndpointSecurity.entitlements
@@ -0,0 +1,12 @@
+
+
+
+
+ com.apple.developer.endpoint-security.client
+
+ com.apple.security.application-groups
+
+ $(TeamIdentifierPrefix)com.example.app-group
+
+
+
diff --git a/Tests/Fixtures/TestProject/EndpointSecurity Extension/Info.plist b/Tests/Fixtures/TestProject/EndpointSecurity Extension/Info.plist
new file mode 100644
index 000000000..d787bc3cd
--- /dev/null
+++ b/Tests/Fixtures/TestProject/EndpointSecurity Extension/Info.plist
@@ -0,0 +1,28 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ EndpointSecurity
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSMinimumSystemVersion
+ $(MACOSX_DEPLOYMENT_TARGET)
+ NSSystemExtensionUsageDescription
+
+
+
diff --git a/Tests/Fixtures/TestProject/EndpointSecurity Extension/main.swift b/Tests/Fixtures/TestProject/EndpointSecurity Extension/main.swift
new file mode 100644
index 000000000..d163fadda
--- /dev/null
+++ b/Tests/Fixtures/TestProject/EndpointSecurity Extension/main.swift
@@ -0,0 +1,22 @@
+//
+// main.swift
+// EndpointSecurity
+//
+// Created by Vlad Gorlov on 18.06.21.
+//
+
+import Foundation
+import EndpointSecurity
+
+var client: OpaquePointer?
+
+// Create the client
+let res = es_new_client(&client) { (client, message) in
+ // Do processing on the message received
+}
+
+if res != ES_NEW_CLIENT_RESULT_SUCCESS {
+ exit(EXIT_FAILURE)
+}
+
+dispatchMain()
diff --git a/Tests/Fixtures/TestProject/ExtensionKit Extension/EntryPoint.swift b/Tests/Fixtures/TestProject/ExtensionKit Extension/EntryPoint.swift
new file mode 100644
index 000000000..0e665c55f
--- /dev/null
+++ b/Tests/Fixtures/TestProject/ExtensionKit Extension/EntryPoint.swift
@@ -0,0 +1,5 @@
+import AppIntents
+
+@main
+struct EntryPoint: AppIntentsExtension {
+}
diff --git a/Tests/Fixtures/TestProject/ExtensionKit Extension/Info.plist b/Tests/Fixtures/TestProject/ExtensionKit Extension/Info.plist
new file mode 100644
index 000000000..8d15acbed
--- /dev/null
+++ b/Tests/Fixtures/TestProject/ExtensionKit Extension/Info.plist
@@ -0,0 +1,11 @@
+
+
+
+
+ EXAppExtensionAttributes
+
+ EXExtensionPointIdentifier
+ com.apple.appintents-extension
+
+
+
diff --git a/Tests/Fixtures/TestProject/ExtensionKit Extension/Intent.swift b/Tests/Fixtures/TestProject/ExtensionKit Extension/Intent.swift
new file mode 100644
index 000000000..0449c505c
--- /dev/null
+++ b/Tests/Fixtures/TestProject/ExtensionKit Extension/Intent.swift
@@ -0,0 +1,9 @@
+import AppIntents
+
+struct Intent: AppIntent {
+ static var title: LocalizedStringResource = "Intent"
+
+ func perform() async throws -> some IntentResult {
+ return .result()
+ }
+}
diff --git a/Tests/Fixtures/TestProject/Framework/FrameworkFile.swift b/Tests/Fixtures/TestProject/Framework/FrameworkFile.swift
index bb3d49eee..f003efd55 100644
--- a/Tests/Fixtures/TestProject/Framework/FrameworkFile.swift
+++ b/Tests/Fixtures/TestProject/Framework/FrameworkFile.swift
@@ -2,6 +2,5 @@ import Foundation
public struct FrameworkStruct {
- public init() {
- }
+ public init() {}
}
diff --git a/Tests/Fixtures/TestProject/Group/File1.swift b/Tests/Fixtures/TestProject/Group/File1.swift
new file mode 100644
index 000000000..e69de29bb
diff --git a/Tests/Fixtures/TestProject/Group2/File2.swift b/Tests/Fixtures/TestProject/Group2/File2.swift
new file mode 100644
index 000000000..e69de29bb
diff --git a/Tests/Fixtures/TestProject/Mintfile b/Tests/Fixtures/TestProject/Mintfile
index e69de29bb..8b1378917 100644
--- a/Tests/Fixtures/TestProject/Mintfile
+++ b/Tests/Fixtures/TestProject/Mintfile
@@ -0,0 +1 @@
+
diff --git a/Tests/Fixtures/TestProject/NestedFiles/Foo/Nested.swift b/Tests/Fixtures/TestProject/NestedFiles/Foo/Nested.swift
index 70552f805..75c4994c8 100644
--- a/Tests/Fixtures/TestProject/NestedFiles/Foo/Nested.swift
+++ b/Tests/Fixtures/TestProject/NestedFiles/Foo/Nested.swift
@@ -1,3 +1,3 @@
func nested() -> String {
- return "Nested"
+ "Nested"
}
diff --git a/Tests/Fixtures/TestProject/Network Extension/FilterDataProvider.swift b/Tests/Fixtures/TestProject/Network Extension/FilterDataProvider.swift
new file mode 100644
index 000000000..5a5d4dd38
--- /dev/null
+++ b/Tests/Fixtures/TestProject/Network Extension/FilterDataProvider.swift
@@ -0,0 +1,26 @@
+//
+// FilterDataProvider.swift
+// NetworkExtension
+//
+// Created by Vlad Gorlov on 18.06.21.
+//
+
+import NetworkExtension
+
+class FilterDataProvider: NEFilterDataProvider {
+
+ override func startFilter(completionHandler: @escaping (Error?) -> Void) {
+ // Add code to initialize the filter.
+ completionHandler(nil)
+ }
+
+ override func stopFilter(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
+ // Add code to clean up filter resources.
+ completionHandler()
+ }
+
+ override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict {
+ // Add code to determine if the flow should be dropped or not, downloading new rules if required.
+ return .allow()
+ }
+}
diff --git a/Tests/Fixtures/TestProject/Network Extension/Info.plist b/Tests/Fixtures/TestProject/Network Extension/Info.plist
new file mode 100644
index 000000000..88c7ae8f5
--- /dev/null
+++ b/Tests/Fixtures/TestProject/Network Extension/Info.plist
@@ -0,0 +1,38 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ NetworkExtension
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSMinimumSystemVersion
+ $(MACOSX_DEPLOYMENT_TARGET)
+ NSSystemExtensionUsageDescription
+
+ NetworkExtension
+
+ NEMachServiceName
+ $(TeamIdentifierPrefix)com.example.app-group.MySystemExtension
+ NEProviderClasses
+
+ com.apple.networkextension.filter-data
+ $(PRODUCT_MODULE_NAME).FilterDataProvider
+
+
+
+
diff --git a/Tests/Fixtures/TestProject/Network Extension/NetworkExtension.entitlements b/Tests/Fixtures/TestProject/Network Extension/NetworkExtension.entitlements
new file mode 100644
index 000000000..b8ae39612
--- /dev/null
+++ b/Tests/Fixtures/TestProject/Network Extension/NetworkExtension.entitlements
@@ -0,0 +1,19 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.application-groups
+
+ $(TeamIdentifierPrefix)com.example.app-group
+
+ com.apple.developer.networking.networkextension
+
+ packet-tunnel-provider
+ app-proxy-provider
+ content-filter-provider
+ dns-proxy
+
+
+
diff --git a/Tests/Fixtures/TestProject/Network Extension/main.swift b/Tests/Fixtures/TestProject/Network Extension/main.swift
new file mode 100644
index 000000000..2313f84f4
--- /dev/null
+++ b/Tests/Fixtures/TestProject/Network Extension/main.swift
@@ -0,0 +1,15 @@
+//
+// main.swift
+// NetworkExtension
+//
+// Created by Vlad Gorlov on 18.06.21.
+//
+
+import Foundation
+import NetworkExtension
+
+autoreleasepool {
+ NEProvider.startSystemExtensionMode()
+}
+
+dispatchMain()
diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj
index 10501eab2..8a2c46fc5 100644
--- a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj
+++ b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj
@@ -3,1993 +3,5423 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 46;
+ objectVersion = 77;
objects = {
/* Begin PBXAggregateTarget section */
- AT_D40C01B2BAD29EDEA392714DFB69FE8F /* SuperTarget */ = {
+ BF3693DCA6182D7AEC410AFC /* SuperTarget */ = {
isa = PBXAggregateTarget;
- buildConfigurationList = CL_B523BC91AF70F483BF998E2BA8DD6669 /* Build configuration list for PBXAggregateTarget "SuperTarget" */;
+ buildConfigurationList = 7CBF487CACC0BBFB530D7963 /* Build configuration list for PBXAggregateTarget "SuperTarget" */;
buildPhases = (
- SSBP_831D7D5A30B0F736E1E92F7B5CF9428F /* MyScript */,
+ CF3AABFD4A48983B322677DA /* MyScript */,
);
dependencies = (
- TD_B8AB4064784522A5F3760CB1372D1BCD /* PBXTargetDependency */,
- TD_D056799E80FFF41247BAE692D01142F0 /* PBXTargetDependency */,
+ D19F2660FAD44CCC4390265C /* PBXTargetDependency */,
+ 106CDB4BCFD183241A565E6C /* PBXTargetDependency */,
);
name = SuperTarget;
+ packageProductDependencies = (
+ );
productName = SuperTarget;
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
- BF_021628792D80050372F6E56A6C3D0561 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_B993F75B001AB1C272CE83CACC06F0E5 /* AppDelegate.swift */; settings = {COMPILER_FLAGS = "-Werror"; }; };
- BF_0A6C302954FF03411D6F2704ECF5C738 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = FR_A957DAE2193BE1E970F452BFEFF3EBF6 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
- BF_0B3CE605B6243480C374176E01B1BB12 /* module.modulemap in CopyFiles */ = {isa = PBXBuildFile; fileRef = FR_96127F4D9D804B89024AB846F0961621 /* module.modulemap */; };
- BF_149B8FD8F114C531F675E01FBE814609 /* ResourceFolder in Resources */ = {isa = PBXBuildFile; fileRef = FR_1B0304C0E4DC73614BAA550E4A80D41C /* ResourceFolder */; };
- BF_19C973A29D9994B1E0655B1AFC5528AD /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = VG_6542B94EE7DA40CD30BC54DED26C61C7 /* Interface.storyboard */; };
- BF_1C8AAB7468188315681C0879591969B4 /* module.modulemap in CopyFiles */ = {isa = PBXBuildFile; fileRef = FR_96127F4D9D804B89024AB846F0961621 /* module.modulemap */; };
- BF_1EC32E6544284999188A140FB5FC6E67 /* TestProjectUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_5CFDEA59B939FA9F3CA4F775B9E6AD2B /* TestProjectUITests.swift */; };
- BF_230439786C4C6849F488A5FADC6A42A5 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_11478908A963CED036DABEF21D85DF01 /* Result.framework */; };
- BF_2356EAE09301354149D45720369BE7F2 /* StaticLibrary_ObjC.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = FR_D0BE69522DB875ADB041E9135E0767CA /* StaticLibrary_ObjC.h */; };
- BF_30024D558743C8ABC415D87431CDC13C /* SomeFramework.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FR_2F56FD7A1F7782467AC9F315B6133468 /* SomeFramework.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- BF_3080067722B357BFC053D89CCDBF5397 /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_261C31660333EF514356EFCBDB368EAB /* FrameworkFile.swift */; };
- BF_33EA0F8B2ADA2D24A683FCD8D6235B10 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = FR_A957DAE2193BE1E970F452BFEFF3EBF6 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
- BF_3811C1771AF5342AD8F9FEB63A3465B5 /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_261C31660333EF514356EFCBDB368EAB /* FrameworkFile.swift */; };
- BF_390908C547D6F8520DF9AA0CC5A98EB1 /* Headers in Headers */ = {isa = PBXBuildFile; fileRef = FR_3DD4DE355C21662A9169EE41C44F73E3 /* Headers */; settings = {ATTRIBUTES = (Public, ); }; };
- BF_3B680DD27CFCD491675F1BF257A95A24 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = FR_A957DAE2193BE1E970F452BFEFF3EBF6 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
- BF_42222C3F9126FFCC21BA88AD21CFD964 /* iMessageExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = FR_586C1089A4869FFCB50BE6A26B0FA1E7 /* iMessageExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
- BF_42944378315033432F76246C09E5A4DB /* Framework2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_20EF6A69E91EC1859026CEBF5AD33FC2 /* Framework2.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
- BF_47A75C8A7EF15658238E254C846C5C6B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = VG_30676EBEE9BE54AA26CAE69BE744CAE8 /* Main.storyboard */; };
- BF_47FBEFEA08B9642C1F711EDA77FC8C89 /* LocalizedStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = VG_FE6D89DA2D7E7F58340D566590FF221C /* LocalizedStoryboard.storyboard */; };
- BF_47FF83A37355E90F93C0F5B2CFBCE317 /* Standalone.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_82E3C6C060C4487B5177509917C3FAE3 /* Standalone.swift */; };
- BF_4D56F3F4D081A77C11325B96DD34D8E1 /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_261C31660333EF514356EFCBDB368EAB /* FrameworkFile.swift */; };
- BF_4EBBAD70FA73DDC89BD933866B90DD08 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = FR_A957DAE2193BE1E970F452BFEFF3EBF6 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
- BF_4FC40CAFC1CFAD431215635FE1A0ED86 /* Framework.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FR_F25AB3F5F1D72653F078DF0E063DC9A5 /* Framework.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- BF_51D370314B5DA8E002A908021E459F50 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FR_A55A35F549FD72775C37ED05342812AA /* Assets.xcassets */; };
- BF_527E94EECC2501E12D28790BF9318FD3 /* module.modulemap in CopyFiles */ = {isa = PBXBuildFile; fileRef = FR_96127F4D9D804B89024AB846F0961621 /* module.modulemap */; };
- BF_52C8E2C5962601534CE6F00B88FDB048 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = VG_14B7B5469AA17939993A2B25E775D391 /* Main.storyboard */; };
- BF_5336CD16DBDB1654D75AA91B922DEAD6 /* InterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_CC3A24A5C8E1815346752C5AB0745176 /* InterfaceController.swift */; };
- BF_569CC068CD4B7BCE35E974D7B61566DB /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = FR_A957DAE2193BE1E970F452BFEFF3EBF6 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
- BF_587563F1FBA507305EC4AD895394002E /* StaticLibrary_ObjC.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_0EDA4C9F9810C805C447660BF181FFF9 /* StaticLibrary_ObjC.a */; };
- BF_5C6407EDAC3D88B020BCB46724DC1D14 /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_261C31660333EF514356EFCBDB368EAB /* FrameworkFile.swift */; };
- BF_63672615CB7B136F2706924EC460710A /* Framework.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FR_F25AB3F5F1D72653F078DF0E063DC9A5 /* Framework.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- BF_65835F9276FF0DB8E27D098367F9D03C /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = VG_C247AA37E9057916EDDC65C7B55E2F38 /* Localizable.stringsdict */; };
- BF_67219751DE020023F9D6068EDCCDC445 /* SomeXPCService.xpc in CopyFiles */ = {isa = PBXBuildFile; fileRef = FR_4EB4AAACD714C13E5BC894C71B954299 /* SomeXPCService.xpc */; };
- BF_6EF6DB8898CD050E19303DC481EEFDC5 /* Framework2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FR_20EF6A69E91EC1859026CEBF5AD33FC2 /* Framework2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- BF_6FA9DA8B67663BAD9E11C1CEBD3BAC43 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_DC1C8DE46218F90A3F4FD5F8DE4F0ABB /* Result.framework */; };
- BF_703163C1C547AD6EF09BEA5F5F5ED49C /* module.modulemap in CopyFiles */ = {isa = PBXBuildFile; fileRef = FR_96127F4D9D804B89024AB846F0961621 /* module.modulemap */; };
- BF_729EB1C88A5E43B2342099D81B301F4D /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_261C31660333EF514356EFCBDB368EAB /* FrameworkFile.swift */; };
- BF_76825DD9B3B10103CDAB9D235BDF6A91 /* MessagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_5FEC20FF753341FD0483E2E4C622DB05 /* MessagesViewController.swift */; };
- BF_77C9BB7BED4F5970D02E69312259FE09 /* StaticLibrary_ObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = FR_C2A280C4FA602E6610BCFB820602B69E /* StaticLibrary_ObjC.m */; };
- BF_7C9646667C0D479544A2207DEE3E930B /* Headers in Headers */ = {isa = PBXBuildFile; fileRef = FR_3DD4DE355C21662A9169EE41C44F73E3 /* Headers */; settings = {ATTRIBUTES = (Public, ); }; };
- BF_81711FB79375EB743254B809B4E246E0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_4DB225C7FF39C16EEFD9A1A5595FCDBC /* AppDelegate.swift */; };
- BF_81C29A47481BF6D67B4CB3F6C836A57E /* Framework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_F25AB3F5F1D72653F078DF0E063DC9A5 /* Framework.framework */; };
- BF_8610CB66872BC72C1690EBD8D102FBC0 /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_261C31660333EF514356EFCBDB368EAB /* FrameworkFile.swift */; };
- BF_86F2552DA1E230901EC4CB1A40399019 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_CEAE8D9C3F3920D6EADE5455C188EAAE /* Result.framework */; };
- BF_8765851BF5D99B10C220DDD57B968B10 /* Result.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FR_11478908A963CED036DABEF21D85DF01 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- BF_90F0E9253F2768C312B65530931CD55A /* Empty.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = FR_DBD29CA78CDBBEF69B2C39C6D23BBDA1 /* Empty.h */; };
- BF_910C2D6055BD22643F042545CD21AA78 /* StaticLibrary_ObjC.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = FR_D0BE69522DB875ADB041E9135E0767CA /* StaticLibrary_ObjC.h */; };
- BF_93A4E1A93C7DB4289E526466D547E55E /* SomeFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_2F56FD7A1F7782467AC9F315B6133468 /* SomeFramework.framework */; };
- BF_9830FFD35765AACD86D1B619E22830D6 /* Headers in Headers */ = {isa = PBXBuildFile; fileRef = FR_3DD4DE355C21662A9169EE41C44F73E3 /* Headers */; settings = {ATTRIBUTES = (Public, ); }; };
- BF_98DEE7FD578AA439550E0023A2ECE8D0 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_25BDF725104C5E280D45CBF33F54C72C /* ViewController.swift */; };
- BF_9A74AED05E2F61530BFA0F7D23AF0112 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = VG_EB676136B9F946373D4796800CC00AD4 /* Model.xcdatamodeld */; settings = {COMPILER_FLAGS = "-Werror"; }; };
- BF_9D9B97D239384F4CE6E73852E027B787 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FR_B537FEE8515090D3BA0F5CFF3BC76BB1 /* Assets.xcassets */; };
- BF_A038D29BE93A9CF1654D2B7392845AEA /* StaticLibrary_ObjC.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_0EDA4C9F9810C805C447660BF181FFF9 /* StaticLibrary_ObjC.a */; };
- BF_A29F8B04C9DD9ADE1EF5AFDAAA0130D2 /* ExtensionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_DBB69305458B16774C88720CDD3978E5 /* ExtensionDelegate.swift */; };
- BF_A314322BB45C75B7EE401C539B1120C5 /* Framework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_F25AB3F5F1D72653F078DF0E063DC9A5 /* Framework.framework */; };
- BF_A8A5905BCA8872B2D7B0EF6326B33077 /* MyBundle.bundle in Resources */ = {isa = PBXBuildFile; fileRef = FR_291D6D09ACADC420638E83BEE89DFFEB /* MyBundle.bundle */; };
- BF_ABA4EA32A6DB022806126D9C1418BBA3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FR_ED407ECED0DF010B1E787D238EA91A5D /* Assets.xcassets */; };
- BF_AC2CE59D56846478FDFBB376FF9F9DC0 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = VG_BE436BDD64E90EFB600D47AC69B49DD3 /* Localizable.strings */; };
- BF_B2B4B15D6F7B242DBDA39939111282F9 /* TestProjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_D7B45FAEE40EE622B4FA9192609F9717 /* TestProjectTests.swift */; };
- BF_B5446C54E942A18E025D2061A88004A4 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_11478908A963CED036DABEF21D85DF01 /* Result.framework */; };
- BF_B57F9691AD12CA8AE5528A2BAB9E4A43 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = VG_F256536BB07CF3E64C4DD934F75BED9C /* LaunchScreen.storyboard */; };
- BF_B6E31790D07B981E52CD5BC9049FE303 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = FR_A957DAE2193BE1E970F452BFEFF3EBF6 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
- BF_B8E824A58BFE0B81E16BB26087FDC8B4 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_CEAE8D9C3F3920D6EADE5455C188EAAE /* Result.framework */; };
- BF_BD2537A230BFC8A86681F0AF34C929DA /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_8B770F475242D91FC20289A3B35CD165 /* Result.framework */; };
- BF_BEFDF47CD7D88221A9C166BD65BF2013 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = FR_A957DAE2193BE1E970F452BFEFF3EBF6 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
- BF_C5EA496FE2EDF51C92DC55FD552472E5 /* StaticLibrary_ObjC.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = FR_D0BE69522DB875ADB041E9135E0767CA /* StaticLibrary_ObjC.h */; };
- BF_C9D24A56926211130F4E25B5D9972B58 /* StaticLibrary_ObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = FR_C2A280C4FA602E6610BCFB820602B69E /* StaticLibrary_ObjC.m */; };
- BF_CD062A97959629BD672893FDAE89A1E9 /* NotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_BB49B398F9291781A60DA963A0BF168C /* NotificationController.swift */; };
- BF_CF8F05DAB384E10C59B65F4D0F51C109 /* App_watchOS.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = FR_0DDA6B4CCFF0DEB79BE16E3F72B95C7F /* App_watchOS.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
- BF_D0676C98017B6FDD96A733CB851645DE /* StaticLibrary_ObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = FR_C2A280C4FA602E6610BCFB820602B69E /* StaticLibrary_ObjC.m */; };
- BF_D1EF4AECA866B1132F3F01B826B5EE5D /* iMessageApp.app in Resources */ = {isa = PBXBuildFile; fileRef = FR_1E05BB66BFD5629EAFDC86A4E869F864 /* iMessageApp.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
- BF_D3D64E2595369BBDEF03E07543AE2779 /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_261C31660333EF514356EFCBDB368EAB /* FrameworkFile.swift */; };
- BF_D4A5544EDEE7F80A1F9DF1D0684CAFBB /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_DC1C8DE46218F90A3F4FD5F8DE4F0ABB /* Result.framework */; };
- BF_D6588CD7B83034816FFD3A7DB10DAD78 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FR_16B791CFA2095097A17CC216977DF6EF /* Assets.xcassets */; };
- BF_D9111FF58DCAA51B7251F882EC418E67 /* XPC Service.xpc in CopyFiles */ = {isa = PBXBuildFile; fileRef = FR_8FCFCDD6A5A7292C72787E2FC0A36294 /* XPC Service.xpc */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- BF_DA12D48C9BCEC64D55B87CE8703432BE /* StaticLibrary_ObjC.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = FR_D0BE69522DB875ADB041E9135E0767CA /* StaticLibrary_ObjC.h */; };
- BF_DBF3047DCE71CB2E7B9AC7367F44F8DB /* StaticLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_CCC97AF230188CAF9B4A66324891CCAD /* StaticLibrary.swift */; };
- BF_E228A4D3997297780216722D71AC9FE5 /* StaticLibrary_ObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = FR_C2A280C4FA602E6610BCFB820602B69E /* StaticLibrary_ObjC.m */; };
- BF_E2D02DDEDD7DC21831F50A6EAA71E528 /* StandaloneAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FR_C6A9DE885CAF350F6C9D0AC6F7324CD3 /* StandaloneAssets.xcassets */; };
- BF_E35C9C198B45406E64439FF96C17F056 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = FR_A957DAE2193BE1E970F452BFEFF3EBF6 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
- BF_E6B3BA0A3A687598FB5CCDF0524E1B10 /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_261C31660333EF514356EFCBDB368EAB /* FrameworkFile.swift */; };
- BF_E6D3B938E2ED4C5339D11D340D3809EF /* App_watchOS Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = FR_09608EA4613E62B3088BBA06F08E00E9 /* App_watchOS Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
- BF_EA0063B4FC2A0980A746D35A8DF71CED /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = VG_06DC429EEFBBA25BC3EE1AA1B4062C10 /* MainInterface.storyboard */; };
- BF_EBC45940911A4942BA04AE1285BF3802 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FR_A0867B127ACF3ED382EB2FD5133D3EA8 /* Assets.xcassets */; };
- BF_EC0616A0D18A2F723D2AF50287C43669 /* Headers in Headers */ = {isa = PBXBuildFile; fileRef = FR_3DD4DE355C21662A9169EE41C44F73E3 /* Headers */; settings = {ATTRIBUTES = (Public, ); }; };
- BF_ED8BB47A2229EC3BEE544608267FB82D /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_DC1C8DE46218F90A3F4FD5F8DE4F0ABB /* Result.framework */; };
- BF_F12E44D8E7F617E8A5FD8E1A342E5FAD /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_752FB5DFFBC490CFB9742549A0C48527 /* ViewController.swift */; settings = {COMPILER_FLAGS = "-Werror"; }; };
- BF_F6A0FC1F0C9A4EC13BCCBB764D716C3F /* Result.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FR_CEAE8D9C3F3920D6EADE5455C188EAAE /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- BF_F7AC9F45ED37FCD5A749FD6F5F4518AF /* Standalone.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_82E3C6C060C4487B5177509917C3FAE3 /* Standalone.swift */; };
- BF_F9E44FE6ECBD936A8C762D425DDBA37C /* Result.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FR_CEAE8D9C3F3920D6EADE5455C188EAAE /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- BF_FA28229372AB6236883309181258AF26 /* XPC_Service.m in Sources */ = {isa = PBXBuildFile; fileRef = FR_7F4496C534F2DF8C4FB9A8D52414990B /* XPC_Service.m */; };
- BF_FA5F3C87A2571E0F39637D118B6C6F8F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FR_B243640A6F73B3F3D33ABD05ABDBC26B /* Assets.xcassets */; };
- BF_FD13F69A90FB1D5676B97A8C77D710B4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = FR_F5436E663145426483F206F8A61400BC /* main.m */; };
- BF_FF7EBDFA4462D2983743C84CE99D9714 /* MoreUnder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_AC5B2FCE520D5306B254D5857E15B6CB /* MoreUnder.swift */; };
+ 01BFA2C4D9C5BBC72C015AA8 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A58A16491CDDF968B0D56DE /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 02F9D686CBA6068A8EE58026 /* StaticLibrary_ObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D0C79A8C750EC0DE748C463 /* StaticLibrary_ObjC.m */; };
+ 03D1147528CED90EC1D844CE /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C7F9636B706AC92629D0B48 /* XCTest.framework */; };
+ 03FFCE664129864A8F167C2F /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81E4CCE342955E0E934BE533 /* FrameworkFile.swift */; };
+ 052D6B4572FBF002286865D7 /* CrossOverlayFramework.swiftcrossimport in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8DD7A61B07AD2F91BDECC255 /* CrossOverlayFramework.swiftcrossimport */; };
+ 06F1750F0E45E4822F806523 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 576675973B56A96047CB4944 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 0786F9C725AD215C4F915BB5 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A58A16491CDDF968B0D56DE /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 079B6E02AF21664AB08E621C /* TestProjectUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E9A3533E965CA602B76 /* TestProjectUITests.swift */; };
+ 0927149520F12314CE8B4079 /* TestFramework.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E43116070AFEF5D8C3A5A957 /* TestFramework.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 0932FB6FB887D7D6F7727CB7 /* Driver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 432E2C071A4B6B3757BEA13E /* Driver.cpp */; };
+ 09617AB755651FFEB2564CBC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F1A2F579A6F79C62DDA0571 /* AppDelegate.swift */; settings = {COMPILER_FLAGS = "-Werror"; }; };
+ 0AB541AE3163B063E7012877 /* StaticLibrary_ObjC.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5A2B916A11DCC2565241359F /* StaticLibrary_ObjC.h */; };
+ 0BDA156BEBFCB9E65910F838 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A58A16491CDDF968B0D56DE /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 0D0E2466833FC2636B92C43D /* Swinject in Frameworks */ = {isa = PBXBuildFile; platformFilter = ios; productRef = D7917D10F77DA9D69937D493 /* Swinject */; };
+ 0F99AECCB4691803C791CDCE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2FC2A8A829CE71B1CF415FF7 /* Main.storyboard */; };
+ 15129B8D9ED000BDA1FEEC27 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23A2F16890ECF2EE3FED72AE /* AppDelegate.swift */; };
+ 1B485D6584C3B47AC58831C6 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18722C61B05FFF4CC63D5755 /* ContentView.swift */; platformFilters = (tvos, ); };
+ 1BC891D89980D82738D963F3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 74FBDFA5CB063F6001AD8ACD /* Main.storyboard */; };
+ 1E03FC7312293997599C6435 /* Empty.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 068EDF47F0B087F6A4052AC0 /* Empty.h */; };
+ 1E2A4D61E96521FF7123D7B0 /* XPC Service.xpc in CopyFiles */ = {isa = PBXBuildFile; fileRef = 22237B8EBD9E6BE8EBC8735F /* XPC Service.xpc */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 1EFFFE113C5E54A91148D3EB /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81E4CCE342955E0E934BE533 /* FrameworkFile.swift */; };
+ 1F9168A43FD8E2FCC2699E14 /* Documentation.docc in Sources */ = {isa = PBXBuildFile; fileRef = B5C943D39DD7812CAB94B614 /* Documentation.docc */; settings = {COMPILER_FLAGS = "-Werror"; }; };
+ 204958B9AD868004CCE6B779 /* App_watchOS Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0D09D243DBCF9D32E239F1E8 /* App_watchOS Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ 210B49C23B9717C668B40C8C /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5F527F2590C14956518174 /* FrameworkFile.swift */; };
+ 2116F89CF5A04EA0EFA30A89 /* TestProjectUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D88C6BF7355702B74396791 /* TestProjectUITests.swift */; };
+ 212BCB51DAF3212993DDD49E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D51CC8BCCBD68A90E90A3207 /* Assets.xcassets */; };
+ 21425F6DE3D493B6F1E33D21 /* Framework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 41FC82ED1C4C3B7B3D7B2FB7 /* Framework.framework */; };
+ 216B220EC7961DF7CA9188B7 /* StaticLibrary_ObjC.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5A2B916A11DCC2565241359F /* StaticLibrary_ObjC.h */; };
+ 262891CCD5F74316610437FA /* Framework2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3EF21DF245F66BEF5446AAEF /* Framework2.framework */; platformFilter = ios; settings = {ATTRIBUTES = (Weak, ); }; };
+ 265B6A05C0198FD2EB485173 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93C033648A37D95027845BD3 /* main.swift */; };
+ 2698ED273D0A5820B28CAD20 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D52EC9AA9FFD3B690C355068 /* LaunchScreen.storyboard */; platformFilters = (ios, ); };
+ 2730C6D0A35AED4ADD6EDF17 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0704B6CAFBB53E0EBB08F6B3 /* ViewController.swift */; settings = {COMPILER_FLAGS = "-Werror"; }; };
+ 28A96EBC76D53817AABDA91C /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 8AF20308873AEEEC4D8C45D1 /* Settings.bundle */; };
+ 2A5356FCC03EE312F1738C61 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B82F603D981398F38D762E /* AppDelegate.swift */; };
+ 2A7EB1A9A365A7EC5D49AFCF /* LocalizedStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0C6BA0D12467A13EC012C728 /* LocalizedStoryboard.storyboard */; };
+ 2B940E57041A72E6A39B6BF0 /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C872631362DDBAFCE71E5C66 /* Interface.storyboard */; };
+ 2C7C03B45571A13D472D6B23 /* iMessageApp.app in Resources */ = {isa = PBXBuildFile; fileRef = 9A87A926D563773658FB87FE /* iMessageApp.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ 2FAE950E8FF2E7C0F7FF1FE9 /* Framework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A9274BE42A03DC5DA1FAD04 /* Framework.framework */; };
+ 3133B36F3898A27A2B1C56FC /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5F527F2590C14956518174 /* FrameworkFile.swift */; };
+ 3318F40C855184C18197ED30 /* StaticLibrary_ObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D0C79A8C750EC0DE748C463 /* StaticLibrary_ObjC.m */; };
+ 339578307B9266AB3D7722D9 /* File2.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC56891DA7446EAC8C2F27EB /* File2.swift */; };
+ 3535891EC86283BB5064E7E1 /* Headers in Headers */ = {isa = PBXBuildFile; fileRef = 2E1E747C7BC434ADB80CC269 /* Headers */; settings = {ATTRIBUTES = (Public, ); }; };
+ 3788E1382B38DF4ACE3D2BB1 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A58A16491CDDF968B0D56DE /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 3A3BA9F91994D8B472C71F04 /* Swinject in Frameworks */ = {isa = PBXBuildFile; platformFilters = (tvos, ); productRef = C7F9B7EDE85527EFEA85D46D /* Swinject */; };
+ 3BBCA6F76E5F212E9C55FB78 /* BundleX.bundle in Copy Bundle Resources */ = {isa = PBXBuildFile; fileRef = 45C12576F5AA694DD0CE2132 /* BundleX.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 3C5134EE524310ACF7B7CD6E /* ExtensionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CAF6C55B555E3E1352645B6 /* ExtensionDelegate.swift */; };
+ 3DF22C477446669094AC7C8C /* ExternalTarget.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F6ADE654A3459AFDA2CC0CD3 /* ExternalTarget.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 447D59BE2E0993D7245EA247 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3797E591F302ECC0AA2FC607 /* Assets.xcassets */; };
+ 45E6702CD9C088FF1FC25F34 /* App_watchOS.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = A680BE9F68A255B0FB291AE6 /* App_watchOS.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ 470D3493CDBFE56E2083A5FD /* BundleY.bundle in Copy Bundle Resources */ = {isa = PBXBuildFile; fileRef = E3958AB20082EA12D4D5E60C /* BundleY.bundle */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ 47D1F439B8E6D287B3F3E8D1 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A58A16491CDDF968B0D56DE /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 47FC57B04A3AD83359F433EA /* StaticLibrary_ObjC.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5A2B916A11DCC2565241359F /* StaticLibrary_ObjC.h */; };
+ 49A4B8937BB5520B36EA33F0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 814D72C2B921F60B759C2D4B /* Main.storyboard */; };
+ 4B862F11762F6BB54E97E401 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 576675973B56A96047CB4944 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 4C1504A05321046B3ED7A839 /* Framework2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB055761199DF36DB0C629A6 /* Framework2.framework */; };
+ 4CB673A7C0C11E04F8544BDB /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FDB2B6A77D39CD5602F2125F /* Contacts.framework */; };
+ 4DA7140FF84DBF39961F3409 /* NetworkSystemExtension.systemextension in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = 2049B6DD2AFE85F9DC9F3EB3 /* NetworkSystemExtension.systemextension */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ 4F6481557E2BEF8D749C37E3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 187E665975BB5611AF0F27E1 /* main.m */; };
+ 5126CD91C2CB41C9B14B6232 /* DriverKitDriver.dext in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = 83B5EC7EF81F7E4B6F426D4E /* DriverKitDriver.dext */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ 52AD3276E068EB3396A292BB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D8A016580A3B8F72B820BFBF /* Assets.xcassets */; };
+ 535A98A3E3B74E09891D977F /* TestFramework.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E43116070AFEF5D8C3A5A957 /* TestFramework.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 5447AD526B2A1FD4262E2B61 /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5F527F2590C14956518174 /* FrameworkFile.swift */; };
+ 5748F702ADFB9D85D0F97862 /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5F527F2590C14956518174 /* FrameworkFile.swift */; };
+ 57DC116CE5C9F93FBA529C2F /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 576675973B56A96047CB4944 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 58C18019E71E372F635A3FB4 /* MoreUnder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA8718C7CD3BE86D9B1F5120 /* MoreUnder.swift */; };
+ 5D10822B0E7C33DD6979F656 /* Standalone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F5BD97AF0F94A15A5B7DDB7 /* Standalone.swift */; };
+ 5E0369B907E239D1E6884ECF /* TestFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E43116070AFEF5D8C3A5A957 /* TestFramework.framework */; };
+ 61401517ECCEB2362582B5DA /* libEndpointSecurity.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BC75409252FF15F540FBB7B /* libEndpointSecurity.tbd */; };
+ 61516CAC12B2843FBC4572E6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 59DA55A04FA2366B5D0BEEFF /* Assets.xcassets */; };
+ 61601545B6BE00CA74A4E38F /* SceneKitCatalog.scnassets in Resources */ = {isa = PBXBuildFile; fileRef = C9E358FBE2B54D2B5C7FD609 /* SceneKitCatalog.scnassets */; };
+ 6241507B4947B0B65429587C /* ExternalTarget.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F6ADE654A3459AFDA2CC0CD3 /* ExternalTarget.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 632774E7F21CCB386A76B2A8 /* MessagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B198242976C3395E31FE000A /* MessagesViewController.swift */; };
+ 63D8E7F00276736EDA62D227 /* Framework2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3EF21DF245F66BEF5446AAEF /* Framework2.framework */; platformFilter = ios; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 65B3BAC02D5FAE632719C984 /* Model.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF59AC868D227C92CA8B1B57 /* Model.xcmappingmodel */; };
+ 666AA5F3F63C8FD7C68A6CC5 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A58A16491CDDF968B0D56DE /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 666DEC173BC78C7641AB22EC /* File1.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE1343F2238429D4DA9D830B /* File1.swift */; };
+ 66C3C5E3C13325F351A3008F /* module.modulemap in CopyFiles */ = {isa = PBXBuildFile; fileRef = F2950763C4C568CC85021D18 /* module.modulemap */; };
+ 6B0BCD3573931F7BE133B301 /* TestProjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D132EA69984F32DA9DC727B6 /* TestProjectTests.swift */; };
+ 6C02002A4EE169CEBEC7BA7F /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81E4CCE342955E0E934BE533 /* FrameworkFile.swift */; };
+ 6E8F8303759824631C8D9DA3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9E17D598D98065767A04740F /* Localizable.strings */; };
+ 6F3848C1EF3E43A5AC2A4D40 /* TestIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = FD5AEA58D3C0A1C8EEDF00F7 /* TestIcon.icon */; };
+ 713F57A10C62F70058D7FB0A /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81E4CCE342955E0E934BE533 /* FrameworkFile.swift */; };
+ 7148A4172BFA1CC22E6ED5DB /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 753001CDCEAA4C4E1AFF8E87 /* MainInterface.storyboard */; };
+ 71A2AAC5934BDC9EDB6F0D9E /* libStaticLibrary_ObjC.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B221F5A689AD7D3AD52F56B8 /* libStaticLibrary_ObjC.a */; };
+ 747CAE14D196F5652E93353C /* Headers in Headers */ = {isa = PBXBuildFile; fileRef = 2E1E747C7BC434ADB80CC269 /* Headers */; settings = {ATTRIBUTES = (Public, ); }; };
+ 75F2774F183838AF34CA9B8A /* StaticLibrary_ObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D0C79A8C750EC0DE748C463 /* StaticLibrary_ObjC.m */; };
+ 76156B580B30704346296641 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C7F9636B706AC92629D0B48 /* XCTest.framework */; };
+ 768648ED7E93B6D888574144 /* module.modulemap in CopyFiles */ = {isa = PBXBuildFile; fileRef = F2950763C4C568CC85021D18 /* module.modulemap */; };
+ 76DC6A4B18F434BAC239CC4A /* DriverKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0A428E67153BB40184F37BE /* DriverKit.framework */; };
+ 76F3F9A5E2A4623430374F31 /* LocalizableStrings.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = E66ABD3EB14C9D63DEF5C532 /* LocalizableStrings.xcstrings */; };
+ 77C3CB285572EA4BB7E201A7 /* App_Clip.app in Embed App Clips */ = {isa = PBXBuildFile; fileRef = 38DB679FF1CF4E379D1AB103 /* App_Clip.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ 7A0DABBEA55B06E148C665A8 /* StaticLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC91042453E18DF74BA1C0F /* StaticLibrary.swift */; };
+ 7A8C78212CEAC6452DFAB00E /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5F527F2590C14956518174 /* FrameworkFile.swift */; };
+ 7F658343A505B824321E086B /* Headers in Headers */ = {isa = PBXBuildFile; fileRef = 2E1E747C7BC434ADB80CC269 /* Headers */; settings = {ATTRIBUTES = (Public, ); }; };
+ 803B7CE086CFBA409F9D1ED7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 108BB29172D27BE3BD1E7F35 /* Assets.xcassets */; };
+ 818D448D4DDD6649B5B26098 /* example.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 28360ECA4D727FAA58557A81 /* example.mp4 */; settings = {ASSET_TAGS = (tag1, tag2, ); }; };
+ 81DFAB3A7633CE97929B9B2A /* Framework.framework in Embed Dependencies */ = {isa = PBXBuildFile; fileRef = 41FC82ED1C4C3B7B3D7B2FB7 /* Framework.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 8267B75289E9D6C7B38FC426 /* DriverKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0A428E67153BB40184F37BE /* DriverKit.framework */; };
+ 87927928A8A3460166ACB819 /* SwiftFileInDotPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F430AABE04B7499B458D9DB /* SwiftFileInDotPath.swift */; settings = {COMPILER_FLAGS = "-Werror"; }; };
+ 900CFAD929CAEE3861127627 /* MyBundle.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 7B5068D64404C61A67A18458 /* MyBundle.bundle */; };
+ 94FD20C3EA5EBCEC8783740C /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = BDCA996D141DD8A16B18D68F /* GoogleService-Info.plist */; };
+ 95DD9941E1529FD2AE1A191D /* StaticLibrary_ObjC.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5A2B916A11DCC2565241359F /* StaticLibrary_ObjC.h */; };
+ 96B55C0F660235FE6BDD8869 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A58A16491CDDF968B0D56DE /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 998CCB995347CBB8EDC95FB5 /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5F527F2590C14956518174 /* FrameworkFile.swift */; };
+ 9AB50B81C29243936BB419E4 /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5F527F2590C14956518174 /* FrameworkFile.swift */; };
+ 9C92B7C89E5F0A10A34F5AA4 /* Framework.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8A9274BE42A03DC5DA1FAD04 /* Framework.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9D80BD5FAE6BE61CFD74CF1B /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5F527F2590C14956518174 /* FrameworkFile.swift */; };
+ A1588BF3BFFE1DF7409CBA10 /* Framework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A9274BE42A03DC5DA1FAD04 /* Framework.framework */; };
+ A496E1DB82E16DA4099D1411 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C7F9636B706AC92629D0B48 /* XCTest.framework */; };
+ A59B3F08914812573AFF6C2D /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FD4A16C7B8FEB7F97F3CBE3F /* libz.dylib */; };
+ A7438C77A05D83E7016CF044 /* Framework2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A0DC40025AB59B688E758829 /* Framework2.framework */; };
+ A90C4C147AD175DB9F7B5114 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CD22B8CD2E91BB97CC534E /* main.swift */; };
+ A949422315536EACDF8DD78A /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B785B1161553A7DD6DA4255 /* NetworkExtension.framework */; };
+ A9548E5DCFE92236494164DF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE1F06D99242F4223D081F0D /* LaunchScreen.storyboard */; };
+ AFF19412E9B35635D3AF48CB /* XPC_Service.m in Sources */ = {isa = PBXBuildFile; fileRef = 148B7C933698BCC4F1DBA979 /* XPC_Service.m */; };
+ B18C121B0A4D43ED8149D8E2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 79325B44B19B83EC6CEDBCC5 /* LaunchScreen.storyboard */; };
+ B20617116B230DED1F7AF5E5 /* libStaticLibrary_ObjC.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B221F5A689AD7D3AD52F56B8 /* libStaticLibrary_ObjC.a */; };
+ B2D43A31C184E34EF9CB743C /* Framework.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8A9274BE42A03DC5DA1FAD04 /* Framework.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ B358AB913543E62237FCC4E3 /* MyAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A4363D659A58DA835DE8BA /* MyAppApp.swift */; };
+ B47F2629BFE5853767C8BB5E /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FDB2B6A77D39CD5602F2125F /* Contacts.framework */; };
+ B49D3A51787E362DE4D0E78A /* SomeXPCService.xpc in CopyFiles */ = {isa = PBXBuildFile; fileRef = 70A8E15C81E454DC950C59F0 /* SomeXPCService.xpc */; };
+ B502EF8F7605CBD038298F23 /* CrossOverlayFramework.swiftcrossimport in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8DD7A61B07AD2F91BDECC255 /* CrossOverlayFramework.swiftcrossimport */; };
+ B9F3C9E77019EC3423A7F5D8 /* TestProjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87DF9DCA8399E3214A7E27CF /* TestProjectTests.swift */; };
+ BAA1C1E3828F5D43546AF997 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BB1B49A91B892152D68ED76 /* libc++.tbd */; };
+ BFCCC56337A5D9D513C1C791 /* module.modulemap in CopyFiles */ = {isa = PBXBuildFile; fileRef = F2950763C4C568CC85021D18 /* module.modulemap */; };
+ C093BF20B99FE892D0F06B2D /* libEndpointSecurity.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BC75409252FF15F540FBB7B /* libEndpointSecurity.tbd */; };
+ C3672B561F456794151C047C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4C3FE6B986506724DAB5D0F /* ViewController.swift */; };
+ C400EBD25886ACB5CD9035EB /* module.modulemap in CopyFiles */ = {isa = PBXBuildFile; fileRef = F2950763C4C568CC85021D18 /* module.modulemap */; };
+ C4378E3DAF5E0B2F7AB60E03 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFE6A6FAAFF701FE729293DE /* ViewController.swift */; };
+ C836F09B677937EFF69B1FCE /* NotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C934C1F7A68CCD0AB6B38478 /* NotificationController.swift */; };
+ C88598A49087A212990F4E8B /* ResourceFolder in Resources */ = {isa = PBXBuildFile; fileRef = 6B1603BA83AA0C7B94E45168 /* ResourceFolder */; };
+ CAE18A2194B57C830A297F83 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6680EFE4E908CDBDCE405C8 /* main.swift */; };
+ CAF8470C7F1BF207DBE6AEE3 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEEFDE76B5FEC833403C0869 /* ContentView.swift */; platformFilters = (ios, ); };
+ CCA17097382757012B58C17C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1BC32A813B80A53962A1F365 /* Assets.xcassets */; };
+ D058D241BDF5FB0C919BBECA /* CrossOverlayFramework.swiftcrossimport in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8DD7A61B07AD2F91BDECC255 /* CrossOverlayFramework.swiftcrossimport */; };
+ D5458D67C3596943114C3205 /* Standalone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F5BD97AF0F94A15A5B7DDB7 /* Standalone.swift */; };
+ D5D493E4A7AD63860E1399DD /* ExternalTarget.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6ADE654A3459AFDA2CC0CD3 /* ExternalTarget.framework */; };
+ D61BEABD5B26B2DE67D0C2EC /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5F527F2590C14956518174 /* FrameworkFile.swift */; };
+ D62AB3A85B32F353ABBD57BC /* iMessageExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D629E142AB87C681D4EC90F7 /* iMessageExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ D8ED40ED61AD912385CFF5F0 /* StaticLibrary_ObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D0C79A8C750EC0DE748C463 /* StaticLibrary_ObjC.m */; };
+ DBCA8149E5C4183AB52B8D99 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 576675973B56A96047CB4944 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DD5FBFC3C1B2DB3D0D1CF210 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B785B1161553A7DD6DA4255 /* NetworkExtension.framework */; };
+ E0B27599D701E6BB0223D0A8 /* FilterDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16AA52945B70B1BF9E246350 /* FilterDataProvider.swift */; };
+ E1836941C13CC7F13650C317 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3ED831531AA349CCC19B258B /* Assets.xcassets */; };
+ E34351AC0049221C167A60AC /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A58A16491CDDF968B0D56DE /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ E4B41CB5C796DD2C3C2E564C /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A58A16491CDDF968B0D56DE /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ E4D0F435405DABCB51C5B684 /* TestFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E43116070AFEF5D8C3A5A957 /* TestFramework.framework */; };
+ E5DD0AD6F7AE1DD4AF98B83E /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 65C8D6D1DDC1512D396C07B7 /* Localizable.stringsdict */; };
+ E7B40B34D8807F43A3805381 /* ExternalTarget.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6ADE654A3459AFDA2CC0CD3 /* ExternalTarget.framework */; };
+ E8A135F768448632F8D77C8F /* StandaloneAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3571E41E19A5AB8AAAB04109 /* StandaloneAssets.xcassets */; };
+ EDB55692D392FD09C3FCFBF6 /* libStaticLibrary_ObjC.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 86169DEEDEAF09AB89C8A31D /* libStaticLibrary_ObjC.a */; };
+ F4D77E81B0539EA5F4F141A6 /* EndpointSecuritySystemExtension.systemextension in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = E5E0A80CCE8F8DB662DCD2D0 /* EndpointSecuritySystemExtension.systemextension */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ F5D71267BB5A326BDD69D532 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E55F45EACB0F382722D61C8D /* Assets.xcassets */; };
+ F6537CE373C94809E6653758 /* Headers in Headers */ = {isa = PBXBuildFile; fileRef = 2E1E747C7BC434ADB80CC269 /* Headers */; settings = {ATTRIBUTES = (Public, ); }; };
+ F6734680031310575CDE9F23 /* CrossOverlayFramework.swiftcrossimport in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8DD7A61B07AD2F91BDECC255 /* CrossOverlayFramework.swiftcrossimport */; };
+ F7423E8738EECF04795C7601 /* InterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3F6BCB5FEFB16F1BA368059 /* InterfaceController.swift */; };
+ F788A3FA1CE6489BC257C1C3 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 306796628DD52FA55E833B65 /* Model.xcdatamodeld */; settings = {COMPILER_FLAGS = "-Werror"; }; };
+ FB6DA0DB62C425066D51767E /* Driver.iig in Sources */ = {isa = PBXBuildFile; fileRef = 5A3A73F307648F58213E4EA1 /* Driver.iig */; };
+ FF030751A47C85F082C7EDB9 /* TestProjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D132EA69984F32DA9DC727B6 /* TestProjectTests.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
- CIP_1AEF07B9F6A9BC9605E82F26AE7BFE15 /* PBXContainerItemProxy */ = {
+ 01630C98B755921A79418B08 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_6AEE12F93D449E249F5348BBF35D3053;
+ remoteGlobalIDString = 13E8C5AB873CEE21E18E552F;
remoteInfo = StaticLibrary_ObjC_iOS;
};
- CIP_1C3D8F2BF099A773651CF8A195466469 /* PBXContainerItemProxy */ = {
+ 02FC8A8CFD5676EA402CCCBC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_D4C3E345E90AF9F6CBE8CF226FCFBCD6;
- remoteInfo = StaticLibrary_ObjC_tvOS;
+ remoteGlobalIDString = 13E8C5AB873CEE21E18E552F;
+ remoteInfo = StaticLibrary_ObjC_iOS;
};
- CIP_1F8C8F9FAC75236BB2F60C3BCAE41C55 /* PBXContainerItemProxy */ = {
+ 0B37F7A37D610FCFE187A6B7 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_6AEE12F93D449E249F5348BBF35D3053;
- remoteInfo = StaticLibrary_ObjC_iOS;
+ remoteGlobalIDString = D137C04B64B7052419A2DF4E;
+ remoteInfo = App_Clip;
+ };
+ 25714659454527D9511C6093 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = D137C04B64B7052419A2DF4E;
+ remoteInfo = App_Clip;
+ };
+ 2CA19347816754250A29DA32 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 578C80E461E675508CED5DC3;
+ remoteInfo = StaticLibrary_ObjC_macOS;
+ };
+ 2FA0A954833DB0981CDE58E1 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = AE3F93DB94E7208F2F1D9A78;
+ remoteInfo = Framework_iOS;
};
- CIP_291A38592460740F400A80DE5DBA9679 /* PBXContainerItemProxy */ = {
+ 3A81C6D6875469889D53A2C5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_BEB0891E36797FE2214A0A9D516D408D;
+ remoteGlobalIDString = 0867B0DACEF28C11442DE8F7;
remoteInfo = App_iOS;
};
- CIP_47399FDE0885501C87782313489C95A3 /* PBXContainerItemProxy */ = {
+ 45907115465077029040BF29 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_34A020E43FBBD797051205235CD82B70;
- remoteInfo = StaticLibrary_ObjC_watchOS;
+ remoteGlobalIDString = 8B9A14DC280CCE013CC86440;
+ remoteInfo = Framework2_tvOS;
};
- CIP_4FA1F11884BC4F5DBDFAC1FBEC60AFC1 /* PBXContainerItemProxy */ = {
+ 469D922BE967C6D52ED84552 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_BEB0891E36797FE2214A0A9D516D408D;
+ remoteGlobalIDString = 0867B0DACEF28C11442DE8F7;
remoteInfo = App_iOS;
};
- CIP_5E9F76628F77E820EB9AA60A282D8691 /* PBXContainerItemProxy */ = {
+ 57F1BE74D4C4252529F97984 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_6BD068FAAC6AA35C090C48147B94EC6E;
+ remoteGlobalIDString = 834F55973F05AC8A18144DB0;
remoteInfo = iMessageApp;
};
- CIP_5F2FD3F0AED59431B4F98D7238CAA733 /* PBXContainerItemProxy */ = {
+ 57FA84916D499216199F8C95 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_5245AB6D3B3CFBC95AEBADF6E0C593B8;
- remoteInfo = "App_watchOS Extension";
+ remoteGlobalIDString = 13E8C5AB873CEE21E18E552F;
+ remoteInfo = StaticLibrary_ObjC_iOS;
};
- CIP_64ED071F49DC88F8192387D7A18827F1 /* PBXContainerItemProxy */ = {
+ 59BFAC272F73B46E97B74426 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_15609BCEEB00CBCC4C42110EB0366A6F;
- remoteInfo = StaticLibrary_ObjC_macOS;
+ remoteGlobalIDString = 6ED01BC471A8C3642258E178;
+ remoteInfo = Framework2_watchOS;
};
- CIP_6D60DD975DE7829AEA72BD4098DE13E6 /* PBXContainerItemProxy */ = {
+ 610412261F48A0A36C32FC5C /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_38A9FE87056942A2746E0FF025B52A91;
- remoteInfo = App_watchOS;
+ remoteGlobalIDString = 020A320BB3736FCDE6CC4E70;
+ remoteInfo = App_macOS;
};
- CIP_73A78E804E93B79E9ED934358CDD7D9D /* PBXContainerItemProxy */ = {
+ 69E205A3F578A8FFE3ECF3F9 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_BEB0891E36797FE2214A0A9D516D408D;
- remoteInfo = App_iOS;
+ remoteGlobalIDString = 0636AAF06498C336E1CEEDE4;
+ remoteInfo = TestFramework;
};
- CIP_862C50E687ABB75CCDF71F1157709A4D /* PBXContainerItemProxy */ = {
+ 6ED42BD51E8832232E58D9C1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_9F72C903B42E1AA3B88F97B917231B15;
- remoteInfo = Framework2_iOS;
+ remoteGlobalIDString = 428715FBC1D86458DA70CBDE;
+ remoteInfo = DriverKitDriver;
+ };
+ 747773057270E6F58470B5FA /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = D137C04B64B7052419A2DF4E;
+ remoteInfo = App_Clip;
};
- CIP_A760C88E6F24D3F06081AEBDEB8AE54B /* PBXContainerItemProxy */ = {
+ 7E37A3C0A67C3B6363029A18 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_7D108AE86BED8C9CCF52C2646FA4C5DE;
+ remoteGlobalIDString = AE3F93DB94E7208F2F1D9A78;
remoteInfo = Framework_iOS;
};
- CIP_B5B71FB9D5064D7E05E9E44827A87775 /* PBXContainerItemProxy */ = {
+ 7F4EAACE4AD6CF285B7D3308 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_6A4FC6EE80FB2821AB96D51C3BC8966E;
- remoteInfo = iMessageExtension;
+ remoteGlobalIDString = 53A3B531E3947D8A8722745E;
+ remoteInfo = Framework_macOS;
};
- CIP_BAAD03920C86C2839C3C93FDA5568ECE /* PBXContainerItemProxy */ = {
+ 8BAA7F3717FCBE0B8D6669B3 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_23509FD082D1F788E6D6431F509B11AF;
+ remoteGlobalIDString = E7815F2F0D9CDECF9185AAF3;
remoteInfo = "XPC Service";
};
- CIP_C82ED1EBAD4D7218ED9694EB7BF4DE74 /* PBXContainerItemProxy */ = {
+ 9636E133F9CACD17C56B7915 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_15609BCEEB00CBCC4C42110EB0366A6F;
- remoteInfo = StaticLibrary_ObjC_macOS;
+ remoteGlobalIDString = 208179651927D1138D19B5AD;
+ remoteInfo = App_watchOS;
};
- CIP_CA46A507405C3CBD7579D2C9A3F8719E /* PBXContainerItemProxy */ = {
+ A7E63E75AAC31076E5090CAA /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_9D53AF351F8DAE25354F2391248DFCCA;
- remoteInfo = Framework_macOS;
+ remoteGlobalIDString = 7D3D92034F4F203C140574F0;
+ remoteInfo = StaticLibrary_ObjC_watchOS;
+ };
+ AC20E10997F6C1DBBA16C7A0 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 307AE3FA155FFD09B74AE351;
+ remoteInfo = "App_watchOS Extension";
+ };
+ C42BA4EA0239AF536F0F0993 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 0636AAF06498C336E1CEEDE4;
+ remoteInfo = TestFramework;
+ };
+ C8FD369800D87311EC532712 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 1C26A6A0BC446191F311D470;
+ remoteInfo = iMessageExtension;
+ };
+ C9947CF0A436FC6113FC0837 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 0867B0DACEF28C11442DE8F7;
+ remoteInfo = App_iOS;
+ };
+ CA16090DFCA7842CB4E20265 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = F192E783CCA898FBAA5C34EA /* AnotherProject */;
+ proxyType = 1;
+ remoteGlobalIDString = E76A5F5E363E470416D3B487;
+ remoteInfo = ExternalTarget;
+ };
+ CB8F4B3FDD84A2A6F3CA7F4C /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 93542A75A613F00FDB5C9C63;
+ remoteInfo = StaticLibrary_ObjC_tvOS;
+ };
+ D3E1EE9F1E22A388123A116D /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = CE7D183D3752B5B35D2D8E6D;
+ remoteInfo = Framework2_iOS;
+ };
+ D4A7C57F6272F44F2E69A5DB /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = AD28397BCC984F769EE8A937;
+ remoteInfo = NetworkSystemExtension;
+ };
+ DD32A97CFD2016BF1477CF6C /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9F551F66949B55E8328EB995;
+ remoteInfo = EndpointSecuritySystemExtension;
+ };
+ DECF0B88B325A158E4E1D9AE /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 578C80E461E675508CED5DC3;
+ remoteInfo = StaticLibrary_ObjC_macOS;
+ };
+ E231CFA00B56E5D8C57331C5 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = F192E783CCA898FBAA5C34EA /* AnotherProject */;
+ proxyType = 2;
+ remoteGlobalIDString = D6340FC7DEBC81E0127BAFD6;
+ remoteInfo = ExternalTarget;
};
- CIP_DC690C90FF2F615A1EB93D9803F8D905 /* PBXContainerItemProxy */ = {
+ FAFAEB2DDD9CCD218336D267 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
- containerPortal = P_88EC38DC1E39C9039D2FA49EDC2FA124 /* Project object */;
+ containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
proxyType = 1;
- remoteGlobalIDString = NT_7D108AE86BED8C9CCF52C2646FA4C5DE;
+ remoteGlobalIDString = AE3F93DB94E7208F2F1D9A78;
remoteInfo = Framework_iOS;
};
+ FF75DC967D1097BC31DCF5E6 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = F192E783CCA898FBAA5C34EA /* AnotherProject */;
+ proxyType = 1;
+ remoteGlobalIDString = E76A5F5E363E470416D3B487;
+ remoteInfo = ExternalTarget;
+ };
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
- CFBP_0B6C520DAC94DDCE678BE15C53528F25 /* CopyFiles */ = {
+ 04D94C495E299B50EB0DC7C4 /* CopyFiles */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "$(PRODUCT_NAME).framework/Modules";
+ dstSubfolderSpec = 16;
+ files = (
+ B502EF8F7605CBD038298F23 /* CrossOverlayFramework.swiftcrossimport in CopyFiles */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 06FAE8D6834F982AA934B3E8 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
- BF_910C2D6055BD22643F042545CD21AA78 /* StaticLibrary_ObjC.h in CopyFiles */,
- BF_527E94EECC2501E12D28790BF9318FD3 /* module.modulemap in CopyFiles */,
+ 216B220EC7961DF7CA9188B7 /* StaticLibrary_ObjC.h in CopyFiles */,
+ BFCCC56337A5D9D513C1C791 /* module.modulemap in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
- CFBP_1C40B0777F31334440F91C2DB34EF404 /* Embed Frameworks */ = {
+ 096753D5DAA26D110F699A7F /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 10;
+ dstPath = "$(PRODUCT_NAME).framework/Modules";
+ dstSubfolderSpec = 16;
files = (
- BF_6EF6DB8898CD050E19303DC481EEFDC5 /* Framework2.framework in Embed Frameworks */,
- BF_63672615CB7B136F2706924EC460710A /* Framework.framework in Embed Frameworks */,
- BF_30024D558743C8ABC415D87431CDC13C /* SomeFramework.framework in Embed Frameworks */,
+ F6734680031310575CDE9F23 /* CrossOverlayFramework.swiftcrossimport in CopyFiles */,
);
- name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
- CFBP_2FE0B34CC045616C815F2675387DA9D5 /* Embed Frameworks */ = {
+ 30A8F3568B05F3DB13D8B466 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
- BF_F9E44FE6ECBD936A8C762D425DDBA37C /* Result.framework in Embed Frameworks */,
+ 0927149520F12314CE8B4079 /* TestFramework.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
- CFBP_3BE6FE084D6BE913354F37B1DD9A8D92 /* Embed Frameworks */ = {
+ 3217EBDE07BBCBDE3C16CEDC /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 10;
+ dstPath = "include/$(PRODUCT_NAME)";
+ dstSubfolderSpec = 16;
files = (
- BF_F6A0FC1F0C9A4EC13BCCBB764D716C3F /* Result.framework in Embed Frameworks */,
+ 95DD9941E1529FD2AE1A191D /* StaticLibrary_ObjC.h in CopyFiles */,
+ 768648ED7E93B6D888574144 /* module.modulemap in CopyFiles */,
);
- name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
- CFBP_469B033759EACBB99ECBF1008677C590 /* Embed App Extensions */ = {
+ 661E0CEEEC2395C39375961F /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
- BF_E6D3B938E2ED4C5339D11D340D3809EF /* App_watchOS Extension.appex in Embed App Extensions */,
+ 204958B9AD868004CCE6B779 /* App_watchOS Extension.appex in Embed Foundation Extensions */,
);
- name = "Embed App Extensions";
+ name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
- CFBP_4DFB7882C9F8BAB250BB0A6B9457B4CC /* CopyFiles */ = {
+ 7FAF0BBB3DE701EBE5DBE810 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
- BF_C5EA496FE2EDF51C92DC55FD552472E5 /* StaticLibrary_ObjC.h in CopyFiles */,
- BF_0B3CE605B6243480C374176E01B1BB12 /* module.modulemap in CopyFiles */,
+ 47FC57B04A3AD83359F433EA /* StaticLibrary_ObjC.h in CopyFiles */,
+ 66C3C5E3C13325F351A3008F /* module.modulemap in CopyFiles */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 807155B9081529D99AAB4743 /* Embed Watch Content */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
+ dstSubfolderSpec = 16;
+ files = (
+ 45E6702CD9C088FF1FC25F34 /* App_watchOS.app in Embed Watch Content */,
);
+ name = "Embed Watch Content";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 848740AD60C4329197FF876B /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 3DF22C477446669094AC7C8C /* ExternalTarget.framework in Embed Frameworks */,
+ 9C92B7C89E5F0A10A34F5AA4 /* Framework.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
- CFBP_712E86A86BBF89DB5B5F3874B24B8996 /* CopyFiles */ = {
+ 865AAD9909027AC34D1374EA /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
- BF_DA12D48C9BCEC64D55B87CE8703432BE /* StaticLibrary_ObjC.h in CopyFiles */,
- BF_703163C1C547AD6EF09BEA5F5F5ED49C /* module.modulemap in CopyFiles */,
+ 1E03FC7312293997599C6435 /* Empty.h in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
- CFBP_961F46C30E720E886AEFD11D45DDA199 /* Embed Watch Content */ = {
+ 924D7F0A22013EE6F06E7400 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
- dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
+ dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
- BF_CF8F05DAB384E10C59B65F4D0F51C109 /* App_watchOS.app in Embed Watch Content */,
+ 0AB541AE3163B063E7012877 /* StaticLibrary_ObjC.h in CopyFiles */,
+ C400EBD25886ACB5CD9035EB /* module.modulemap in CopyFiles */,
);
- name = "Embed Watch Content";
runOnlyForDeploymentPostprocessing = 0;
};
- CFBP_AD91D17C63B2CD9AE7EFB9EFD62A8223 /* Embed App Extensions */ = {
+ 94FF9CA021C43301BA069930 /* Embed App Clips */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 13;
+ dstPath = "$(CONTENTS_FOLDER_PATH)/AppClips";
+ dstSubfolderSpec = 16;
files = (
- BF_42222C3F9126FFCC21BA88AD21CFD964 /* iMessageExtension.appex in Embed App Extensions */,
+ 77C3CB285572EA4BB7E201A7 /* App_Clip.app in Embed App Clips */,
);
- name = "Embed App Extensions";
+ name = "Embed App Clips";
runOnlyForDeploymentPostprocessing = 0;
};
- CFBP_CB2355266C8BAA9C97863011F3DE05A2 /* Embed Frameworks */ = {
+ A8688B5E0D1C2F35AD20BB85 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
- BF_4FC40CAFC1CFAD431215635FE1A0ED86 /* Framework.framework in Embed Frameworks */,
- BF_8765851BF5D99B10C220DDD57B968B10 /* Result.framework in Embed Frameworks */,
+ 535A98A3E3B74E09891D977F /* TestFramework.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
- CFBP_CC1EF1963551F7E08925519982C248B9 /* CopyFiles */ = {
+ ADD98000970B8907F73BFD92 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
- dstPath = "include/$(PRODUCT_NAME)";
+ dstPath = "$(PRODUCT_NAME).framework/Modules";
dstSubfolderSpec = 16;
files = (
- BF_2356EAE09301354149D45720369BE7F2 /* StaticLibrary_ObjC.h in CopyFiles */,
- BF_1C8AAB7468188315681C0879591969B4 /* module.modulemap in CopyFiles */,
+ 052D6B4572FBF002286865D7 /* CrossOverlayFramework.swiftcrossimport in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
- CFBP_CC737A6BF6243B189B109606B0C4B5A2 /* CopyFiles */ = {
+ CF6B94E7B2D2312582A526F5 /* Embed Dependencies */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
- dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices";
+ dstPath = test;
+ dstSubfolderSpec = 13;
+ files = (
+ 81DFAB3A7633CE97929B9B2A /* Framework.framework in Embed Dependencies */,
+ );
+ name = "Embed Dependencies";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DE875E9A37F7CB9C347AEFA0 /* Embed System Extensions */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "$(SYSTEM_EXTENSIONS_FOLDER_PATH)";
dstSubfolderSpec = 16;
files = (
- BF_D9111FF58DCAA51B7251F882EC418E67 /* XPC Service.xpc in CopyFiles */,
- BF_67219751DE020023F9D6068EDCCDC445 /* SomeXPCService.xpc in CopyFiles */,
+ 5126CD91C2CB41C9B14B6232 /* DriverKitDriver.dext in Embed System Extensions */,
+ F4D77E81B0539EA5F4F141A6 /* EndpointSecuritySystemExtension.systemextension in Embed System Extensions */,
+ 4DA7140FF84DBF39961F3409 /* NetworkSystemExtension.systemextension in Embed System Extensions */,
);
+ name = "Embed System Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
- CFBP_D7E07645BC437C4DFBB212DFC4F3B09E /* CopyFiles */ = {
+ E1C04BDC65F3DC88D6D0473F /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
- dstPath = "include/$(PRODUCT_NAME)";
+ dstPath = "$(PRODUCT_NAME).framework/Modules";
+ dstSubfolderSpec = 16;
+ files = (
+ D058D241BDF5FB0C919BBECA /* CrossOverlayFramework.swiftcrossimport in CopyFiles */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ E8BC0F358D693454E5027ECC /* Copy Bundle Resources */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstSubfolderSpec = 7;
+ files = (
+ 3BBCA6F76E5F212E9C55FB78 /* BundleX.bundle in Copy Bundle Resources */,
+ 470D3493CDBFE56E2083A5FD /* BundleY.bundle in Copy Bundle Resources */,
+ );
+ name = "Copy Bundle Resources";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ EB212BCB1E2E1D2667233F98 /* Embed Foundation Extensions */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 13;
+ files = (
+ D62AB3A85B32F353ABBD57BC /* iMessageExtension.appex in Embed Foundation Extensions */,
+ );
+ name = "Embed Foundation Extensions";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ FB79B30FEA6073A29B4D9FCC /* CopyFiles */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices";
dstSubfolderSpec = 16;
files = (
- BF_90F0E9253F2768C312B65530931CD55A /* Empty.h in CopyFiles */,
+ 1E2A4D61E96521FF7123D7B0 /* XPC Service.xpc in CopyFiles */,
+ B49D3A51787E362DE4D0E78A /* SomeXPCService.xpc in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
+ FE78CC3322C9C2DB1D64EAAA /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 8;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 6241507B4947B0B65429587C /* ExternalTarget.framework in Embed Frameworks */,
+ 63D8E7F00276736EDA62D227 /* Framework2.framework in Embed Frameworks */,
+ B2D43A31C184E34EF9CB743C /* Framework.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 1;
+ };
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- FR_05405007CB77B2E003B19B89401C12AB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- FR_07C0A7866967FA47B3F2777BF4AB694A /* XPC_Service.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPC_Service.h; sourceTree = ""; };
- FR_0823766D9A1DAB5D3F9CC04A9B35FA3E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- FR_09608EA4613E62B3088BBA06F08E00E9 /* App_watchOS Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "App_watchOS Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
- FR_0DDA6B4CCFF0DEB79BE16E3F72B95C7F /* App_watchOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App_watchOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
- FR_0EDA4C9F9810C805C447660BF181FFF9 /* StaticLibrary_ObjC.a */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = archive.ar; path = StaticLibrary_ObjC.a; sourceTree = BUILT_PRODUCTS_DIR; };
- FR_11478908A963CED036DABEF21D85DF01 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Result.framework; sourceTree = ""; };
- FR_11E95FCD1DEB0C8A01A053518C1DAA8E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; };
- FR_1672C218968DD6996F05E3C7688F0718 /* App_iOS.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = App_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
- FR_16B791CFA2095097A17CC216977DF6EF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- FR_1B0304C0E4DC73614BAA550E4A80D41C /* ResourceFolder */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ResourceFolder; path = Resources/ResourceFolder; sourceTree = SOURCE_ROOT; };
- FR_1E05BB66BFD5629EAFDC86A4E869F864 /* iMessageApp.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = iMessageApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
- FR_20EF6A69E91EC1859026CEBF5AD33FC2 /* Framework2.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Framework2.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- FR_22A431E337CB22CE70E39135206EDE27 /* config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = config.xcconfig; sourceTree = ""; };
- FR_25BDF725104C5E280D45CBF33F54C72C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
- FR_261C31660333EF514356EFCBDB368EAB /* FrameworkFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrameworkFile.swift; sourceTree = ""; };
- FR_2649A01C17A7301DD72EA826604ED8AA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = ""; };
- FR_291D6D09ACADC420638E83BEE89DFFEB /* MyBundle.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = MyBundle.bundle; sourceTree = ""; };
- FR_2F56FD7A1F7782467AC9F315B6133468 /* SomeFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SomeFramework.framework; path = Vendor/SomeFramework.framework; sourceTree = ""; };
- FR_347062562C9C212A082B1326BBCDC71B /* Model 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Model 2.xcdatamodel"; sourceTree = ""; };
- FR_35B9B5E8A2ED60FBB9CED8AE515B16B5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/LocalizedStoryboard.strings; sourceTree = ""; };
- FR_3744DA35690747A918CC896E2354C59D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- FR_3B4065B548BFD8CFB3D1153F7106DDA3 /* Folder */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Folder; sourceTree = SOURCE_ROOT; };
- FR_3DD4DE355C21662A9169EE41C44F73E3 /* Headers */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Headers; sourceTree = SOURCE_ROOT; };
- FR_3ED99EEAF978C071491E281B8EAFD249 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- FR_41E1155D8A7FDD97B783A1D5B3AD2C5B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- FR_4B58A01ABBE8E0A56B7B5A539C9BF5C7 /* Base */ = {isa = PBXFileReference; name = Base; path = Base.lproj/Localizable.stringsdict; sourceTree = ""; };
- FR_4DB225C7FF39C16EEFD9A1A5595FCDBC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
- FR_4EB4AAACD714C13E5BC894C71B954299 /* SomeXPCService.xpc */ = {isa = PBXFileReference; path = SomeXPCService.xpc; sourceTree = ""; };
- FR_5465435FA4A3763830C566514B358969 /* StaticLibrary_Swift.a */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = archive.ar; path = StaticLibrary_Swift.a; sourceTree = BUILT_PRODUCTS_DIR; };
- FR_586C1089A4869FFCB50BE6A26B0FA1E7 /* iMessageExtension.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = iMessageExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
- FR_58A974F0E117C1384FD1B5147E524659 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; };
- FR_5B3188BDA6DB0560398A483646F17AE4 /* App_iOS_Tests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = App_iOS_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- FR_5CFDEA59B939FA9F3CA4F775B9E6AD2B /* TestProjectUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestProjectUITests.swift; sourceTree = ""; };
- FR_5E4B4E53251DE2294715A48CC249EBFD /* Mintfile */ = {isa = PBXFileReference; path = Mintfile; sourceTree = ""; };
- FR_5FEC20FF753341FD0483E2E4C622DB05 /* MessagesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesViewController.swift; sourceTree = ""; };
- FR_62ECE5A3D0F25415CB50A28226B91EBE /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; };
- FR_6643E0FE8DD9AAC71691A739070C6833 /* Model 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Model 3.xcdatamodel"; sourceTree = ""; };
- FR_752FB5DFFBC490CFB9742549A0C48527 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
- FR_7532DD7B78451A5040048474AC4FBCCC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
- FR_7C782E8FBF41DE8BB1E3789DF2C8C1F7 /* PushNotificationPayload.apns */ = {isa = PBXFileReference; lastKnownFileType = text; path = PushNotificationPayload.apns; sourceTree = ""; };
- FR_7C8280C3C5E1D43BBB12A5F0156A8305 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; };
- FR_7F4496C534F2DF8C4FB9A8D52414990B /* XPC_Service.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPC_Service.m; sourceTree = ""; };
- FR_81A08F81FED7C42DC346B9611ECD21AA /* XPC_ServiceProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPC_ServiceProtocol.h; sourceTree = ""; };
- FR_82E3C6C060C4487B5177509917C3FAE3 /* Standalone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Standalone.swift; sourceTree = ""; };
- FR_8B770F475242D91FC20289A3B35CD165 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Result.framework; sourceTree = ""; };
- FR_8FCFCDD6A5A7292C72787E2FC0A36294 /* XPC Service.xpc */ = {isa = PBXFileReference; includeInIndex = 0; path = "XPC Service.xpc"; sourceTree = BUILT_PRODUCTS_DIR; };
- FR_9403DF729F7FE2E6B823C1DACE1E6214 /* App_iOS_UITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = App_iOS_UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- FR_96127F4D9D804B89024AB846F0961621 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.module; path = module.modulemap; sourceTree = ""; };
- FR_98BB4C8D33EB0E666C136ACD08B21EB3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- FR_9B4B00A3CDADD50167B2393562AEBAB2 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.module; path = module.modulemap; sourceTree = ""; };
- FR_A0867B127ACF3ED382EB2FD5133D3EA8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- FR_A51DE87AC269BC36016F9CAC32B4ECB2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LocalizedStoryboard.storyboard; sourceTree = ""; };
- FR_A55A35F549FD72775C37ED05342812AA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- FR_A957DAE2193BE1E970F452BFEFF3EBF6 /* MyFramework.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MyFramework.h; sourceTree = ""; };
- FR_AC5B2FCE520D5306B254D5857E15B6CB /* MoreUnder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreUnder.swift; sourceTree = ""; };
- FR_B243640A6F73B3F3D33ABD05ABDBC26B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- FR_B537FEE8515090D3BA0F5CFF3BC76BB1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- FR_B993F75B001AB1C272CE83CACC06F0E5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
- FR_BB49B398F9291781A60DA963A0BF168C /* NotificationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationController.swift; sourceTree = ""; };
- FR_BCEF8D29C9B93D3160D8D76DD368C8AD /* en */ = {isa = PBXFileReference; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; };
- FR_C2A280C4FA602E6610BCFB820602B69E /* StaticLibrary_ObjC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StaticLibrary_ObjC.m; sourceTree = ""; };
- FR_C5682A1371F91CBD1254C115F0439F12 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
- FR_C6A9DE885CAF350F6C9D0AC6F7324CD3 /* StandaloneAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = StandaloneAssets.xcassets; sourceTree = ""; };
- FR_C73B96CEEE97DE429C638EBD9C2F3D2B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
- FR_C86A7EAE4DFF4327D1E7D32A597CA4AC /* SomeFile */ = {isa = PBXFileReference; path = SomeFile; sourceTree = ""; };
- FR_CB1D0F4BB53B8DEE921C6E232193A62B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- FR_CC3A24A5C8E1815346752C5AB0745176 /* InterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceController.swift; sourceTree = ""; };
- FR_CCC97AF230188CAF9B4A66324891CCAD /* StaticLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLibrary.swift; sourceTree = ""; };
- FR_CEAE8D9C3F3920D6EADE5455C188EAAE /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Result.framework; sourceTree = ""; };
- FR_D0BE69522DB875ADB041E9135E0767CA /* StaticLibrary_ObjC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StaticLibrary_ObjC.h; sourceTree = ""; };
- FR_D7B45FAEE40EE622B4FA9192609F9717 /* TestProjectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestProjectTests.swift; sourceTree = ""; };
- FR_DACDD51068A6E1B7D2470F3E27E5EB2C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- FR_DBB69305458B16774C88720CDD3978E5 /* ExtensionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionDelegate.swift; sourceTree = ""; };
- FR_DBD29CA78CDBBEF69B2C39C6D23BBDA1 /* Empty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Empty.h; sourceTree = ""; };
- FR_DC1C8DE46218F90A3F4FD5F8DE4F0ABB /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Result.framework; sourceTree = ""; };
- FR_E3314150DA4CEFF65D69CF7DB678E845 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- FR_ED407ECED0DF010B1E787D238EA91A5D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- FR_F18AD4408D0A35A0B9EAE353E8087CDE /* App_macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App_macOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
- FR_F25AB3F5F1D72653F078DF0E063DC9A5 /* Framework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Framework.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- FR_F5353B71B99044C8C37D7601A8107195 /* base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = base.xcconfig; sourceTree = ""; };
- FR_F5436E663145426483F206F8A61400BC /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
- FR_F861CF6DF133AAD164CBE6DF2C7CEBFF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 0095836FE59395511E0CB4F0 /* CrossOverlayFramework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CrossOverlayFramework.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 01E6934B571B91EAAFF0EDCB /* Resource.abc */ = {isa = PBXFileReference; path = Resource.abc; sourceTree = ""; };
+ 020E4DA91C9132845CAFDC5D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; };
+ 039F208D1138598CE060F140 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
+ 03CD22B8CD2E91BB97CC534E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
+ 03D6D1E34022DA9524E5B38D /* Mintfile */ = {isa = PBXFileReference; path = Mintfile; sourceTree = ""; };
+ 0510CEA09E3BFD387E3EDE28 /* MyPlayground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = MyPlayground.playground; sourceTree = ""; };
+ 068EDF47F0B087F6A4052AC0 /* Empty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Empty.h; sourceTree = ""; };
+ 0704B6CAFBB53E0EBB08F6B3 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
+ 09B82F603D981398F38D762E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 0B193CC6D2B3003418A550B6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/LocalizedStoryboard.strings; sourceTree = ""; };
+ 0B9D98D935F2C69A1F5BA539 /* App_macOS_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = App_macOS_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 0BB1B49A91B892152D68ED76 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
+ 0BC75409252FF15F540FBB7B /* libEndpointSecurity.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libEndpointSecurity.tbd; path = usr/lib/libEndpointSecurity.tbd; sourceTree = SDKROOT; };
+ 0D09D243DBCF9D32E239F1E8 /* App_watchOS Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "App_watchOS Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 0F5BD97AF0F94A15A5B7DDB7 /* Standalone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Standalone.swift; sourceTree = ""; };
+ 102A08142A31E44F4ED52649 /* base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = base.xcconfig; sourceTree = ""; };
+ 108BB29172D27BE3BD1E7F35 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 13EEAB58665D79C15184D9D0 /* App_iOS_UITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = App_iOS_UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 148B7C933698BCC4F1DBA979 /* XPC_Service.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPC_Service.m; sourceTree = ""; };
+ 15A4363D659A58DA835DE8BA /* MyAppApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAppApp.swift; sourceTree = ""; };
+ 16AA52945B70B1BF9E246350 /* FilterDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterDataProvider.swift; sourceTree = ""; };
+ 16D662EE577E4CD6AFF39D66 /* config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = config.xcconfig; sourceTree = ""; };
+ 18722C61B05FFF4CC63D5755 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
+ 187E665975BB5611AF0F27E1 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
+ 1BC32A813B80A53962A1F365 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 1D0C79A8C750EC0DE748C463 /* StaticLibrary_ObjC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StaticLibrary_ObjC.m; sourceTree = ""; };
+ 1FA5E208EC184E3030D2A21D /* Clip.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Clip.entitlements; sourceTree = ""; };
+ 2049B6DD2AFE85F9DC9F3EB3 /* NetworkSystemExtension.systemextension */ = {isa = PBXFileReference; explicitFileType = "wrapper.system-extension"; includeInIndex = 0; path = NetworkSystemExtension.systemextension; sourceTree = BUILT_PRODUCTS_DIR; };
+ 22237B8EBD9E6BE8EBC8735F /* XPC Service.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = "XPC Service.xpc"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 2233774B86539B1574D206B0 /* Framework2.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Framework2.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 2385A62F6C6EE8D461EE19F2 /* ExternalTarget.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ExternalTarget.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 23A2F16890ECF2EE3FED72AE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 28360ECA4D727FAA58557A81 /* example.mp4 */ = {isa = PBXFileReference; path = example.mp4; sourceTree = ""; };
+ 2A5F527F2590C14956518174 /* FrameworkFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrameworkFile.swift; sourceTree = ""; };
+ 2E1E747C7BC434ADB80CC269 /* Headers */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Headers; sourceTree = SOURCE_ROOT; };
+ 2F430AABE04B7499B458D9DB /* SwiftFileInDotPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftFileInDotPath.swift; sourceTree = ""; };
+ 3096A0760969873D46F80A92 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 325F18855099386B08DD309B /* Resource.abcd */ = {isa = PBXFileReference; path = Resource.abcd; sourceTree = ""; };
+ 33F6DCDC37D2E66543D4965D /* App_macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App_macOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 34F13B632328979093CE6056 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
+ 3571E41E19A5AB8AAAB04109 /* StandaloneAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = StandaloneAssets.xcassets; sourceTree = ""; };
+ 3797E591F302ECC0AA2FC607 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 382E11E88B12BCB30F575686 /* Driver.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Driver.entitlements; sourceTree = ""; };
+ 38DB679FF1CF4E379D1AB103 /* App_Clip.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = App_Clip.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 3A7BEFAB4710735CF169B1E8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
+ 3D8A2D4363866877B9140156 /* XPC_ServiceProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPC_ServiceProtocol.h; sourceTree = ""; };
+ 3ED831531AA349CCC19B258B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "