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

85 lines
2.7 KiB
Swift

import Foundation
private import XcodeProj
extension XcodeWorkspace {
public init(url: URL) throws {
let workspace = try XCWorkspace(pathString: url.path)
self.init(workspace.data)
}
public func dataRepresentation() throws -> Data {
try xcWorkspaceData.dataRepresentation() ?? Data()
}
public func write(to url: URL) throws {
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
try self.dataRepresentation()
.write(to: url.appendingPathComponent("contents.xcworkspacedata"))
}
}
// MARK: - Conversion
extension XcodeWorkspace {
fileprivate init(_ xcWorkspaceData: XCWorkspaceData) {
self.init(children: xcWorkspaceData.children.map { .init(from: $0) })
}
fileprivate var xcWorkspaceData: XCWorkspaceData {
XCWorkspaceData(children: children.map(\.xcWorkspaceDataElement))
}
}
extension XcodeWorkspace.Element {
fileprivate init(from xcWorkspaceDataElement: XCWorkspaceDataElement) {
switch xcWorkspaceDataElement {
case .file(let file):
self = .file(.init(file.location))
case .group(let group):
self = .group(.init(group))
case .fileSystemSynchronizedGroup(let group):
self = .fileSystemSynchronizedGroup(.init(group))
}
}
fileprivate var xcWorkspaceDataElement: XCWorkspaceDataElement {
switch self {
case .file(let location):
.file(.init(location: location.xcWorkspaceLocation))
case .group(let group):
.group(group.xcWorkspaceDataElement)
case .fileSystemSynchronizedGroup(let group):
.fileSystemSynchronizedGroup(group.xcWorkspaceDataElement)
}
}
}
extension XcodeWorkspace.Location {
fileprivate init(_ xcWorkspaceDataElementLocation: XCWorkspaceDataElementLocationType) {
self.base = .init(rawValue: xcWorkspaceDataElementLocation.schema)
self.path = xcWorkspaceDataElementLocation.path
}
fileprivate var xcWorkspaceLocation: XCWorkspaceDataElementLocationType {
.other(base.rawValue, path)
}
}
extension XcodeWorkspace.Group {
fileprivate init(_ xcWorkspaceDataGroup: XCWorkspaceDataGroup) {
self.init(
location: .init(xcWorkspaceDataGroup.location),
name: xcWorkspaceDataGroup.name,
children: xcWorkspaceDataGroup.children.map { .init(from: $0) }
)
}
fileprivate var xcWorkspaceDataElement: XCWorkspaceDataGroup {
.init(
location: self.location.xcWorkspaceLocation,
name: name,
children: children.map(\.xcWorkspaceDataElement)
)
}
}