File: Project.swift

package info (click to toggle)
swiftlang 6.2.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,856,264 kB
  • sloc: cpp: 9,995,718; ansic: 2,234,019; asm: 1,092,167; python: 313,940; objc: 82,726; f90: 80,126; lisp: 38,373; pascal: 25,580; sh: 20,378; ml: 5,058; perl: 4,751; makefile: 4,725; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (218 lines) | stat: -rw-r--r-- 9,662 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SWBProtocol
public import SWBUtil

public final class Project: ProjectModelItem, PIFObject, Hashable, Encodable
{
    static func referencedObjects(for data: EncodedPIFValue) throws -> [PIFObjectReference] {
        // Any errors here will be diagnosed in the loader.
        switch data {
        case .json(let data):
            guard case let .plArray(projects)? = data["targets"] else { return [] }
            return projects.compactMap{
                guard case let .plString(signature) = $0 else { return nil }
                return PIFObjectReference(signature: signature, type: .target)
            }

        case .binary(let data):
            // The only direct references are to targets.
            //
            // FIXME: This sucks, we are doing the protocol decode twice: <rdar://problem/31097863> Don't require duplicate binary PIF decode in incremental PIF transfer
            let deserializer = MsgPackDeserializer(data)
            let model: SWBProtocol.Project = try deserializer.deserialize()
            return model.targetSignatures.map{ PIFObjectReference(signature: $0, type: .target) }
        }
    }

    static func construct(from data: EncodedPIFValue, signature: PIFObject.Signature, loader: PIFLoader) throws -> Project {
        switch data {
        case .json(let data):
            return try construct(from: data, signature: signature, loader: loader)
        case .binary(let data):
            let deserializer = MsgPackDeserializer(data)
            let model: SWBProtocol.Project = try deserializer.deserialize()
            return try Project(model, loader, signature: signature)
        }
    }

    /// The PIFObject type name
    static let pifType = PIFObjectType.project

    /// A unique signature for the instance.
    public let signature: String

    public let guid: String

    /// The path to the project's .xcodeproj file.
    public let xcodeprojPath: Path

    /// The source root for the project.
    public let sourceRoot: Path

    /// The name of the project.
    public let name: String

    public let targets: [Target]
    public let groupTree: FileGroup
    public let buildConfigurations: [BuildConfiguration]
    public let defaultConfigurationName: String
    public let developmentRegion: String?
    public let classPrefix: String
    public let appPreferencesBuildSettings: [String: PropertyListItem]
    public let isPackage: Bool

    private enum CodingKeys : String, CodingKey {
        case name
        case signature
        case guid
        case targets
    }

    static func construct(from data: ProjectModelItemPIF, signature: PIFObject.Signature, loader: PIFLoader) throws -> Project {
        // Delegate to protocol-based representation, if in use.
        if let data = try parseOptionalValueForKeyAsByteString("data", pifDict: data) {
            let deserializer = MsgPackDeserializer(data)
            let model: SWBProtocol.Project = try deserializer.deserialize()
            return try Project(model, loader, signature: signature)
        }
        return try Project(fromDictionary: data, signature: signature, withPIFLoader: loader)
    }

    init(_ model: SWBProtocol.Project, _ pifLoader: PIFLoader, signature: String) throws {
        self.signature = signature
        self.guid = model.guid
        self.isPackage = model.isPackage
        self.xcodeprojPath = model.xcodeprojPath
        self.sourceRoot = model.sourceRoot
        self.targets = try model.targetSignatures.map{ try pifLoader.loadReference(signature: $0, type: Target.self) }
        self.groupTree = try Reference.create(model.groupTree, pifLoader, isRoot: true) as! FileGroup
        self.buildConfigurations = model.buildConfigurations.map{ BuildConfiguration($0, pifLoader) }
        self.defaultConfigurationName = model.defaultConfigurationName
        self.developmentRegion = model.developmentRegion
        self.classPrefix = model.classPrefix
        self.appPreferencesBuildSettings = BuildConfiguration.convertMacroBindingSourceToPlistDictionary(model.appPreferencesBuildSettings)

        // Compute the derived name.
        self.name = xcodeprojPath.basenameWithoutSuffix

        try validateTargets()
    }

