mirror of
https://github.com/yonaskolb/XcodeGen.git
synced 2024-10-26 14:01:24 +03:00
Compare commits
18 Commits
af90cc0dcb
...
481da390bf
Author | SHA1 | Date | |
---|---|---|---|
|
481da390bf | ||
|
f51719ce29 | ||
|
1b0720d139 | ||
|
576739bcb5 | ||
|
aa79a3ed0b | ||
|
d99e448647 | ||
|
274ce7342c | ||
|
17acb4dc61 | ||
|
447cc7f9ac | ||
|
9816466703 | ||
|
bc08f53505 | ||
|
0301741002 | ||
|
ecb9b560ae | ||
|
632ca2d419 | ||
|
1645d419ce | ||
|
6694943ad8 | ||
|
9df3e62734 | ||
|
e7ee5f3f63 |
31
CHANGELOG.md
31
CHANGELOG.md
@ -2,6 +2,37 @@
|
||||
|
||||
## 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
|
||||
|
||||
### 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.
|
||||
|
||||
- [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)
|
||||
- [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)
|
||||
|
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.
|
||||
|
||||
## 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 use `--use-cache` so that the project is not needlessly generated.
|
||||
It's recommended to set up some [git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) to automate the process:
|
||||
- run `xcodegen generate --use-cache` on the following hooks. This will make sure the project is up to date when checking out, merging and rebasing
|
||||
- `post-checkout`
|
||||
- `post-rewrite`
|
||||
- `post-merge`
|
||||
- run `xcodegen cache` on `pre-commit`. This will make sure that when switching branches the cache will be updated in case you made local changes, or are ammending a commit that added a new file.
|
||||
|
||||
## Can I use CocoaPods
|
||||
Yes, you will just need to run `pod install` after the project is generated to integrate Cocoapods changes.
|
||||
|
@ -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`.
|
||||
- [ ] **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`
|
||||
- [ ] **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.
|
||||
@ -483,6 +483,7 @@ This will provide a mix of default build settings for the chosen platform destin
|
||||
- `macOS`
|
||||
- `macCatalyst`
|
||||
- `visionOS`
|
||||
- `watchOS`
|
||||
|
||||
```yaml
|
||||
targets:
|
||||
@ -499,7 +500,7 @@ targets:
|
||||
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
|
||||
|
||||
@ -1140,12 +1141,16 @@ 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
|
||||
coverageTargets:
|
||||
- MyTarget1
|
||||
@ -1240,7 +1245,7 @@ Swift packages are defined at a project level, and then linked to individual tar
|
||||
### 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.
|
||||
- [ ] **group** : **String**- Optional path that specifies the location where the package will live in your xcode project. Use `""` to specify the project root.
|
||||
|
||||
```yml
|
||||
packages:
|
||||
|
8
Makefile
8
Makefile
@ -1,14 +1,12 @@
|
||||
TOOL_NAME = XcodeGen
|
||||
export EXECUTABLE_NAME = xcodegen
|
||||
VERSION = 2.39.1
|
||||
VERSION = 2.41.0
|
||||
|
||||
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)
|
||||
@ -42,10 +40,6 @@ release:
|
||||
publish: archive brew
|
||||
echo "published $(VERSION)"
|
||||
|
||||
brew:
|
||||
brew update
|
||||
brew bump-formula-pr --url=$(RELEASE_TAR) XcodeGen
|
||||
|
||||
archive: build
|
||||
./scripts/archive.sh "$(EXECUTABLE_PATH)"
|
||||
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:
|
||||
|
||||
```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`
|
||||
@ -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.
|
||||
- **--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
|
||||
<details>
|
||||
|
@ -8,4 +8,3 @@
|
||||
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` and `xcodegen.artifactbundle.zip` to the github release
|
||||
1. Run `make brew` which will open a PR on homebrew core
|
@ -88,10 +88,12 @@ public struct Scheme: 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
|
||||
}
|
||||
}
|
||||
|
||||
@ -400,6 +402,7 @@ 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")
|
||||
}
|
||||
}
|
||||
|
||||
@ -409,6 +412,7 @@ extension Scheme.ExecutionAction: JSONEncodable {
|
||||
"script": script,
|
||||
"name": name,
|
||||
"settingsTarget": settingsTarget,
|
||||
"shell": shell
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -10,9 +10,11 @@ public struct SpecOptions: Equatable {
|
||||
public static let generateEmptyDirectoriesDefault = false
|
||||
public static let findCarthageFrameworksDefault = false
|
||||
public static let useBaseInternationalizationDefault = true
|
||||
public static let carthageXCFrameworksDefault = false
|
||||
public static let schemePathPrefixDefault = "../../"
|
||||
|
||||
public var minimumXcodeGenVersion: Version?
|
||||
public var carthageXCFrameworks: Bool
|
||||
public var carthageBuildPath: String?
|
||||
public var carthageExecutablePath: String?
|
||||
public var createIntermediateGroups: Bool
|
||||
@ -77,6 +79,7 @@ public struct SpecOptions: Equatable {
|
||||
|
||||
public init(
|
||||
minimumXcodeGenVersion: Version? = nil,
|
||||
carthageXCFrameworks: Bool = carthageXCFrameworksDefault,
|
||||
carthageBuildPath: String? = nil,
|
||||
carthageExecutablePath: String? = nil,
|
||||
createIntermediateGroups: Bool = createIntermediateGroupsDefault,
|
||||
@ -103,6 +106,7 @@ public struct SpecOptions: Equatable {
|
||||
schemePathPrefix: String = schemePathPrefixDefault
|
||||
) {
|
||||
self.minimumXcodeGenVersion = minimumXcodeGenVersion
|
||||
self.carthageXCFrameworks = carthageXCFrameworks
|
||||
self.carthageBuildPath = carthageBuildPath
|
||||
self.carthageExecutablePath = carthageExecutablePath
|
||||
self.createIntermediateGroups = createIntermediateGroups
|
||||
@ -137,6 +141,7 @@ extension SpecOptions: JSONObjectConvertible {
|
||||
minimumXcodeGenVersion = try Version.parse(string)
|
||||
}
|
||||
|
||||
carthageXCFrameworks = jsonDictionary.json(atKeyPath: "carthageXCFrameworks") ?? SpecOptions.carthageXCFrameworksDefault
|
||||
carthageBuildPath = jsonDictionary.json(atKeyPath: "carthageBuildPath")
|
||||
carthageExecutablePath = jsonDictionary.json(atKeyPath: "carthageExecutablePath")
|
||||
bundleIdPrefix = jsonDictionary.json(atKeyPath: "bundleIdPrefix")
|
||||
@ -206,6 +211,9 @@ extension SpecOptions: JSONEncodable {
|
||||
if useBaseInternationalization != SpecOptions.useBaseInternationalizationDefault {
|
||||
dict["useBaseInternationalization"] = useBaseInternationalization
|
||||
}
|
||||
if carthageXCFrameworks != SpecOptions.carthageXCFrameworksDefault {
|
||||
dict["carthageXCFrameworks"] = carthageXCFrameworks
|
||||
}
|
||||
if schemePathPrefix != SpecOptions.schemePathPrefixDefault {
|
||||
dict["schemePathPrefix"] = schemePathPrefix
|
||||
}
|
||||
|
@ -185,6 +185,12 @@ extension Project {
|
||||
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 {
|
||||
|
||||
|
@ -19,6 +19,7 @@ public struct SpecValidationError: Error, CustomStringConvertible {
|
||||
case invalidTargetSchemeTest(target: String, testTarget: String)
|
||||
case invalidTargetPlatformForSupportedDestinations(target: String)
|
||||
case unexpectedTargetPlatformForSupportedDestinations(target: String, platform: Platform)
|
||||
case containsWatchOSDestinationForMultiplatformApp(target: String)
|
||||
case multipleMacPlatformsInSupportedDestinations(target: String)
|
||||
case missingTargetPlatformInSupportedDestinations(target: String, platform: Platform)
|
||||
case invalidSchemeTarget(scheme: String, target: String, action: String)
|
||||
@ -66,6 +67,8 @@ public struct SpecValidationError: Error, CustomStringConvertible {
|
||||
return "Target \(target.quoted) has multiple definitions of mac platforms in supported destinations"
|
||||
case let .missingTargetPlatformInSupportedDestinations(target, platform):
|
||||
return "Target \(target.quoted) has platform \(platform.rawValue.quoted) that is missing in supported destinations"
|
||||
case let .containsWatchOSDestinationForMultiplatformApp(target):
|
||||
return "Multiplatform app \(target.quoted) cannot contain watchOS in \"supportedDestinations\". Create a separate target using \"platform\" for watchOS apps"
|
||||
case let .invalidConfigFile(configFile, config):
|
||||
return "Invalid config file \(configFile.quoted) for config \(config.quoted)"
|
||||
case let .invalidSchemeTarget(scheme, target, action):
|
||||
|
@ -3,6 +3,6 @@ import ProjectSpec
|
||||
import XcodeGenCLI
|
||||
import Version
|
||||
|
||||
let version = Version("2.39.1")
|
||||
let version = Version("2.41.0")
|
||||
let cli = XcodeGenCLI(version: version)
|
||||
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.write(output)
|
||||
} else {
|
||||
stdout.print(output)
|
||||
success(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,6 @@ import Version
|
||||
|
||||
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")
|
||||
var useCache: Bool
|
||||
|
||||
@ -46,7 +43,7 @@ class GenerateCommand: ProjectCommand {
|
||||
Path("~/.xcodegen/cache/\(projectSpecPath.absolute().string.md5)").absolute()
|
||||
var cacheFile: CacheFile?
|
||||
|
||||
// read cache
|
||||
// generate cache
|
||||
if useCache || self.cacheFilePath != nil {
|
||||
do {
|
||||
cacheFile = try specLoader.generateCacheFile()
|
||||
@ -138,22 +135,4 @@ class GenerateCommand: ProjectCommand {
|
||||
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 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?
|
||||
|
||||
@ -58,4 +61,22 @@ class ProjectCommand: Command {
|
||||
}
|
||||
|
||||
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",
|
||||
commands: [
|
||||
generateCommand,
|
||||
CacheCommand(version: version),
|
||||
DumpCommand(version: version),
|
||||
]
|
||||
)
|
||||
|
@ -39,14 +39,43 @@ public class CarthageDependencyResolver {
|
||||
versionLoader = CarthageVersionLoader(buildPath: project.basePath + CarthageDependencyResolver.getBuildPath(project))
|
||||
}
|
||||
|
||||
func frameworkPath(for name: String, platform: Platform, linkType: Dependency.CarthageLinkType) -> Path {
|
||||
var file = Path(name)
|
||||
var xcFramework = project.options.carthageXCFrameworks
|
||||
switch file.extension {
|
||||
case "xcframework":
|
||||
xcFramework = true
|
||||
case "framework":
|
||||
xcFramework = false
|
||||
case .none:
|
||||
let fileExtension: String
|
||||
if xcFramework {
|
||||
fileExtension = "xcframework"
|
||||
} else {
|
||||
fileExtension = "framework"
|
||||
}
|
||||
file = Path(file.string + ".\(fileExtension)")
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
let parentPath = Path(buildPath(for: platform, linkType: linkType, xcFramework: xcFramework))
|
||||
return parentPath + file
|
||||
}
|
||||
|
||||
/// Carthage's build path for the given platform
|
||||
func buildPath(for platform: Platform, linkType: Dependency.CarthageLinkType) -> String {
|
||||
func buildPath(for platform: Platform, linkType: Dependency.CarthageLinkType, xcFramework: Bool) -> String {
|
||||
var path = buildPath
|
||||
if !xcFramework {
|
||||
path += "/\(platform.carthageName)"
|
||||
}
|
||||
switch linkType {
|
||||
case .static:
|
||||
return "\(buildPath)/\(platform.carthageName)/Static"
|
||||
path += "/Static"
|
||||
case .dynamic:
|
||||
return "\(buildPath)/\(platform.carthageName)"
|
||||
break
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
/// Fetches all carthage dependencies for a given target
|
||||
|
@ -57,8 +57,13 @@ class CarthageVersionLoader {
|
||||
struct CarthageVersionFile: Decodable {
|
||||
|
||||
private struct Reference: Decodable, Equatable {
|
||||
public let name: String
|
||||
public let hash: String
|
||||
let name: String
|
||||
let hash: String
|
||||
let container: String?
|
||||
|
||||
var frameworkFilename: String {
|
||||
container ?? "\(name).framework"
|
||||
}
|
||||
}
|
||||
|
||||
private let data: [Platform: [String]]
|
||||
@ -67,7 +72,7 @@ struct CarthageVersionFile: Decodable {
|
||||
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()
|
||||
let frameworks = Set(references.map { $0.frameworkFilename }).sorted()
|
||||
data[platform] = frameworks
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ public class PBXProjGenerator {
|
||||
var packageReferences: [String: XCRemoteSwiftPackageReference] = [:]
|
||||
|
||||
var carthageFrameworksByPlatform: [String: Set<PBXFileElement>] = [:]
|
||||
var carthageXCFrameworks: Set<PBXFileElement> = []
|
||||
var frameworkFiles: [PBXFileElement] = []
|
||||
var bundleFiles: [PBXFileElement] = []
|
||||
|
||||
@ -226,8 +227,8 @@ public class PBXProjGenerator {
|
||||
try project.targets.forEach(generateTarget)
|
||||
try project.aggregateTargets.forEach(generateAggregateTarget)
|
||||
|
||||
if !carthageFrameworksByPlatform.isEmpty {
|
||||
var platforms: [PBXGroup] = []
|
||||
if !carthageFrameworksByPlatform.isEmpty || !carthageXCFrameworks.isEmpty {
|
||||
var children: [PBXFileElement] = []
|
||||
for (platform, files) in carthageFrameworksByPlatform {
|
||||
let platformGroup: PBXGroup = addObject(
|
||||
PBXGroup(
|
||||
@ -236,11 +237,14 @@ public class PBXProjGenerator {
|
||||
path: platform
|
||||
)
|
||||
)
|
||||
platforms.append(platformGroup)
|
||||
children.append(platformGroup)
|
||||
}
|
||||
if !carthageXCFrameworks.isEmpty {
|
||||
children += Array(carthageXCFrameworks)
|
||||
}
|
||||
let carthageGroup = addObject(
|
||||
PBXGroup(
|
||||
children: platforms,
|
||||
children: children,
|
||||
sourceTree: .group,
|
||||
name: "Carthage",
|
||||
path: carthageResolver.buildPath
|
||||
@ -631,8 +635,8 @@ public class PBXProjGenerator {
|
||||
}
|
||||
|
||||
if let order = groupOrdering?.order {
|
||||
let files = group.children.filter { $0 is PBXFileReference }
|
||||
var groups = group.children.filter { $0 is PBXGroup }
|
||||
let files = group.children.filter { !$0.isGroupOrFolder }
|
||||
var groups = group.children.filter { $0.isGroupOrFolder }
|
||||
|
||||
var filteredGroups = [PBXFileElement]()
|
||||
|
||||
@ -703,7 +707,7 @@ public class PBXProjGenerator {
|
||||
|
||||
let targetSupportsDirectEmbed = !(target.platform.requiresSimulatorStripping &&
|
||||
(target.type.isApp || target.type == .watch2Extension))
|
||||
let directlyEmbedCarthage = target.directlyEmbedCarthageDependencies ?? targetSupportsDirectEmbed
|
||||
let directlyEmbedCarthage = target.directlyEmbedCarthageDependencies ?? project.options.carthageXCFrameworks || targetSupportsDirectEmbed
|
||||
|
||||
func getEmbedSettings(dependency: Dependency, codeSign: Bool) -> [String: Any] {
|
||||
var embedAttributes: [String] = []
|
||||
@ -920,14 +924,14 @@ public class PBXProjGenerator {
|
||||
? 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)
|
||||
let frameworkPath = carthageResolver.frameworkPath(for: dependency.reference, platform: target.platform, linkType: linkType)
|
||||
let fileReference = self.sourceGenerator.getFileReference(path: frameworkPath, inPath: frameworkPath.parent())
|
||||
|
||||
if frameworkPath.extension == "xcframework" {
|
||||
self.carthageXCFrameworks.insert(fileReference)
|
||||
} else {
|
||||
self.carthageFrameworksByPlatform[target.platform.carthageName, default: []].insert(fileReference)
|
||||
}
|
||||
|
||||
let isStaticLibrary = target.type == .staticLibrary
|
||||
let isCarthageStaticLink = dependency.carthageLinkType == .static
|
||||
@ -1025,12 +1029,8 @@ public class PBXProjGenerator {
|
||||
let isFromTopLevelTarget = carthageDependency.isFromTopLevelTarget
|
||||
let embed = dependency.embed ?? target.shouldEmbedCarthageDependencies
|
||||
|
||||
let platformPath = Path(carthageResolver.buildPath(for: target.platform, linkType: dependency.carthageLinkType ?? .default))
|
||||
var frameworkPath = platformPath + dependency.reference
|
||||
if frameworkPath.extension == nil {
|
||||
frameworkPath = Path(frameworkPath.string + ".framework")
|
||||
}
|
||||
let fileReference = sourceGenerator.getFileReference(path: frameworkPath, inPath: platformPath)
|
||||
let frameworkPath = carthageResolver.frameworkPath(for: dependency.reference, platform: target.platform, linkType: dependency.carthageLinkType ?? .default)
|
||||
let fileReference = self.sourceGenerator.getFileReference(path: frameworkPath, inPath: frameworkPath.parent())
|
||||
|
||||
if dependency.carthageLinkType == .static {
|
||||
guard isFromTopLevelTarget else { continue } // ignore transitive dependencies if static
|
||||
@ -1182,7 +1182,7 @@ public class PBXProjGenerator {
|
||||
if !carthageFrameworksToEmbed.isEmpty {
|
||||
|
||||
let inputPaths = carthageFrameworksToEmbed
|
||||
.map { "$(SRCROOT)/\(carthageResolver.buildPath(for: target.platform, linkType: .dynamic))/\($0)\($0.contains(".") ? "" : ".framework")" }
|
||||
.map { "$(SRCROOT)/\(carthageResolver.frameworkPath(for: $0, platform: target.platform, linkType: .dynamic))" }
|
||||
let outputPaths = carthageFrameworksToEmbed
|
||||
.map { "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/\($0)\($0.contains(".") ? "" : ".framework")" }
|
||||
let carthageExecutable = carthageResolver.executable
|
||||
@ -1385,15 +1385,21 @@ public class PBXProjGenerator {
|
||||
let configFrameworkBuildPaths: [String]
|
||||
if !carthageDependencies.isEmpty {
|
||||
var carthagePlatformBuildPaths: Set<String> = []
|
||||
if carthageDependencies.contains(where: { $0.dependency.carthageLinkType == .static }) {
|
||||
let carthagePlatformBuildPath = "$(PROJECT_DIR)/" + carthageResolver.buildPath(for: target.platform, linkType: .static)
|
||||
carthagePlatformBuildPaths.insert(carthagePlatformBuildPath)
|
||||
func getCarthageBuildPaths(linkType: Dependency.CarthageLinkType) -> Set<String> {
|
||||
var paths: Set<String> = []
|
||||
if carthageDependencies.contains(where: { $0.dependency.carthageLinkType == linkType }) {
|
||||
let path = "$(PROJECT_DIR)/\(carthageResolver.buildPath(for: target.platform, linkType: linkType, xcFramework: false))"
|
||||
paths.insert(path)
|
||||
if project.options.carthageXCFrameworks {
|
||||
let path = "$(PROJECT_DIR)/\(carthageResolver.buildPath(for: target.platform, linkType: linkType, xcFramework: true))/**"
|
||||
paths.insert(path)
|
||||
}
|
||||
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()
|
||||
return paths
|
||||
}
|
||||
carthagePlatformBuildPaths = carthagePlatformBuildPaths.union(getCarthageBuildPaths(linkType: .static))
|
||||
carthagePlatformBuildPaths = carthagePlatformBuildPaths.union(getCarthageBuildPaths(linkType: .dynamic))
|
||||
configFrameworkBuildPaths = carthagePlatformBuildPaths.union(frameworkBuildPaths).sorted()
|
||||
} else {
|
||||
configFrameworkBuildPaths = frameworkBuildPaths.sorted()
|
||||
}
|
||||
@ -1626,6 +1632,10 @@ extension Platform {
|
||||
}
|
||||
|
||||
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 {
|
||||
if type(of: self).isa == "PBXGroup" {
|
||||
|
@ -208,7 +208,12 @@ public class SchemeGenerator {
|
||||
.first { settingsTarget == $0.buildableReference.blueprintName }?
|
||||
.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?
|
||||
|
@ -72,9 +72,13 @@ class SourceGenerator {
|
||||
)
|
||||
)
|
||||
|
||||
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.
|
||||
///
|
||||
|
@ -203,6 +203,32 @@ class ProjectSpecTests: XCTestCase {
|
||||
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") {
|
||||
var project = baseProject
|
||||
project.targets = [
|
||||
|
@ -55,7 +55,7 @@ class CarthageDependencyResolverTests: XCTestCase {
|
||||
}
|
||||
|
||||
try allPlatforms.forEach { platform in
|
||||
try expect(expectedByPlatform[platform]) == resolver.buildPath(for: platform, linkType: .dynamic)
|
||||
try expect(expectedByPlatform[platform]) == resolver.buildPath(for: platform, linkType: .dynamic, xcFramework: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -259,6 +259,96 @@ class PBXProjGeneratorTests: XCTestCase {
|
||||
.map { $0.nameOrPath }
|
||||
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"
|
||||
}
|
||||
|
||||
$0.it("supportedDestinations merges settings - iOS, watchOS") {
|
||||
let target = Target(name: "Target", type: .application, platform: .auto, supportedDestinations: [.iOS, .watchOS])
|
||||
$0.it("supportedDestinations merges settings - iOS, watchOS (framework)") {
|
||||
let target = Target(name: "Target", type: .framework, platform: .auto, supportedDestinations: [.iOS, .watchOS])
|
||||
let project = Project(name: "", targets: [target])
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
$0.it("supportedDestinations merges settings - visionOS, watchOS") {
|
||||
let target = Target(name: "Target", type: .application, platform: .auto, supportedDestinations: [.visionOS, .watchOS])
|
||||
$0.it("supportedDestinations merges settings - visionOS, watchOS (framework)") {
|
||||
let target = Target(name: "Target", type: .framework, platform: .auto, supportedDestinations: [.visionOS, .watchOS])
|
||||
let project = Project(name: "", targets: [target])
|
||||
|
||||
let pbxProject = try project.generatePbxProj()
|
||||
@ -1642,6 +1642,74 @@ class ProjectGeneratorTests: XCTestCase {
|
||||
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") {
|
||||
let plist = Plist(path: "Info.plist", attributes: ["UISupportedInterfaceOrientations": ["UIInterfaceOrientationPortrait", "UIInterfaceOrientationLandscapeLeft"]])
|
||||
let tempPath = Path.temporary + "info"
|
||||
|
Loading…
Reference in New Issue
Block a user