Files
Kabir Oberai f1b455b4c5 xcode: track paths on XcodeProject (#388)
## What does this PR do?

An XcodeProject/XcodeWorkspace now knows its own location on disk, which
is important when we start using these types to build PIF.

## How was it tested?

- [x] Used in future PRs to build various Xcode projects

## 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
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Improvements**
* Projects and workspaces now retain their associated locations and save
directly to those locations.
* Generated project files preserve the configured project directory
path, helping keep file references consistent when projects are written
or reopened.
* Projects and workspaces can be associated with a new location while
retaining their existing contents.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-10-28 19:22:27 -04:00

86 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, url: url)
}
public func dataRepresentation() throws -> Data {
try xcWorkspaceData.dataRepresentation() ?? Data()
}
/// Writes the workspace to its current URL.
public func write() 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, url: URL) {
self.init(children: xcWorkspaceData.children.map { .init(from: $0) }, url: url)
}
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)
)
}
}