File: SwiftSymbolExtractor.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 (149 lines) | stat: -rw-r--r-- 8,051 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
//===----------------------------------------------------------------------===//
//
// 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 Foundation
import SWBUtil
import SWBMacro

final class SwiftSymbolExtractor: GenericCompilerSpec, GCCCompatibleCompilerCommandLineBuilder, SpecIdentifierType, @unchecked Sendable {
    static let identifier = "com.apple.compilers.documentation.swift-symbol-extract"

    static public func shouldConstructSymbolExtractionTask(_ cbc: CommandBuildContext) -> Bool {
        guard
            // Only extract symbols when documentation will be built
            DocumentationCompilerSpec.shouldConstructSymbolGenerationTask(cbc),
            // and when DOCC_EXTRACT_SWIFT_INFO_FOR_OBJC_SYMBOLS is YES
            cbc.scope.evaluate(BuiltinMacros.DOCC_EXTRACT_SWIFT_INFO_FOR_OBJC_SYMBOLS)
        else {
            return false
        }

        guard cbc.scope.evaluate(BuiltinMacros.DEFINES_MODULE) else {
            return false
        }

        if let swiftVersion = try? Version(cbc.scope.evaluate(BuiltinMacros.SWIFT_VERSION)), swiftVersion < Version(4) {
            // The `swift-symbolgraph-extract` tool requires at least Swift version 4.0
            // See https://github.com/apple/swift/blob/main/lib/Basic/Version.cpp#L292
            return false
        }

        let buildPhaseTarget = cbc.producer.configuredTarget?.target as? BuildPhaseTarget
        let willAlreadyExtractSwiftSymbolGraphFiles = buildPhaseTarget?.sourcesBuildPhase?.containsSwiftSources(cbc.producer, cbc.producer, cbc.scope, cbc.producer.filePathResolver) ?? false

        // Skip this task if Swift code will be compiled.
        return !willAlreadyExtractSwiftSymbolGraphFiles
    }

