Files
xtool-mirror/Sources/XcodeProjects/XcodeWorkspace.swift
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

113 lines
3.3 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 var url: URL
public init(children: [Element], url: URL) {
self.children = children
self.url = url.standardizedFileURL.resolvingSymlinksInPath()
}
/// Returns a copy at a new workspace location.
public func withURL(_ url: URL) -> Self {
var copy = self
copy.url = url.standardizedFileURL.resolvingSymlinksInPath()
return copy
}
}
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)"
}
}