File: BuildRule.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 (235 lines) | stat: -rw-r--r-- 12,642 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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
//===----------------------------------------------------------------------===//
//
// 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
import SWBUtil
public import SWBMacro

public final class BuildRule: ProjectModelItem {
    /// The build rule name.
    public let name: String

    /// The GUID of the build rule.
    public let guid: String

    public let inputSpecifier: BuildRuleInputSpecifier

    public let actionSpecifier: BuildRuleActionSpecifier

    init(_ model: SWBProtocol.BuildRule, _ pifLoader: PIFLoader) {
        // FIXME: Clean up the model within this class, once the legacy implementation dies.
        self.name = model.name
        self.guid = model.guid
        switch model.inputSpecifier {
        case .patterns(let value):
            switch value {
            case .string(let str):
                // Split the value into a list of patterns and then parse each one.
                // FIXME: <rdar://problem/29304140> Adopt the new API when it's available rather than doing this naive split.
                self.inputSpecifier = .patterns(str.split(separator: " ").map({ pifLoader.userNamespace.parseString(String($0)) }))
            case .stringList(let list):
                self.inputSpecifier = .patterns(list.map({ pifLoader.userNamespace.parseString($0) }))
            }
        case .fileType(let identifier):
            self.inputSpecifier = .fileType(identifier: identifier)
        }


        switch model.actionSpecifier {
        case .compiler(let identifier):
            self.actionSpecifier = .compiler(identifier: identifier)
        case .shellScript(let contents, let inputInfo, let inputFileLists, let outputInfo, let outputFileLists, let dependencyInfo, let runOncePerArchitecture):
            self.actionSpecifier = .shellScript(
                contents: contents,
                inputs: inputInfo.map { pifLoader.userNamespace.parseString($0) },
                inputFileLists: inputFileLists.map { pifLoader.userNamespace.parseString($0) },
                outputs: outputInfo.map {
                    BuildRuleOutputInfo(path: pifLoader.userNamespace.parseString($0.path), additionalCompilerFlags: $0.additionalCompilerFlags.map {
                        pifLoader.userNamespace.parseStringList($0)
                    } ?? pifLoader.userNamespace.parseLiteralStringList([]))
                },
                outputFileLists: outputFileLists.map { pifLoader.userNamespace.parseString($0) },
                dependencyInfo: DependencyInfoFormat.fromPIF(dependencyInfo, pifLoader: pifLoader),
                runOncePerArchitecture: runOncePerArchitecture
            )
        }
    }

