File: InterfaceBuilderCompiler.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 (188 lines) | stat: -rw-r--r-- 9,763 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
//===----------------------------------------------------------------------===//
//
// 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 SWBUtil
public import SWBCore
public import SWBMacro

public class IbtoolCompilerSpec : GenericCompilerSpec, IbtoolCompilerSupport, @unchecked Sendable {
    /// The info object collects information across the build phase so that an ibtool task doesn't try to produce a ~device output which is already being explicitly produced from another input.
    private final class BuildPhaseInfo: BuildPhaseInfoForToolSpec {
        var allInputFilenames = Set<String>()

        func addToContext(_ ftb: FileToBuild) {
            // Only collect info about files we want to match against.
            // FIXME: We should be using FileTypeSpec.conformsTo() here, but we don't have a good way in this context to look up the file type.
            guard ftb.fileType.identifier == "file.xib" else {
                return
            }
            allInputFilenames.insert(ftb.absolutePath.basenameWithoutSuffix)
        }

        func filterOutputFiles(_ outputs: [any PlannedNode], inputs: [Path]) -> [any PlannedNode] {
            // This filter is operating on basenames-without-suffix, since ibtool is going to transform xibs into nibs.
            return outputs.filter {
                let outputFilename = $0.path.basenameWithoutSuffix
                let inputFilenames = Set(inputs.map { $0.basenameWithoutSuffix })

                // If this output filename is the same as one of our own input filenames, then we keep it.
                guard !inputFilenames.contains(outputFilename) else {
                    return true
                }

                // If this output filename is among any of the input filenames that *aren't* one of our own inputs, then we remove it.
                let otherInputFilenames = allInputFilenames.subtracting(inputFilenames)
                guard !otherInputFilenames.contains(outputFilename) else {
                    return false
                }

                // If this output filename is something like ~ipad~ipad or ~iphone~iphone then we remove it (just to avoid ugliness).
                guard !outputFilename.contains("~ipad~") && !outputFilename.contains("~iphone~") else {
                    return false
                }

                // Otherwise we keep it.
                return true
            }
        }
    }

    public override func newBuildPhaseInfo() -> (any BuildPhaseInfoForToolSpec)? {
        return BuildPhaseInfo()
    }

    /// Override to compute the special arguments.
    public override func constructTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) async {
        var specialArgs = [String]()

        specialArgs += targetDeviceArguments(cbc, delegate)
        specialArgs += minimumDeploymentTargetArguments(cbc, delegate)

        // Get the strings file paths and regions.
        let stringsFiles = stringsFilesAndRegions(cbc)

        // Define the inputs, including the strings files from any variant groups.
        let inputs = cbc.inputs.map({ $0.absolutePath }) + stringsFiles.map({ $0.stringsFile })

        // Compute and declare the outputs.
        var outputs = evaluatedOutputs(cbc, delegate) ?? []
        if let buildPhaseInfo = cbc.buildPhaseInfo as? BuildPhaseInfo {
            outputs = buildPhaseInfo.filterOutputFiles(outputs, inputs: inputs)
        }
        for output in outputs {
            delegate.declareOutput(FileToBuild(absolutePath: output.path, inferringTypeUsing: cbc.producer))
        }

        // FIXME: Add the output paths for the .strings files.  I think this is a little tricky because ibtool will lay down strings files for .xibs, but not for .storyboards; instead the storyboard linker will put the .strings files in place.  I'm not certain about that, though.

        // Add the additional outputs defined by the spec.  These are not declared as outputs but should be processed by the tool separately.
        let additionalEvaluatedOutputsResult = await additionalEvaluatedOutputs(cbc, delegate)
        outputs += additionalEvaluatedOutputsResult.outputs.map { output in
            if let fileTypeIdentifier = output.fileType, let fileType = cbc.producer.lookupFileType(identifier: fileTypeIdentifier) {
                delegate.declareOutput(FileToBuild(absolutePath: output.path, fileType: fileType))
            }
            return delegate.createNode(output.path)
        }

        if let infoPlistContent = additionalEvaluatedOutputsResult.generatedInfoPlistContent {
            delegate.declareGeneratedInfoPlistContent(infoPlistContent)
        }

        // Construct the command line.
        func lookup(_ macro: MacroDeclaration) -> MacroExpression? {
            switch macro {
            case BuiltinMacros.IBC_REGIONS_AND_STRINGS_FILES:
                return stringsFiles.count > 0 ? cbc.scope.table.namespace.parseLiteralStringList(stringsFiles.map({ "\($0.region):\($0.stringsFile.str)" })) : nil
            default:
                return nil
            }
        }

        let commandLine = await commandLineFromTemplate(cbc, delegate, optionContext: discoveredCommandLineToolSpecInfo(cbc.producer, cbc.scope, delegate), specialArgs: specialArgs, lookup: lookup)

        delegate.createTask(
            type: self,
            dependencyData: nil,
            payload: nil,
            ruleInfo: defaultRuleInfo(cbc, delegate),
            additionalSignatureData: "",
            commandLine: commandLine,
            additionalOutput: [],
            environment: environmentFromSpec(cbc, delegate),
            workingDirectory: cbc.producer.defaultWorkingDirectory,
            inputs: inputs.map(delegate.createNode),
            outputs: outputs,
            mustPrecede: [],
            action: createTaskAction(cbc, delegate),
            execDescription: resolveExecutionDescription(cbc, delegate),
            preparesForIndexing: false,
            enableSandboxing: enableSandboxing,
            llbuildControlDisabled: true,
            additionalTaskOrderingOptions: []
        )
    }

    override public func customOutputParserType(for task: any ExecutableTask) -> (any TaskOutputParser.Type)? {
        return InterfaceBuilderCompilerOutputParser.self
    }

    public override func discoveredCommandLineToolSpecInfo(_ producer: any CommandProducer, _ scope: MacroEvaluationScope, _ delegate: any CoreClientTargetDiagnosticProducingDelegate) async -> (any DiscoveredCommandLineToolSpecInfo)? {
        do {
            return try await discoveredIbtoolToolInfo(producer, delegate, at: self.resolveExecutablePath(producer, scope.ibtoolExecutablePath()))
        } catch {
            delegate.error(error)
            return nil
        }
    }
}

