Files
XcodeGen/Sources/ProjectSpec/TargetSource.swift
T
Max Seelemann 167d11998a Various synced folder enhancements (#1596)
* Add explicitFolders support to syncedFolder

Adds an `explicitFolders` property to `TargetSource` that is expanded from Glob patterns and passed through to `PBXFileSystemSynchronizedRootGroup`.

* Fix syncedFolder sources ignoring createIntermediateGroups

When createIntermediateGroups was enabled and a syncedFolder source had a
multi-component path (e.g. SyncedParent/SyncedChild), two things went wrong:

1. The synced folder was unconditionally added to rootGroups, causing it
   to appear both at the project root and inside the correct intermediate
   parent group.

2. The synced folder kept its full project-relative path instead of being
   made relative to its parent group, so Xcode concatenated them into a
   wrong path (e.g. SyncedParent/SyncedParent/SyncedChild).

* Enhance PBXFileElement to recognize synced folders as groups that can be sorted

* Fix membership exceptions for nested synced folder with intermediate groups

* Update Changelog
2026-03-05 13:22:29 +11:00

169 lines
5.9 KiB
Swift

import Foundation
import JSONUtilities
import PathKit
public struct TargetSource: Equatable {
public static let optionalDefault = false
public var path: String {
didSet {
path = (path as NSString).standardizingPath
}
}
public var name: String?
public var group: String?
public var compilerFlags: [String]
public var excludes: [String]
public var includes: [String]
public var explicitFolders: [String]
public var type: SourceType?
public var optional: Bool
public var buildPhase: BuildPhaseSpec?
public var headerVisibility: HeaderVisibility?
public var createIntermediateGroups: Bool?
public var attributes: [String]
public var resourceTags: [String]
public var inferDestinationFiltersByPath: Bool?
public var destinationFilters: [SupportedDestination]?
public enum HeaderVisibility: String {
case `public`
case `private`
case project
public var settingName: String {
switch self {
case .public: return "Public"
case .private: return "Private"
case .project: return "Project"
}
}
}
public init(
path: String,
name: String? = nil,
group: String? = nil,
compilerFlags: [String] = [],
excludes: [String] = [],
includes: [String] = [],
explicitFolders: [String] = [],
type: SourceType? = nil,
optional: Bool = optionalDefault,
buildPhase: BuildPhaseSpec? = nil,
headerVisibility: HeaderVisibility? = nil,
createIntermediateGroups: Bool? = nil,
attributes: [String] = [],
resourceTags: [String] = [],
inferDestinationFiltersByPath: Bool? = nil,
destinationFilters: [SupportedDestination]? = nil
) {
self.path = (path as NSString).standardizingPath
self.name = name
self.group = group
self.compilerFlags = compilerFlags
self.excludes = excludes
self.includes = includes
self.explicitFolders = explicitFolders
self.type = type
self.optional = optional
self.buildPhase = buildPhase
self.headerVisibility = headerVisibility
self.createIntermediateGroups = createIntermediateGroups
self.attributes = attributes
self.resourceTags = resourceTags
self.inferDestinationFiltersByPath = inferDestinationFiltersByPath
self.destinationFilters = destinationFilters
}
}
extension TargetSource: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
self = TargetSource(path: value)
}
public init(extendedGraphemeClusterLiteral value: String) {
self = TargetSource(path: value)
}
public init(unicodeScalarLiteral value: String) {
self = TargetSource(path: value)
}
}
extension TargetSource: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
path = try jsonDictionary.json(atKeyPath: "path")
path = (path as NSString).standardizingPath // Done in two steps as the compiler can't figure out the types otherwise
name = jsonDictionary.json(atKeyPath: "name")
group = jsonDictionary.json(atKeyPath: "group")
let maybeCompilerFlagsString: String? = jsonDictionary.json(atKeyPath: "compilerFlags")
let maybeCompilerFlagsArray: [String]? = jsonDictionary.json(atKeyPath: "compilerFlags")
compilerFlags = maybeCompilerFlagsArray ??
maybeCompilerFlagsString.map { $0.split(separator: " ").map { String($0) } } ?? []
headerVisibility = jsonDictionary.json(atKeyPath: "headerVisibility")
excludes = jsonDictionary.json(atKeyPath: "excludes") ?? []
includes = jsonDictionary.json(atKeyPath: "includes") ?? []
explicitFolders = jsonDictionary.json(atKeyPath: "explicitFolders") ?? []
type = jsonDictionary.json(atKeyPath: "type")
optional = jsonDictionary.json(atKeyPath: "optional") ?? TargetSource.optionalDefault
if let string: String = jsonDictionary.json(atKeyPath: "buildPhase") {
buildPhase = try BuildPhaseSpec(string: string)
} else if let dict: JSONDictionary = jsonDictionary.json(atKeyPath: "buildPhase") {
buildPhase = try BuildPhaseSpec(jsonDictionary: dict)
}
createIntermediateGroups = jsonDictionary.json(atKeyPath: "createIntermediateGroups")
attributes = jsonDictionary.json(atKeyPath: "attributes") ?? []
resourceTags = jsonDictionary.json(atKeyPath: "resourceTags") ?? []
inferDestinationFiltersByPath = jsonDictionary.json(atKeyPath: "inferDestinationFiltersByPath")
if let destinationFilters: [SupportedDestination] = jsonDictionary.json(atKeyPath: "destinationFilters") {
self.destinationFilters = destinationFilters
}
}
}
extension TargetSource: JSONEncodable {
public func toJSONValue() -> Any {
var dict: [String: Any?] = [
"compilerFlags": compilerFlags,
"excludes": excludes,
"includes": includes,
"explicitFolders": explicitFolders,
"name": name,
"group": group,
"headerVisibility": headerVisibility?.rawValue,
"type": type?.rawValue,
"buildPhase": buildPhase?.toJSONValue(),
"createIntermediateGroups": createIntermediateGroups,
"resourceTags": resourceTags,
"path": path,
"inferDestinationFiltersByPath": inferDestinationFiltersByPath,
"destinationFilters": destinationFilters?.map { $0.rawValue },
]
if optional != TargetSource.optionalDefault {
dict["optional"] = optional
}
return dict
}
}
extension TargetSource: PathContainer {
static var pathProperties: [PathProperty] {
[
.string("path"),
]
}
}