    @_spi(Testing) public init(fromDictionary pifDict: ProjectModelItemPIF, withPIFLoader pifLoader: PIFLoader) throws {
        // The name is required.
        name = try Self.parseValueForKeyAsString(PIFKey_name, pifDict: pifDict)

        guid = try Self.parseValueForKeyAsString(PIFKey_guid, pifDict: pifDict)

        let fileTypeIdentifier = try Self.parseValueForKeyAsString(PIFKey_BuildRule_fileTypeIdentifier, pifDict: pifDict)
        if fileTypeIdentifier == BuildRule_FileTypeIsPatternIdentifier {
            guard let filePatterns = try Self.parseOptionalValueForKeyAsString(PIFKey_BuildRule_filePatterns, pifDict: pifDict) else {
                throw PIFParsingError.custom("BuildRule input file type is pattern, but no pattern string was defined")
            }

            // Split the file patterns into a list of patterns and then parse each one.
            // FIXME: <rdar://problem/29304140> Adopt the new API when it's available rather than doing this naive split.
            self.inputSpecifier = .patterns(filePatterns.split(separator: " ").map({ return pifLoader.userNamespace.parseString(String($0)) }))
        } else if !fileTypeIdentifier.isEmpty {
            self.inputSpecifier = .fileType(identifier: fileTypeIdentifier)
        } else {
            throw PIFParsingError.custom("BuildRule \(PIFKey_BuildRule_fileTypeIdentifier) is empty")
        }

        let compilerSpecificationIdentifier = try Self.parseValueForKeyAsString(PIFKey_BuildRule_compilerSpecificationIdentifier, pifDict: pifDict)
        if compilerSpecificationIdentifier == BuildRule_CompilerIsShellScriptIdentifier {
            guard let scriptContents = try Self.parseOptionalValueForKeyAsString(PIFKey_BuildRule_scriptContents, pifDict: pifDict) else {
                throw PIFParsingError.custom("BuildRule compiler is shell script, but no script contents were defined")
            }

            let inputFilePaths = try Self.parseValueForKeyAsArrayOfStrings(PIFKey_BuildRule_inputFilePaths, pifDict: pifDict).map { return pifLoader.userNamespace.parseString($0) }

            let inputFileListPaths = (try Self.parseOptionalValueForKeyAsArrayOfStrings(PIFKey_BuildRule_inputFileListPaths, pifDict: pifDict) ?? []).map { return pifLoader.userNamespace.parseString($0) }

            let outputFilePaths = try Self.parseValueForKeyAsArrayOfStrings(PIFKey_BuildRule_outputFilePaths, pifDict: pifDict).map { return pifLoader.userNamespace.parseString($0) }

            let outputFileListPaths = (try Self.parseOptionalValueForKeyAsArrayOfStrings(PIFKey_BuildRule_outputFileListPaths, pifDict: pifDict) ?? []).map { return pifLoader.userNamespace.parseString($0) }


            // The output file compiler flags are optional.
            let outputFilesCompilerFlags: [MacroStringListExpression]?
            if let data = pifDict[PIFKey_BuildRule_outputFilesCompilerFlags] {
                // Each entry in the array is a list of values.
                guard case let .plArray(contents) = data else {
                    throw PIFParsingError.incorrectType(keyName: PIFKey_BuildRule_outputFilesCompilerFlags, objectType: Self.self, expectedType: "Array", destinationType: nil)
                }
                outputFilesCompilerFlags = try contents.map { flagData in
                    guard case let .plArray(value) = flagData else {
                        throw PIFParsingError.incorrectType(keyName: PIFKey_BuildRule_outputFilesCompilerFlags, objectType: Self.self, expectedType: "Array", destinationType: nil)
                    }
                    return try pifLoader.userNamespace.parseStringList(value.map { item -> String in
                        guard case let .plString(value) = item else {
                            throw PIFParsingError.incorrectType(keyName: PIFKey_BuildRule_outputFilesCompilerFlags, objectType: Self.self, expectedType: "String", destinationType: nil)
                        }
                        return value
                    })
                }
            } else {
                outputFilesCompilerFlags = nil
            }

            let dependencyInfo: DependencyInfoFormat?
            if let format = try Self.parseOptionalValueForKeyAsStringEnum(PIFKey_BuildRule_dependencyFileFormat, pifDict: pifDict) as PIFDependencyFormatValue? {
                let dependencyFilePaths = try Self.parseValueForKeyAsArrayOfStrings(PIFKey_BuildRule_dependencyFilePaths, pifDict: pifDict).map({ return pifLoader.userNamespace.parseString($0) })

                switch format {
                case .dependencyInfo:
                    guard dependencyFilePaths.count == 1 else {
                        throw PIFParsingError.custom("BuildRule dependencyInfo dependency format expects 1 dependency file path, found \(dependencyFilePaths.count)")
                    }
                    dependencyInfo = .dependencyInfo(dependencyFilePaths[0])
                case .makefile:
                    guard dependencyFilePaths.count == 1 else {
                        throw PIFParsingError.custom("BuildRule makefile dependency format expects 1 dependency file path, found \(dependencyFilePaths.count)")
                    }
                    dependencyInfo = .makefile(dependencyFilePaths[0])
                case .makefiles:
                    dependencyInfo = .makefiles(dependencyFilePaths)
                }

            } else {
                dependencyInfo = nil
            }

            let runOncePerArchitecture = try Self.parseValueForKeyAsBool(PIFKey_BuildRule_runOncePerArchitecture, pifDict: pifDict, defaultValue: true)

            let outputFiles = outputFilePaths.enumerated().map { (i, expr) -> BuildRuleOutputInfo in
                if let flags = outputFilesCompilerFlags, i < flags.count {
                    return .init(path: expr, additionalCompilerFlags: flags[i])
                } else {
                    return .init(path: expr, additionalCompilerFlags: nil)
                }
            }

            self.actionSpecifier = .shellScript(contents: scriptContents, inputs: inputFilePaths, inputFileLists: inputFileListPaths, outputs: outputFiles, outputFileLists: outputFileListPaths, dependencyInfo: dependencyInfo, runOncePerArchitecture: runOncePerArchitecture)
        } else if !compilerSpecificationIdentifier.isEmpty {
            self.actionSpecifier = .compiler(identifier: compilerSpecificationIdentifier)
        } else {
            throw PIFParsingError.custom("BuildRule \(PIFKey_BuildRule_compilerSpecificationIdentifier) is empty")
        }
    }

