mirror of
https://github.com/yonaskolb/XcodeGen.git
synced 2024-09-11 12:56:03 +03:00
Compare commits
18 Commits
50d6add262
...
2d14bd7e8f
Author | SHA1 | Date | |
---|---|---|---|
|
2d14bd7e8f | ||
|
f51719ce29 | ||
|
1b0720d139 | ||
|
576739bcb5 | ||
|
aa79a3ed0b | ||
|
d99e448647 | ||
|
274ce7342c | ||
|
17acb4dc61 | ||
|
447cc7f9ac | ||
|
9816466703 | ||
|
bc08f53505 | ||
|
0301741002 | ||
|
ecb9b560ae | ||
|
632ca2d419 | ||
|
1645d419ce | ||
|
6694943ad8 | ||
|
9df3e62734 | ||
|
d04986e0e3 |
31
CHANGELOG.md
31
CHANGELOG.md
@ -2,6 +2,37 @@
|
|||||||
|
|
||||||
## Next Version
|
## Next Version
|
||||||
|
|
||||||
|
## 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
|
## 2.39.1
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
These are a bunch of real world examples of XcodeGen project specs. Feel free to add your own via PR.
|
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/Examples/CocoaPodsExample.yml)
|
|
||||||
- [toshi0383/Bitrise-iOS](https://github.com/toshi0383/Bitrise-iOS/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)
|
- [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/AppRepositoryTemplate](https://github.com/atelier-socle/AppRepositoryTemplate/blob/master/project.yml)
|
||||||
|
10
Docs/FAQ.md
10
Docs/FAQ.md
@ -9,10 +9,14 @@ 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.
|
>Note that you can run `xcodegen` as a step in your build process on CI.
|
||||||
|
|
||||||
## What happens when I switch branches
|
## 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:
|
||||||
It's recommended to use `--use-cache` so that the project is not needlessly generated.
|
- 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
|
## Can I use CocoaPods
|
||||||
Yes, you will just need to run `pod install` after the project is generated to integrate Cocoapods changes.
|
Yes, you will just need to run `pod install` after the project is generated to integrate Cocoapods changes.
|
||||||
|
@ -147,7 +147,7 @@ Note that target names can also be changed by adding a `name` property to a targ
|
|||||||
- [ ] **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`.
|
- [ ] **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`.
|
- [ ] **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`.
|
- [ ] **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`
|
- [ ] **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.
|
- [ ] **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.
|
- [ ] **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.
|
- [ ] **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.
|
||||||
@ -483,6 +483,7 @@ This will provide a mix of default build settings for the chosen platform destin
|
|||||||
- `macOS`
|
- `macOS`
|
||||||
- `macCatalyst`
|
- `macCatalyst`
|
||||||
- `visionOS`
|
- `visionOS`
|
||||||
|
- `watchOS`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
targets:
|
targets:
|
||||||
@ -499,7 +500,7 @@ targets:
|
|||||||
destinationFilters: [iOS]
|
destinationFilters: [iOS]
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the definition of supported destinations can be applied to every type of bundle making everything more easy to manage (app targets, unit tests, UI tests etc).
|
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
|
### Sources
|
||||||
|
|
||||||
@ -1140,12 +1141,16 @@ schemes:
|
|||||||
MyTarget2: [run, archive]
|
MyTarget2: [run, archive]
|
||||||
run:
|
run:
|
||||||
config: prod-debug
|
config: prod-debug
|
||||||
commandLineArguments: "--option value"
|
commandLineArguments:
|
||||||
|
"-MyEnabledArg": true
|
||||||
|
"-MyDisabledArg": false
|
||||||
environmentVariables:
|
environmentVariables:
|
||||||
RUN_ENV_VAR: VALUE
|
RUN_ENV_VAR: VALUE
|
||||||
test:
|
test:
|
||||||
config: prod-debug
|
config: prod-debug
|
||||||
commandLineArguments: "--option testValue"
|
commandLineArguments:
|
||||||
|
"-MyEnabledArg": true
|
||||||
|
"-MyDisabledArg": false
|
||||||
gatherCoverageData: true
|
gatherCoverageData: true
|
||||||
coverageTargets:
|
coverageTargets:
|
||||||
- MyTarget1
|
- MyTarget1
|
||||||
@ -1240,7 +1245,7 @@ Swift packages are defined at a project level, and then linked to individual tar
|
|||||||
### Local Package
|
### Local Package
|
||||||
|
|
||||||
- [x] **path**: **String** - the path to the package in local. The path must be directory with a `Package.swift`.
|
- [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.
|
- [ ] **group** : **String**- Optional path that specifies the location where the package will live in your xcode project. Use `""` to specify the project root.
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
packages:
|
packages:
|
||||||
|
8
Makefile
8
Makefile
@ -1,14 +1,12 @@
|
|||||||
TOOL_NAME = XcodeGen
|
TOOL_NAME = XcodeGen
|
||||||
export EXECUTABLE_NAME = xcodegen
|
export EXECUTABLE_NAME = xcodegen
|
||||||
VERSION = 2.39.1
|
VERSION = 2.41.0
|
||||||
|
|
||||||
PREFIX = /usr/local
|
PREFIX = /usr/local
|
||||||
INSTALL_PATH = $(PREFIX)/bin/$(EXECUTABLE_NAME)
|
INSTALL_PATH = $(PREFIX)/bin/$(EXECUTABLE_NAME)
|
||||||
SHARE_PATH = $(PREFIX)/share/$(EXECUTABLE_NAME)
|
SHARE_PATH = $(PREFIX)/share/$(EXECUTABLE_NAME)
|
||||||
CURRENT_PATH = $(PWD)
|
CURRENT_PATH = $(PWD)
|
||||||
REPO = https://github.com/yonaskolb/$(TOOL_NAME)
|
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
|
SWIFT_BUILD_FLAGS = --disable-sandbox -c release --arch arm64 --arch x86_64
|
||||||
BUILD_PATH = $(shell swift build $(SWIFT_BUILD_FLAGS) --show-bin-path)
|
BUILD_PATH = $(shell swift build $(SWIFT_BUILD_FLAGS) --show-bin-path)
|
||||||
EXECUTABLE_PATH = $(BUILD_PATH)/$(EXECUTABLE_NAME)
|
EXECUTABLE_PATH = $(BUILD_PATH)/$(EXECUTABLE_NAME)
|
||||||
@ -42,10 +40,6 @@ release:
|
|||||||
publish: archive brew
|
publish: archive brew
|
||||||
echo "published $(VERSION)"
|
echo "published $(VERSION)"
|
||||||
|
|
||||||
brew:
|
|
||||||
brew update
|
|
||||||
brew bump-formula-pr --url=$(RELEASE_TAR) XcodeGen
|
|
||||||
|
|
||||||
archive: build
|
archive: build
|
||||||
./scripts/archive.sh "$(EXECUTABLE_PATH)"
|
./scripts/archive.sh "$(EXECUTABLE_PATH)"
|
||||||
swift package plugin --allow-writing-to-package-directory generate-artifact-bundle \
|
swift package plugin --allow-writing-to-package-directory generate-artifact-bundle \
|
||||||
|
@ -113,7 +113,7 @@ swift run xcodegen
|
|||||||
Add the following to your Package.swift file's dependencies:
|
Add the following to your Package.swift file's dependencies:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
.package(url: "https://github.com/yonaskolb/XcodeGen.git", from: "2.39.1"),
|
.package(url: "https://github.com/yonaskolb/XcodeGen.git", from: "2.41.0"),
|
||||||
```
|
```
|
||||||
|
|
||||||
And then import wherever needed: `import XcodeGenKit`
|
And then import wherever needed: `import XcodeGenKit`
|
||||||
@ -136,7 +136,7 @@ Options:
|
|||||||
- **--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.
|
- **--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}`
|
- **--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 out output the resolved spec in many different formats, or write it to a file. Use `xcodegen help` to see more detailed usage information.
|
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.
|
||||||
|
|
||||||
## Dependency Diagrams
|
## Dependency Diagrams
|
||||||
<details>
|
<details>
|
||||||
|
@ -8,4 +8,3 @@
|
|||||||
1. Push commit and tag to github
|
1. Push commit and tag to github
|
||||||
1. Create release from tag on GitHub using the version number and relevant changelog contents
|
1. Create release from tag on GitHub using the version number and relevant changelog contents
|
||||||
1. Run `make archive` and upload `xcodegen.zip` and `xcodegen.artifactbundle.zip` to the github release
|
1. Run `make archive` and upload `xcodegen.zip` and `xcodegen.artifactbundle.zip` to the github release
|
||||||
1. Run `make brew` which will open a PR on homebrew core
|
|
@ -8,7 +8,7 @@ public typealias BuildType = XCScheme.BuildAction.Entry.BuildFor
|
|||||||
public struct Scheme: Equatable {
|
public struct Scheme: Equatable {
|
||||||
|
|
||||||
public var name: String
|
public var name: String
|
||||||
public var build: Build
|
public var build: Build!
|
||||||
public var run: Run?
|
public var run: Run?
|
||||||
public var archive: Archive?
|
public var archive: Archive?
|
||||||
public var analyze: Analyze?
|
public var analyze: Analyze?
|
||||||
@ -18,7 +18,7 @@ public struct Scheme: Equatable {
|
|||||||
|
|
||||||
public init(
|
public init(
|
||||||
name: String,
|
name: String,
|
||||||
build: Build,
|
build: Build? = nil,
|
||||||
run: Run? = nil,
|
run: Run? = nil,
|
||||||
test: Test? = nil,
|
test: Test? = nil,
|
||||||
profile: Profile? = nil,
|
profile: Profile? = nil,
|
||||||
@ -88,10 +88,12 @@ public struct Scheme: Equatable {
|
|||||||
public var script: String
|
public var script: String
|
||||||
public var name: String
|
public var name: String
|
||||||
public var settingsTarget: 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.script = script
|
||||||
self.name = name
|
self.name = name
|
||||||
self.settingsTarget = settingsTarget
|
self.settingsTarget = settingsTarget
|
||||||
|
self.shell = shell
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,6 +402,7 @@ extension Scheme.ExecutionAction: JSONObjectConvertible {
|
|||||||
script = try jsonDictionary.json(atKeyPath: "script")
|
script = try jsonDictionary.json(atKeyPath: "script")
|
||||||
name = jsonDictionary.json(atKeyPath: "name") ?? "Run Script"
|
name = jsonDictionary.json(atKeyPath: "name") ?? "Run Script"
|
||||||
settingsTarget = jsonDictionary.json(atKeyPath: "settingsTarget")
|
settingsTarget = jsonDictionary.json(atKeyPath: "settingsTarget")
|
||||||
|
shell = jsonDictionary.json(atKeyPath: "shell")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,6 +412,7 @@ extension Scheme.ExecutionAction: JSONEncodable {
|
|||||||
"script": script,
|
"script": script,
|
||||||
"name": name,
|
"name": name,
|
||||||
"settingsTarget": settingsTarget,
|
"settingsTarget": settingsTarget,
|
||||||
|
"shell": shell
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -787,7 +791,7 @@ extension Scheme: NamedJSONDictionaryConvertible {
|
|||||||
|
|
||||||
public init(name: String, jsonDictionary: JSONDictionary) throws {
|
public init(name: String, jsonDictionary: JSONDictionary) throws {
|
||||||
self.name = name
|
self.name = name
|
||||||
build = try jsonDictionary.json(atKeyPath: "build")
|
build = jsonDictionary.json(atKeyPath: "build")
|
||||||
run = jsonDictionary.json(atKeyPath: "run")
|
run = jsonDictionary.json(atKeyPath: "run")
|
||||||
test = jsonDictionary.json(atKeyPath: "test")
|
test = jsonDictionary.json(atKeyPath: "test")
|
||||||
analyze = jsonDictionary.json(atKeyPath: "analyze")
|
analyze = jsonDictionary.json(atKeyPath: "analyze")
|
||||||
@ -800,7 +804,7 @@ extension Scheme: NamedJSONDictionaryConvertible {
|
|||||||
extension Scheme: JSONEncodable {
|
extension Scheme: JSONEncodable {
|
||||||
public func toJSONValue() -> Any {
|
public func toJSONValue() -> Any {
|
||||||
[
|
[
|
||||||
"build": build.toJSONValue(),
|
"build": build?.toJSONValue(),
|
||||||
"run": run?.toJSONValue(),
|
"run": run?.toJSONValue(),
|
||||||
"test": test?.toJSONValue(),
|
"test": test?.toJSONValue(),
|
||||||
"analyze": analyze?.toJSONValue(),
|
"analyze": analyze?.toJSONValue(),
|
||||||
|
@ -185,6 +185,12 @@ extension Project {
|
|||||||
errors.append(.unexpectedTargetPlatformForSupportedDestinations(target: target.name, platform: target.platform))
|
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,
|
if target.supportedDestinations?.contains(.macOS) == true,
|
||||||
target.supportedDestinations?.contains(.macCatalyst) == true {
|
target.supportedDestinations?.contains(.macCatalyst) == true {
|
||||||
|
|
||||||
@ -212,9 +218,11 @@ extension Project {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for scheme in schemes {
|
for scheme in schemes {
|
||||||
errors.append(
|
if let action = scheme.build {
|
||||||
contentsOf: scheme.build.targets.compactMap { validationError(for: $0.target, in: scheme, action: "build") }
|
errors.append(contentsOf: action.targets.compactMap {
|
||||||
)
|
validationError(for: $0.target, in: scheme, action: "build")
|
||||||
|
})
|
||||||
|
}
|
||||||
if let action = scheme.run, let config = action.config, getConfig(config) == nil {
|
if let action = scheme.run, let config = action.config, getConfig(config) == nil {
|
||||||
errors.append(.invalidSchemeConfig(scheme: scheme.name, config: config))
|
errors.append(.invalidSchemeConfig(scheme: scheme.name, config: config))
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ public struct SpecValidationError: Error, CustomStringConvertible {
|
|||||||
case invalidTargetSchemeTest(target: String, testTarget: String)
|
case invalidTargetSchemeTest(target: String, testTarget: String)
|
||||||
case invalidTargetPlatformForSupportedDestinations(target: String)
|
case invalidTargetPlatformForSupportedDestinations(target: String)
|
||||||
case unexpectedTargetPlatformForSupportedDestinations(target: String, platform: Platform)
|
case unexpectedTargetPlatformForSupportedDestinations(target: String, platform: Platform)
|
||||||
|
case containsWatchOSDestinationForMultiplatformApp(target: String)
|
||||||
case multipleMacPlatformsInSupportedDestinations(target: String)
|
case multipleMacPlatformsInSupportedDestinations(target: String)
|
||||||
case missingTargetPlatformInSupportedDestinations(target: String, platform: Platform)
|
case missingTargetPlatformInSupportedDestinations(target: String, platform: Platform)
|
||||||
case invalidSchemeTarget(scheme: String, target: String, action: String)
|
case invalidSchemeTarget(scheme: String, target: String, action: String)
|
||||||
@ -66,6 +67,8 @@ public struct SpecValidationError: Error, CustomStringConvertible {
|
|||||||
return "Target \(target.quoted) has multiple definitions of mac platforms in supported destinations"
|
return "Target \(target.quoted) has multiple definitions of mac platforms in supported destinations"
|
||||||
case let .missingTargetPlatformInSupportedDestinations(target, platform):
|
case let .missingTargetPlatformInSupportedDestinations(target, platform):
|
||||||
return "Target \(target.quoted) has platform \(platform.rawValue.quoted) that is missing in supported destinations"
|
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):
|
case let .invalidConfigFile(configFile, config):
|
||||||
return "Invalid config file \(configFile.quoted) for config \(config.quoted)"
|
return "Invalid config file \(configFile.quoted) for config \(config.quoted)"
|
||||||
case let .invalidSchemeTarget(scheme, target, action):
|
case let .invalidSchemeTarget(scheme, target, action):
|
||||||
|
@ -3,6 +3,6 @@ import ProjectSpec
|
|||||||
import XcodeGenCLI
|
import XcodeGenCLI
|
||||||
import Version
|
import Version
|
||||||
|
|
||||||
let version = Version("2.39.1")
|
let version = Version("2.41.0")
|
||||||
let cli = XcodeGenCLI(version: version)
|
let cli = XcodeGenCLI(version: version)
|
||||||
cli.execute()
|
cli.execute()
|
||||||
|
44
Sources/XcodeGenCLI/Commands/CacheCommand.swift
Normal file
44
Sources/XcodeGenCLI/Commands/CacheCommand.swift
Normal file
@ -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)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -49,7 +49,7 @@ class DumpCommand: ProjectCommand {
|
|||||||
try file.parent().mkpath()
|
try file.parent().mkpath()
|
||||||
try file.write(output)
|
try file.write(output)
|
||||||
} else {
|
} else {
|
||||||
stdout.print(output)
|
success(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,6 @@ import Version
|
|||||||
|
|
||||||
class GenerateCommand: ProjectCommand {
|
class GenerateCommand: ProjectCommand {
|
||||||
|
|
||||||
@Flag("-q", "--quiet", description: "Suppress all informational and success output")
|
|
||||||
var quiet: Bool
|
|
||||||
|
|
||||||
@Flag("-c", "--use-cache", description: "Use a cache for the xcodegen spec. This will prevent unnecessarily generating the project if nothing has changed")
|
@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
|
var useCache: Bool
|
||||||
|
|
||||||
@ -46,7 +43,7 @@ class GenerateCommand: ProjectCommand {
|
|||||||
Path("~/.xcodegen/cache/\(projectSpecPath.absolute().string.md5)").absolute()
|
Path("~/.xcodegen/cache/\(projectSpecPath.absolute().string.md5)").absolute()
|
||||||
var cacheFile: CacheFile?
|
var cacheFile: CacheFile?
|
||||||
|
|
||||||
// read cache
|
// generate cache
|
||||||
if useCache || self.cacheFilePath != nil {
|
if useCache || self.cacheFilePath != nil {
|
||||||
do {
|
do {
|
||||||
cacheFile = try specLoader.generateCacheFile()
|
cacheFile = try specLoader.generateCacheFile()
|
||||||
@ -138,22 +135,4 @@ class GenerateCommand: ProjectCommand {
|
|||||||
try Task.run(bash: command, directory: projectDirectory.absolute().string)
|
try Task.run(bash: command, directory: projectDirectory.absolute().string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,9 @@ class ProjectCommand: Command {
|
|||||||
let name: String
|
let name: String
|
||||||
let shortDescription: 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.)")
|
@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?
|
var spec: String?
|
||||||
|
|
||||||
@ -58,4 +61,22 @@ class ProjectCommand: Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func execute(specLoader: SpecLoader, projectSpecPath: Path, project: Project) throws {}
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ public class XcodeGenCLI {
|
|||||||
description: "Generates Xcode projects",
|
description: "Generates Xcode projects",
|
||||||
commands: [
|
commands: [
|
||||||
generateCommand,
|
generateCommand,
|
||||||
|
CacheCommand(version: version),
|
||||||
DumpCommand(version: version),
|
DumpCommand(version: version),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -631,8 +631,8 @@ public class PBXProjGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let order = groupOrdering?.order {
|
if let order = groupOrdering?.order {
|
||||||
let files = group.children.filter { $0 is PBXFileReference }
|
let files = group.children.filter { !$0.isGroupOrFolder }
|
||||||
var groups = group.children.filter { $0 is PBXGroup }
|
var groups = group.children.filter { $0.isGroupOrFolder }
|
||||||
|
|
||||||
var filteredGroups = [PBXFileElement]()
|
var filteredGroups = [PBXFileElement]()
|
||||||
|
|
||||||
@ -1626,6 +1626,10 @@ extension Platform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension PBXFileElement {
|
extension PBXFileElement {
|
||||||
|
/// - returns: `true` if the element is a group or a folder reference. Likely an SPM package.
|
||||||
|
var isGroupOrFolder: Bool {
|
||||||
|
self is PBXGroup || (self as? PBXFileReference)?.lastKnownFileType == "folder"
|
||||||
|
}
|
||||||
|
|
||||||
public func getSortOrder(groupSortPosition: SpecOptions.GroupSortPosition) -> Int {
|
public func getSortOrder(groupSortPosition: SpecOptions.GroupSortPosition) -> Int {
|
||||||
if type(of: self).isa == "PBXGroup" {
|
if type(of: self).isa == "PBXGroup" {
|
||||||
|
@ -199,7 +199,7 @@ public class SchemeGenerator {
|
|||||||
|
|
||||||
let testBuildTargetEntries = try testBuildTargets.map(getBuildEntry)
|
let testBuildTargetEntries = try testBuildTargets.map(getBuildEntry)
|
||||||
|
|
||||||
let buildActionEntries: [XCScheme.BuildAction.Entry] = try scheme.build.targets.map(getBuildEntry)
|
let buildActionEntries: [XCScheme.BuildAction.Entry] = try scheme.build?.targets.map(getBuildEntry) ?? []
|
||||||
|
|
||||||
func getExecutionAction(_ action: Scheme.ExecutionAction) -> XCScheme.ExecutionAction {
|
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.
|
// ExecutionActions can require the use of build settings. Xcode allows the settings to come from a build or test target.
|
||||||
@ -208,34 +208,54 @@ public class SchemeGenerator {
|
|||||||
.first { settingsTarget == $0.buildableReference.blueprintName }?
|
.first { settingsTarget == $0.buildableReference.blueprintName }?
|
||||||
.buildableReference
|
.buildableReference
|
||||||
}
|
}
|
||||||
return XCScheme.ExecutionAction(scriptText: action.script, title: action.name, environmentBuildable: environmentBuildable)
|
return XCScheme.ExecutionAction(
|
||||||
|
scriptText: action.script,
|
||||||
|
title: action.name,
|
||||||
|
shellToInvoke: action.shell,
|
||||||
|
environmentBuildable: environmentBuildable
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let schemeTarget: ProjectTarget?
|
let schemeTarget: ProjectTarget?
|
||||||
|
|
||||||
if let targetName = scheme.run?.executable {
|
if let targetName = scheme.run?.executable {
|
||||||
schemeTarget = project.getTarget(targetName)
|
schemeTarget = project.getTarget(targetName)
|
||||||
} else {
|
} else if
|
||||||
guard let firstTarget = scheme.build.targets.first else {
|
let targets = scheme.build?.targets,
|
||||||
throw SchemeGenerationError.missingBuildTargets(scheme.name)
|
let firstTarget = targets.first
|
||||||
}
|
{
|
||||||
let name = scheme.build.targets.first { $0.buildTypes.contains(.running) }?.target.name ?? firstTarget.target.name
|
let name = targets.first { $0.buildTypes.contains(.running) }?.target.name ?? firstTarget.target.name
|
||||||
schemeTarget = target ?? project.getTarget(name)
|
schemeTarget = target ?? project.getTarget(name)
|
||||||
|
} else {
|
||||||
|
schemeTarget = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let shouldExecuteOnLaunch = schemeTarget?.shouldExecuteOnLaunch == true
|
let shouldExecuteOnLaunch = schemeTarget?.shouldExecuteOnLaunch == true
|
||||||
|
let buildableReference: XCScheme.BuildableReference?
|
||||||
|
let buildAction: XCScheme.BuildAction?
|
||||||
|
let runnables: (launch: XCScheme.Runnable, profile: XCScheme.BuildableProductRunnable)?
|
||||||
|
|
||||||
let buildableReference = buildActionEntries.first(where: { $0.buildableReference.blueprintName == schemeTarget?.name })?.buildableReference ?? buildActionEntries.first!.buildableReference
|
if let buildableReferenceCandidate = buildActionEntries.first(where: { $0.buildableReference.blueprintName == schemeTarget?.name })?.buildableReference ?? buildActionEntries.first?.buildableReference
|
||||||
let runnables = makeProductRunnables(for: schemeTarget, buildableReference: buildableReference)
|
{
|
||||||
|
runnables = makeProductRunnables(for: schemeTarget, buildableReference: buildableReferenceCandidate)
|
||||||
|
|
||||||
|
buildableReference = buildableReferenceCandidate
|
||||||
|
buildAction = scheme.build.flatMap { build in
|
||||||
|
XCScheme.BuildAction(
|
||||||
|
buildActionEntries: buildActionEntries,
|
||||||
|
preActions: build.preActions.map(getExecutionAction),
|
||||||
|
postActions: build.postActions.map(getExecutionAction),
|
||||||
|
parallelizeBuild: build.parallelizeBuild,
|
||||||
|
buildImplicitDependencies: build.buildImplicitDependencies,
|
||||||
|
runPostActionsOnFailure: build.runPostActionsOnFailure
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buildableReference = nil
|
||||||
|
buildAction = nil
|
||||||
|
runnables = nil
|
||||||
|
}
|
||||||
|
|
||||||
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
|
let testables: [XCScheme.TestableReference] = zip(testTargets, testBuildTargetEntries).map { testTarget, testBuildEntries in
|
||||||
|
|
||||||
@ -330,7 +350,7 @@ public class SchemeGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let launchAction = XCScheme.LaunchAction(
|
let launchAction = XCScheme.LaunchAction(
|
||||||
runnable: shouldExecuteOnLaunch ? runnables.launch : nil,
|
runnable: shouldExecuteOnLaunch ? runnables?.launch : nil,
|
||||||
buildConfiguration: scheme.run?.config ?? defaultDebugConfig.name,
|
buildConfiguration: scheme.run?.config ?? defaultDebugConfig.name,
|
||||||
preActions: scheme.run?.preActions.map(getExecutionAction) ?? [],
|
preActions: scheme.run?.preActions.map(getExecutionAction) ?? [],
|
||||||
postActions: scheme.run?.postActions.map(getExecutionAction) ?? [],
|
postActions: scheme.run?.postActions.map(getExecutionAction) ?? [],
|
||||||
@ -355,7 +375,7 @@ public class SchemeGenerator {
|
|||||||
)
|
)
|
||||||
|
|
||||||
let profileAction = XCScheme.ProfileAction(
|
let profileAction = XCScheme.ProfileAction(
|
||||||
buildableProductRunnable: shouldExecuteOnLaunch ? runnables.profile : nil,
|
buildableProductRunnable: shouldExecuteOnLaunch ? runnables?.profile : nil,
|
||||||
buildConfiguration: scheme.profile?.config ?? defaultReleaseConfig.name,
|
buildConfiguration: scheme.profile?.config ?? defaultReleaseConfig.name,
|
||||||
preActions: scheme.profile?.preActions.map(getExecutionAction) ?? [],
|
preActions: scheme.profile?.preActions.map(getExecutionAction) ?? [],
|
||||||
postActions: scheme.profile?.postActions.map(getExecutionAction) ?? [],
|
postActions: scheme.profile?.postActions.map(getExecutionAction) ?? [],
|
||||||
@ -437,7 +457,6 @@ enum SchemeGenerationError: Error, CustomStringConvertible {
|
|||||||
case missingTarget(TargetReference, projectPath: String)
|
case missingTarget(TargetReference, projectPath: String)
|
||||||
case missingPackage(String)
|
case missingPackage(String)
|
||||||
case missingProject(String)
|
case missingProject(String)
|
||||||
case missingBuildTargets(String)
|
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
switch self {
|
switch self {
|
||||||
@ -445,8 +464,6 @@ enum SchemeGenerationError: Error, CustomStringConvertible {
|
|||||||
return "Unable to find target named \"\(target)\" in \"\(projectPath)\""
|
return "Unable to find target named \"\(target)\" in \"\(projectPath)\""
|
||||||
case .missingProject(let project):
|
case .missingProject(let project):
|
||||||
return "Unable to find project reference named \"\(project)\" in project.yml"
|
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):
|
case .missingPackage(let package):
|
||||||
return "Unable to find swift package named \"\(package)\" in project.yml"
|
return "Unable to find swift package named \"\(package)\" in project.yml"
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ class SourceGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let absolutePath = project.basePath + path.normalize()
|
let absolutePath = project.basePath + path.normalize()
|
||||||
|
|
||||||
// Get the local package's relative path from the project root
|
// Get the local package's relative path from the project root
|
||||||
let fileReferencePath = try? absolutePath.relativePath(from: projectDirectory ?? project.basePath).string
|
let fileReferencePath = try? absolutePath.relativePath(from: projectDirectory ?? project.basePath).string
|
||||||
|
|
||||||
@ -72,8 +72,12 @@ class SourceGenerator {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
let parentGroups = parentGroup.components(separatedBy: "/")
|
if parentGroup == "" {
|
||||||
createParentGroups(parentGroups, for: fileReference)
|
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.
|
/// Collects an array complete of all `SourceFile` objects that make up the target based on the provided `TargetSource` definitions.
|
||||||
|
12
Tests/Fixtures/test_only.yml
Normal file
12
Tests/Fixtures/test_only.yml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
name: TestOnly
|
||||||
|
targets:
|
||||||
|
Target:
|
||||||
|
type: application
|
||||||
|
platform: iOS
|
||||||
|
sources:
|
||||||
|
- Target
|
||||||
|
schemes:
|
||||||
|
TestOnly:
|
||||||
|
test:
|
||||||
|
targets:
|
||||||
|
- Target
|
@ -203,6 +203,32 @@ class ProjectSpecTests: XCTestCase {
|
|||||||
try expectValidationError(project, .unexpectedTargetPlatformForSupportedDestinations(target: "target1", platform: .watchOS))
|
try expectValidationError(project, .unexpectedTargetPlatformForSupportedDestinations(target: "target1", platform: .watchOS))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$0.it("watchOS in multiplatform app's supported destinations") {
|
||||||
|
var project = baseProject
|
||||||
|
project.targets = [
|
||||||
|
Target(
|
||||||
|
name: "target1",
|
||||||
|
type: .application,
|
||||||
|
platform: .auto,
|
||||||
|
supportedDestinations: [.watchOS]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
try expectValidationError(project, .containsWatchOSDestinationForMultiplatformApp(target: "target1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("watchOS in non-app's supported destinations") {
|
||||||
|
var project = baseProject
|
||||||
|
project.targets = [
|
||||||
|
Target(
|
||||||
|
name: "target1",
|
||||||
|
type: .framework,
|
||||||
|
platform: .auto,
|
||||||
|
supportedDestinations: [.watchOS]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
try expectNoValidationError(project, .containsWatchOSDestinationForMultiplatformApp(target: "target1"))
|
||||||
|
}
|
||||||
|
|
||||||
$0.it("multiple definitions of mac platform in supported destinations") {
|
$0.it("multiple definitions of mac platform in supported destinations") {
|
||||||
var project = baseProject
|
var project = baseProject
|
||||||
project.targets = [
|
project.targets = [
|
||||||
|
@ -75,6 +75,15 @@ class SpecLoadingTests: XCTestCase {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$0.it("accepts schemes that have no build phase") {
|
||||||
|
let path = fixturePath + "test_only.yml"
|
||||||
|
let project = try loadSpec(path: path)
|
||||||
|
try expect(project.name) == "TestOnly"
|
||||||
|
try expect(project.schemes.count) == 1
|
||||||
|
try expect(project.schemes.first?.build).to.beNil()
|
||||||
|
try expect(project.schemes.first?.test?.targets.first?.name) == "Target"
|
||||||
|
}
|
||||||
|
|
||||||
$0.it("expands directories") {
|
$0.it("expands directories") {
|
||||||
let path = fixturePath + "paths_test.yml"
|
let path = fixturePath + "paths_test.yml"
|
||||||
let project = try loadSpec(path: path)
|
let project = try loadSpec(path: path)
|
||||||
|
@ -259,6 +259,96 @@ class PBXProjGeneratorTests: XCTestCase {
|
|||||||
.map { $0.nameOrPath }
|
.map { $0.nameOrPath }
|
||||||
try expect(screenGroups) == ["mainScreen1.swift", "mainScreen2.swift", "View", "Presenter", "Interactor", "Entities", "Assembly"]
|
try expect(screenGroups) == ["mainScreen1.swift", "mainScreen2.swift", "View", "Presenter", "Interactor", "Entities", "Assembly"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$0.it("sorts SPM packages") {
|
||||||
|
var options = SpecOptions()
|
||||||
|
options.groupSortPosition = .top
|
||||||
|
options.groupOrdering = [
|
||||||
|
GroupOrdering(
|
||||||
|
order: [
|
||||||
|
"Sources",
|
||||||
|
"Resources",
|
||||||
|
"Tests",
|
||||||
|
"Packages",
|
||||||
|
"Support files",
|
||||||
|
"Configurations",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
GroupOrdering(
|
||||||
|
pattern: "Packages",
|
||||||
|
order: [
|
||||||
|
"FeatureA",
|
||||||
|
"FeatureB",
|
||||||
|
"Common",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
let directories = """
|
||||||
|
Configurations:
|
||||||
|
- file.swift
|
||||||
|
Resources:
|
||||||
|
- file.swift
|
||||||
|
Sources:
|
||||||
|
- MainScreen:
|
||||||
|
- mainScreen1.swift
|
||||||
|
- mainScreen2.swift
|
||||||
|
- Assembly:
|
||||||
|
- file.swift
|
||||||
|
- Entities:
|
||||||
|
- file.swift
|
||||||
|
- Interactor:
|
||||||
|
- file.swift
|
||||||
|
- Presenter:
|
||||||
|
- file.swift
|
||||||
|
- View:
|
||||||
|
- file.swift
|
||||||
|
Support files:
|
||||||
|
- file.swift
|
||||||
|
Packages:
|
||||||
|
- Common:
|
||||||
|
- Package.swift
|
||||||
|
- FeatureA:
|
||||||
|
- Package.swift
|
||||||
|
- FeatureB:
|
||||||
|
- Package.swift
|
||||||
|
Tests:
|
||||||
|
- file.swift
|
||||||
|
UITests:
|
||||||
|
- file.swift
|
||||||
|
"""
|
||||||
|
try createDirectories(directories)
|
||||||
|
|
||||||
|
let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Configurations", "Resources", "Sources", "Support files", "Tests", "UITests"])
|
||||||
|
let project = Project(
|
||||||
|
basePath: directoryPath,
|
||||||
|
name: "Test",
|
||||||
|
targets: [target],
|
||||||
|
packages: [
|
||||||
|
"Common": .local(path: "Packages/Common", group: nil),
|
||||||
|
"FeatureA": .local(path: "Packages/FeatureA", group: nil),
|
||||||
|
"FeatureB": .local(path: "Packages/FeatureB", group: nil),
|
||||||
|
],
|
||||||
|
options: options
|
||||||
|
)
|
||||||
|
let projGenerator = PBXProjGenerator(project: project)
|
||||||
|
|
||||||
|
let pbxProj = try project.generatePbxProj()
|
||||||
|
let group = try pbxProj.getMainGroup()
|
||||||
|
|
||||||
|
projGenerator.setupGroupOrdering(group: group)
|
||||||
|
|
||||||
|
let mainGroups = group.children.map { $0.nameOrPath }
|
||||||
|
try expect(mainGroups) == ["Sources", "Resources", "Tests", "Packages", "Support files", "Configurations", "UITests", "Products"]
|
||||||
|
|
||||||
|
let packages = group.children
|
||||||
|
.first { $0.nameOrPath == "Packages" }
|
||||||
|
.flatMap { $0 as? PBXGroup }?
|
||||||
|
.children
|
||||||
|
.map(\.nameOrPath)
|
||||||
|
|
||||||
|
try expect(packages) == ["FeatureA", "FeatureB", "Common"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,8 +485,8 @@ class ProjectGeneratorTests: XCTestCase {
|
|||||||
try expect(targetConfig1.buildSettings["CODE_SIGN_IDENTITY"] as? String) == "iPhone Developer"
|
try expect(targetConfig1.buildSettings["CODE_SIGN_IDENTITY"] as? String) == "iPhone Developer"
|
||||||
}
|
}
|
||||||
|
|
||||||
$0.it("supportedDestinations merges settings - iOS, watchOS") {
|
$0.it("supportedDestinations merges settings - iOS, watchOS (framework)") {
|
||||||
let target = Target(name: "Target", type: .application, platform: .auto, supportedDestinations: [.iOS, .watchOS])
|
let target = Target(name: "Target", type: .framework, platform: .auto, supportedDestinations: [.iOS, .watchOS])
|
||||||
let project = Project(name: "", targets: [target])
|
let project = Project(name: "", targets: [target])
|
||||||
|
|
||||||
let pbxProject = try project.generatePbxProj()
|
let pbxProject = try project.generatePbxProj()
|
||||||
@ -499,8 +499,8 @@ class ProjectGeneratorTests: XCTestCase {
|
|||||||
try expect(targetConfig1.buildSettings["SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD"] as? Bool) == true
|
try expect(targetConfig1.buildSettings["SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD"] as? Bool) == true
|
||||||
}
|
}
|
||||||
|
|
||||||
$0.it("supportedDestinations merges settings - visionOS, watchOS") {
|
$0.it("supportedDestinations merges settings - visionOS, watchOS (framework)") {
|
||||||
let target = Target(name: "Target", type: .application, platform: .auto, supportedDestinations: [.visionOS, .watchOS])
|
let target = Target(name: "Target", type: .framework, platform: .auto, supportedDestinations: [.visionOS, .watchOS])
|
||||||
let project = Project(name: "", targets: [target])
|
let project = Project(name: "", targets: [target])
|
||||||
|
|
||||||
let pbxProject = try project.generatePbxProj()
|
let pbxProject = try project.generatePbxProj()
|
||||||
@ -1642,6 +1642,74 @@ class ProjectGeneratorTests: XCTestCase {
|
|||||||
try expect(file.product?.productName) == "XcodeGen"
|
try expect(file.product?.productName) == "XcodeGen"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$0.it("generates local swift packages at the top level") {
|
||||||
|
let app = Target(
|
||||||
|
name: "MyApp",
|
||||||
|
type: .application,
|
||||||
|
platform: .iOS,
|
||||||
|
dependencies: [
|
||||||
|
Dependency(type: .package(products: []), reference: "XcodeGen"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
let project = Project(name: "test", targets: [app], packages: ["XcodeGen": .local(path: "../XcodeGen", group: "")])
|
||||||
|
|
||||||
|
let pbxProject = try project.generatePbxProj(specValidate: false)
|
||||||
|
let nativeTarget = try unwrap(pbxProject.nativeTargets.first(where: { $0.name == app.name }))
|
||||||
|
let localPackageFile = try unwrap(pbxProject.fileReferences.first(where: { $0.path == "../XcodeGen" }))
|
||||||
|
try expect(localPackageFile.lastKnownFileType) == "folder"
|
||||||
|
|
||||||
|
let mainGroup = try pbxProject.getMainGroup()
|
||||||
|
|
||||||
|
try expect(mainGroup.children.contains(localPackageFile)) == true
|
||||||
|
|
||||||
|
let frameworkPhases = nativeTarget.buildPhases.compactMap { $0 as? PBXFrameworksBuildPhase }
|
||||||
|
|
||||||
|
guard let frameworkPhase = frameworkPhases.first else {
|
||||||
|
return XCTFail("frameworkPhases should have more than one")
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let file = frameworkPhase.files?.first else {
|
||||||
|
return XCTFail("frameworkPhase should have file")
|
||||||
|
}
|
||||||
|
|
||||||
|
try expect(file.product?.productName) == "XcodeGen"
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("generates local swift package group at the top level") {
|
||||||
|
let app = Target(
|
||||||
|
name: "MyApp",
|
||||||
|
type: .application,
|
||||||
|
platform: .iOS,
|
||||||
|
dependencies: [
|
||||||
|
Dependency(type: .package(products: []), reference: "XcodeGen"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
let project = Project(name: "test", targets: [app], packages: ["XcodeGen": .local(path: "../XcodeGen", group: nil)], options: .init(localPackagesGroup: ""))
|
||||||
|
|
||||||
|
let pbxProject = try project.generatePbxProj(specValidate: false)
|
||||||
|
let nativeTarget = try unwrap(pbxProject.nativeTargets.first(where: { $0.name == app.name }))
|
||||||
|
let localPackageFile = try unwrap(pbxProject.fileReferences.first(where: { $0.path == "../XcodeGen" }))
|
||||||
|
try expect(localPackageFile.lastKnownFileType) == "folder"
|
||||||
|
|
||||||
|
let mainGroup = try pbxProject.getMainGroup()
|
||||||
|
|
||||||
|
try expect(mainGroup.children.contains(localPackageFile)) == true
|
||||||
|
|
||||||
|
let frameworkPhases = nativeTarget.buildPhases.compactMap { $0 as? PBXFrameworksBuildPhase }
|
||||||
|
|
||||||
|
guard let frameworkPhase = frameworkPhases.first else {
|
||||||
|
return XCTFail("frameworkPhases should have more than one")
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let file = frameworkPhase.files?.first else {
|
||||||
|
return XCTFail("frameworkPhase should have file")
|
||||||
|
}
|
||||||
|
|
||||||
|
try expect(file.product?.productName) == "XcodeGen"
|
||||||
|
}
|
||||||
|
|
||||||
$0.it("generates info.plist") {
|
$0.it("generates info.plist") {
|
||||||
let plist = Plist(path: "Info.plist", attributes: ["UISupportedInterfaceOrientations": ["UIInterfaceOrientationPortrait", "UIInterfaceOrientationLandscapeLeft"]])
|
let plist = Plist(path: "Info.plist", attributes: ["UISupportedInterfaceOrientations": ["UIInterfaceOrientationPortrait", "UIInterfaceOrientationLandscapeLeft"]])
|
||||||
let tempPath = Path.temporary + "info"
|
let tempPath = Path.temporary + "info"
|
||||||
|
Loading…
Reference in New Issue
Block a user