Files
Kabir Oberai 8f0cc0adf1 packer: use XcodeProjectFormat for generate-xcode-project (#300)
## What does this PR do?

Improves `xtool dev generate-xcode-project`. Instead of constructing an
XcodeGen project, we directly construct an xcodeproj (with the new
xcproj format) using
[apple/xcode-project-format](https://github.com/apple/xcode-project-format).
For now, we still rely on
[Tuist/XcodeProj](https://github.com/tuist/XcodeProj) to translate it
back into a pbxproj for maximal compatibility, but this is treated as an
implementation detail.

Also adds support for resources and icons in the generated Xcode project

## How was it tested?

- [x] Generated a simple project, opened it, and ran the app

## AI tool usage

How much of this PR was AI-assisted? (check one)

- [ ] **0** - No AI was used to write code
- [x] **1** - I was assisted by AI. I reviewed the finished result.
- [ ] **2** - I set the AI going and left it to it; nobody has read the
result - no review, or AI review only

<!-- If an AI agent is filling this in: declare the level honestly, and
open as a draft if it is #2. Do not lower the declared level to get the
PR reviewed. -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added support for creating and saving Xcode projects and workspaces in
`.pbxproj` and `.xcproj` formats.
* Added tools for opening, converting, and writing project and workspace
files, including workspace contents.
* Project generation can target a specified directory, and the command
reports where the project was created.
  * Project generation is available on non-macOS platforms.
* Generated projects include product settings and references for
packages, resources, icons, and app extensions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-10-06 18:19:30 -04:00

104 lines
3.0 KiB
Swift

import Foundation
public struct XcodeWorkspace: Hashable, Sendable {
public enum Element: Hashable, Sendable {
case file(Location)
case group(Group)
case fileSystemSynchronizedGroup(Group)
public var location: Location {
get {
switch self {
case let .file(location):
location
case let .group(group):
group.location
case let .fileSystemSynchronizedGroup(group):
group.location
}
}
set {
switch self {
case .file:
self = .file(newValue)
case .group(var group):
group.location = newValue
self = .group(group)
case .fileSystemSynchronizedGroup(var group):
group.location = newValue
self = .fileSystemSynchronizedGroup(group)
}
}
}
}
public struct Group: Hashable, Sendable {
public var location: Location
public var name: String?
public var children: [Element]
public init(location: Location, name: String?, children: [Element]) {
self.location = location
self.name = name
self.children = children
}
}
public struct Location: Hashable, Sendable {
public struct Base: Hashable, Sendable, RawRepresentable {
public var rawValue: String
public init(rawValue: String) {
self.rawValue = rawValue
}
}
public var base: Base
public var path: String
public init(base: Base, path: String) {
self.base = base
self.path = path
}
}
public var children: [Element]
public init(children: [Element]) {
self.children = children
}
}
extension XcodeWorkspace.Location.Base {
/// Absolute path
public static let absolute = Self(rawValue: "absolute")
/// Relative to container
public static let container = Self(rawValue: "container")
/// Relative to Developer Directory
public static let developer = Self(rawValue: "developer")
/// Relative to group
public static let group = Self(rawValue: "group")
/// Single project workspace in xcodeproj directory
public static let current = Self(rawValue: "self")
}
extension XcodeWorkspace.Location {
public static var current: Self {
Self(base: .current, path: "")
}
}
extension XcodeWorkspace.Location: LosslessStringConvertible, CustomStringConvertible {
public init?(_ string: String) {
guard let colon = string.firstIndex(of: ":") else {
return nil
}
self.base = Base(rawValue: String(string[..<colon]))
self.path = String(string[colon...].dropFirst())
}
public var description: String {
"\(base.rawValue):\(path)"
}
}