    override func constructTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) async {
        // Only extract symbols when documentation will be built
        guard Self.shouldConstructSymbolExtractionTask(cbc) else {
            return
        }

        // The main symbol graph files list also include zippered variants. Here we only want the path to the current variant.
        let mainSymbolGraphFiles = SwiftCompilerSpec.mainSymbolGraphFilesForCurrentArch(cbc: cbc)
        let targetTripleWithoutVersion = cbc.scope.evaluate(BuiltinMacros.SWIFT_TARGET_TRIPLE) {
            if $0 == BuiltinMacros.SWIFT_DEPLOYMENT_TARGET {
                return cbc.scope.namespace.parseString("")
            } else {
                return nil
            }
        }

        let symbolGraphOutputDir: Path
        if let onlyOutput = mainSymbolGraphFiles.only {
            symbolGraphOutputDir = onlyOutput.dirname
        } else if cbc.producer.sdkVariant != nil, let matchingOutputDir = mainSymbolGraphFiles.first(where: { $0.dirname.basename.hasSuffix(targetTripleWithoutVersion) }) {
            symbolGraphOutputDir = matchingOutputDir.dirname
        } else {
            delegate.error("Couldn't determine a directory of symbol graph files for \(cbc.scope.evaluate(BuiltinMacros.SWIFT_TARGET_TRIPLE)) among:\n\(mainSymbolGraphFiles.sorted().map { $0.str }.joined(separator: "\n"))")
            return
        }

        func lookup(_ macro: MacroDeclaration) -> MacroExpression? {
            // Override SYMBOL_GRAPH_EXTRACTOR_SEARCH_PATHS to construct all search paths (user headers, headers, system headers, frameworks, system frameworks, etc.)
            // We do this to also include header maps.
            switch macro {
            case BuiltinMacros.SYMBOL_GRAPH_EXTRACTOR_SEARCH_PATHS:
                let headerSearchPaths = GCCCompatibleCompilerSpecSupport.headerSearchPathArguments(cbc.producer, cbc.scope, usesModules: true)
                let frameworkSearchPaths = GCCCompatibleCompilerSpecSupport.frameworkSearchPathArguments(cbc.producer, cbc.scope)
                let sparseSDKSearchPaths = GCCCompatibleCompilerSpecSupport.sparseSDKSearchPathArguments(cbc.producer.sparseSDKs, headerSearchPaths.headerSearchPaths, frameworkSearchPaths.frameworkSearchPaths)

                var defaultHeaderSearchPaths: [String] = headerSearchPaths.searchPathArguments(for: self, scope: cbc.scope)

                if let vfsArgumentIndex = defaultHeaderSearchPaths.firstIndex(of: "-ivfsoverlay") {
                    // Add '-Xcc' before the VFS overlay argument and its value
                    defaultHeaderSearchPaths.insert("-Xcc", at: vfsArgumentIndex + 1)
                    defaultHeaderSearchPaths.insert("-Xcc", at: vfsArgumentIndex)
                }

                // Evaluate the original value and prefix each argument with "-I"
                let userHeaderSearchPaths = cbc.scope.evaluate(BuiltinMacros.SYMBOL_GRAPH_EXTRACTOR_SEARCH_PATHS).map {
                    return "-I" + $0
                }
                let defaultFrameworkSearchPaths = frameworkSearchPaths.searchPathArguments(for: self, scope:cbc.scope) + sparseSDKSearchPaths.searchPathArguments(for: self, scope: cbc.scope)

                // swift-symbolgraph-extract doesn't expect the `-iquote`, `-isystem`, or `-iframework` flags so we map those to `-I` and `-Fsystem` instead.
                let allSearchPaths = (defaultHeaderSearchPaths + userHeaderSearchPaths + defaultFrameworkSearchPaths)
                    .map { (argument: String) -> String in
                    switch argument {
                    case "-iquote", "-isystem":
                        return "-I"
                    case "-iframework":
                        return "-Fsystem"
                    default:
                        return argument
                    }
                }

                return cbc.scope.namespace.parseLiteralStringList(allSearchPaths)

            case BuiltinMacros.SWIFT_VERSION:
                // The 'swift-symbolgraph-extract' only accepts a very small subset of versions: major versions and 4.2 (but no other minor versions).
                // See https://github.com/apple/swift/blob/main/lib/Basic/Version.cpp#L292
                let swiftVersion = cbc.scope.evaluate(BuiltinMacros.SWIFT_VERSION)
                do {
                    let version = try Version(swiftVersion).normalized(toNumberOfComponents: 2)
                    if version[0] == 4 && version[1] == 2 {
                        return cbc.scope.table.namespace.parseString(version.description)
                    } else {
                        return cbc.scope.table.namespace.parseString("\(version[0])")
                    }
                } catch {
                    return nil
                }

            case BuiltinMacros.SYMBOL_GRAPH_EXTRACTOR_OUTPUT_DIR:
                return cbc.scope.namespace.parseString(symbolGraphOutputDir.str)

            default:
                return nil
            }
        }

        // Extract the symbol information
        await delegate.createTask(
            type: self,
            ruleInfo: defaultRuleInfo(cbc, delegate, lookup: lookup),
            commandLine: commandLineFromTemplate(cbc, delegate, optionContext: discoveredCommandLineToolSpecInfo(cbc.producer, cbc.scope, delegate), lookup: lookup).map(\.asString),
            environment: environmentFromSpec(cbc, delegate, lookup: lookup),
            workingDirectory: cbc.producer.defaultWorkingDirectory,
            inputs: cbc.commandOrderingInputs.map { delegate.createNode($0.path) },
            outputs: mainSymbolGraphFiles.map { delegate.createNode($0) },
            action: nil,
            execDescription: resolveExecutionDescription(cbc, delegate, lookup: lookup),
            enableSandboxing: enableSandboxing,
            additionalTaskOrderingOptions: .compilationRequirement
        )
    }
}