    private var fileTypeIdentifier: String {
        switch self.inputSpecifier {
        case .fileType(let identifier):
            return identifier
        case .patterns:
            return BuildRule_FileTypeIsPatternIdentifier
        }
    }

    private var compilerSpecificationIdentifier: String {
        switch self.actionSpecifier {
        case .compiler(let identifier):
            return identifier
        case .shellScript:
            return BuildRule_CompilerIsShellScriptIdentifier
        }
    }

    public var description: String
    {
        return "\(type(of: self))<\(fileTypeIdentifier)->\(compilerSpecificationIdentifier)>"
    }
}

public enum BuildRuleInputSpecifier: Sendable {
    /// A list of file patterns to match the input file's path against, if the identifier is the pattern proxy.
    ///
    /// We would like to deprecate the ability to expand macros in the patterns, as it's unclear if many (or any) people use it.
    case patterns([MacroStringExpression])

    /// The file type identifier to match the input file's file type against.
    case fileType(identifier: String)
}

public struct BuildRuleOutputInfo: Sendable {
    /// The output file path, which may contain build setting references.
    public let path: MacroStringExpression

    /// The custom per-file compiler flags to apply to the output.
    public let additionalCompilerFlags: MacroStringListExpression?
}

// See also: SWBProtocol.ProjectModel.BuildRule.ActionSpecifier
public enum BuildRuleActionSpecifier: Sendable {
    /// The identifier of the compiler specification to use to process the input file.
    case compiler(identifier: String)

    /// Use a custom shell script to process the rule's inputs.
    ///
    /// - contents: The contents of the script to run to process the input file, if the compiler specification identifier is the script proxy.
    /// - inputs: The list of (additional) input file paths, which may contain build setting references.
    /// - inputFileLists: The list of xcfilelists that contains a list of additional input file paths
    /// - outputs: The list of pairs of output file path (which may contain build setting references) and list of per-file compiler flags.
    /// - outputFileLists: The list of xcfilelists that contains a list of additional output file paths
    /// - dependencyInfo: The dependency info.
    /// - runOncePerArchitecture: Run once per architecture/variant.
    case shellScript(contents: String,
                     inputs: [MacroStringExpression],
                     inputFileLists: [MacroStringExpression],
                     outputs: [BuildRuleOutputInfo],
                     outputFileLists: [MacroStringExpression],
                     dependencyInfo: DependencyInfoFormat?,
                     runOncePerArchitecture: Bool)
}

// MARK: Build rule constant strings


let BuildRule_FileTypeIsPatternIdentifier       = "pattern.proxy"
let BuildRule_CompilerIsShellScriptIdentifier   = "com.apple.compilers.proxy.script"