public final class IbtoolCompilerSpecNIB: IbtoolCompilerSpec, SpecIdentifierType, @unchecked Sendable {
    public static let identifier = "com.apple.xcode.tools.ibtool.compiler"
}

public final class IbtoolCompilerSpecStoryboard: IbtoolCompilerSpec, SpecIdentifierType, @unchecked Sendable {
    public static let identifier = "com.apple.xcode.tools.ibtool.storyboard.compiler"

    override public func environmentFromSpec(_ cbc: CommandBuildContext, _ delegate: any DiagnosticProducingDelegate, lookup: ((MacroDeclaration) -> MacroExpression?)? = nil) -> [(String, String)] {
        let cachingEnabled = cbc.scope.evaluate(BuiltinMacros.ENABLE_GENERIC_TASK_CACHING)

        var environment: [(String, String)] = super.environmentFromSpec(cbc, delegate)
        if cachingEnabled {
            environment.append(("IBToolNeverDeque", "1"))
        }
        return environment
    }

    override public func createTaskAction(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) -> (any PlannedTaskAction)? {
        if cbc.scope.evaluate(BuiltinMacros.ENABLE_GENERIC_TASK_CACHING), let casOptions = try? CASOptions.create(cbc.scope, .generic) {
            return delegate.taskActionCreationDelegate.createGenericCachingTaskAction(
                enableCacheDebuggingRemarks: cbc.scope.evaluate(BuiltinMacros.GENERIC_TASK_CACHE_ENABLE_DIAGNOSTIC_REMARKS),
                enableTaskSandboxEnforcement: !cbc.scope.evaluate(BuiltinMacros.DISABLE_TASK_SANDBOXING),
                sandboxDirectory: cbc.scope.evaluate(BuiltinMacros.TEMP_SANDBOX_DIR),
                extraSandboxSubdirectories: [],
                developerDirectory: cbc.scope.evaluate(BuiltinMacros.DEVELOPER_DIR),
                casOptions: casOptions)
        } else {
            return nil
        }
    }

    override public func commandLineFromTemplate(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, optionContext: (any DiscoveredCommandLineToolSpecInfo)?, specialArgs: [String] = [], lookup: ((MacroDeclaration) -> MacroExpression?)? = nil) -> [CommandLineArgument] {
        var commandLine = super.commandLineFromTemplate(cbc, delegate, optionContext: optionContext, specialArgs: specialArgs, lookup: lookup)
        guard let primaryOutput = evaluatedOutputs(cbc, delegate)?.first else {
            delegate.error("Unable to determine primary output for storyboard compilation")
            return []
        }
        commandLine.append(contentsOf: [.literal("--compilation-directory"), .parentPath(primaryOutput.path)])
        return commandLine
    }
}