    @_spi(Testing) public init(fromDictionary pifDict: ProjectModelItemPIF, signature: String, withPIFLoader pifLoader: PIFLoader) throws {
        self.signature = signature

        // The GUID is required.
        guid = try Self.parseValueForKeyAsString(PIFKey_guid, pifDict: pifDict)

        // The path to the .xcodeproj file is required.
        let xcodeprojPath = try Path(Self.parseValueForKeyAsString(PIFKey_path, pifDict: pifDict))
        self.xcodeprojPath = xcodeprojPath

        // Use the name if got one from PIF, otherwise compute it from the project path.
        let name = try Self.parseOptionalValueForKeyAsString(PIFKey_Project_name, pifDict: pifDict)
        self.name = name ?? xcodeprojPath.basenameWithoutSuffix

        self.isPackage = try Self.parseValueForKeyAsBool(PIFKey_Project_isPackage, pifDict: pifDict)

        // The source root is optional, while we wait for the PIF format to update.
        if let path = try Self.parseOptionalValueForKeyAsString(PIFKey_Project_projectDirectory, pifDict: pifDict) {
            sourceRoot = Path(path)
        } else {
            sourceRoot = xcodeprojPath.dirname
        }

        // The top-level file group is required.  We load this early because our or our targets' build configurations can refer to files in here.
        groupTree = try FileGroup.parsePIFDictAsReference(Self.parseValueForKeyAsPIFDictionary(PIFKey_Project_groupTree, pifDict: pifDict), pifLoader: pifLoader, isRoot: true) as! FileGroup

        // The list of targets is required.
        targets = try Self.parseValueForKeyAsArrayOfIndirectObjects(PIFKey_Project_targets, pifDict: pifDict, pifLoader: pifLoader)

        // The list of build configurations is required.
        buildConfigurations = try Self.parseValueForKeyAsArrayOfProjectModelItems(PIFKey_buildConfigurations, pifDict: pifDict, pifLoader: pifLoader, construct: { try BuildConfiguration(fromDictionary: $0, withPIFLoader: pifLoader) })

        // The default configuration name is required.
        defaultConfigurationName = try Self.parseValueForKeyAsString(PIFKey_Project_defaultConfigurationName, pifDict: pifDict)

        // The development region name is required.
        developmentRegion = try Self.parseOptionalValueForKeyAsString(PIFKey_Project_developmentRegion, pifDict: pifDict)

        classPrefix = try Self.parseOptionalValueForKeyAsString(PIFKey_Project_classPrefix, pifDict: pifDict) ?? ""

        // Get the application preferences build settings.
        appPreferencesBuildSettings = try Self.parseOptionalValueForKeyAsPIFDictionary(PIFKey_Project_appPreferencesBuildSettings, pifDict: pifDict) ?? [:]

        try validateTargets()
    }

    public var description: String
    {
        return "\(type(of: self))<\(guid):\(xcodeprojPath.str):\(targets.count) targets>"
    }

    /// Get the named configuration.
    //
    // FIXME: This code is currently duplicated on the target. We should perhaps have an explicit BuildConfigurationList object, like Xcode, to hold these.
    func getConfiguration(_ name: String) -> BuildConfiguration? {
        for config in buildConfigurations {
            if config.name == name {
                return config
            }
        }
        return nil
    }

    /// Get the effective configuration to use for the given name.
    func getEffectiveConfiguration(_ name: String?, packageConfigurationOverride: String?) -> BuildConfiguration? {
        // Return the named config, if present.
        if let name {
            if let config = getConfiguration(name) {
                return config
            }
        }

        // Use the package-specific override, if applicable.
        if let packageConfigurationOverride, isPackage {
            if let config = getConfiguration(packageConfigurationOverride) {
                return config
            }
        }

        // Otherwise, return the configuration matching the default configuration name.
        if let config = getConfiguration(defaultConfigurationName) {
            return config
        }

        // Otherwise, return any configuration.
        return buildConfigurations.first
    }

    /// Returns a hash value based on the identity of the object.
    public func hash(into hasher: inout Hasher) {
        hasher.combine(ObjectIdentifier(self))
    }

    public static func ==(lhs: Project, rhs: Project) -> Bool {
        return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
    }

    /// Validates the target listing to ensure that collection of targets are valid for the given project.
    /// - remark: This is used to ensure that no `PackageProductTarget` exists in a project where `isPackage = false`.
    func validateTargets() throws {
        if !isPackage {
            for target in targets where target is PackageProductTarget {
                throw PIFLoadingError.incompatiblePackageTargetProject(target: target.name)
            }
        }
    }
}