-
Notifications
You must be signed in to change notification settings - Fork 187
Expand file tree
/
Copy pathSourcesTaskProducer.swift
More file actions
2293 lines (1971 loc) · 146 KB
/
Copy pathSourcesTaskProducer.swift
File metadata and controls
2293 lines (1971 loc) · 146 KB
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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
package import SWBCore
package import SWBUtil
import struct SWBProtocol.BuildOperationTaskEnded
import Foundation
package import SWBMacro
import SwiftDriver
// Some things that should probably live in this task producer that might not be immediately obvious:
// Emitting an error if PGO is turned on for a target containing Swift files.
/// This class adapts the TaskGenerationDelegate protocol used by the Core to that provided by the producer delegate API, for use inside the sources build phase.
///
/// This delegate auto-attaches constructed tasks to the generated headers completion ordering gates, and chains to another delegate for performing the actual work.
private final class SourcesPhaseBasedTaskGenerationDelegate: TaskGenerationDelegate {
/// The producer we are operating on behalf of.
let producer: SourcesTaskProducer
/// The delegate to chain to.
let delegate: any TaskGenerationDelegate
let userPreferences: UserPreferences
init(producer: SourcesTaskProducer, userPreferences: UserPreferences, delegate: any TaskGenerationDelegate) {
self.producer = producer
self.delegate = delegate
self.userPreferences = userPreferences
}
func diagnosticsEngine(for target: ConfiguredTarget?) -> DiagnosticProducingDelegateProtocolPrivate<DiagnosticsEngine> {
return delegate.diagnosticsEngine(for: target)
}
func beginActivity(ruleInfo: String, executionDescription: String, signature: ByteString, target: ConfiguredTarget?, parentActivity: ActivityID?) -> ActivityID {
delegate.beginActivity(ruleInfo: ruleInfo, executionDescription: executionDescription, signature: signature, target: target, parentActivity: parentActivity)
}
func endActivity(id: ActivityID, signature: ByteString, status: BuildOperationTaskEnded.Status) {
delegate.endActivity(id: id, signature: signature, status: status)
}
func emit(data: [UInt8], for activity: ActivityID, signature: ByteString) {
delegate.emit(data: data, for: activity, signature: signature)
}
func emit(diagnostic: Diagnostic, for activity: ActivityID, signature: ByteString) {
delegate.emit(diagnostic: diagnostic, for: activity, signature: signature)
}
var hadErrors: Bool {
delegate.hadErrors
}
var diagnosticContext: DiagnosticContextData {
return delegate.diagnosticContext
}
func createVirtualNode(_ name: String) -> PlannedVirtualNode {
return delegate.createVirtualNode(name)
}
func createNode(_ path: Path) -> PlannedPathNode {
return delegate.createNode(path)
}
func createDirectoryTreeNode(_ path: Path, excluding: [String]) -> PlannedDirectoryTreeNode {
return delegate.createDirectoryTreeNode(path, excluding: excluding)
}
func createBuildDirectoryNode(absolutePath path: Path) -> PlannedPathNode {
return delegate.createBuildDirectoryNode(absolutePath: path)
}
func declareOutput(_ file: FileToBuild) {
delegate.declareOutput(file)
}
func declareGeneratedSourceFile(_ path: Path) {
delegate.declareGeneratedSourceFile(path)
}
func declareGeneratedInfoPlistContent(_ path: Path) {
delegate.declareGeneratedInfoPlistContent(path)
}
func declareGeneratedPrivacyPlistContent(_ path: Path) {
delegate.declareGeneratedPrivacyPlistContent(path)
}
func declareGeneratedTBDFile(_ path: Path, forVariant variant: String) {
delegate.declareGeneratedTBDFile(path, forVariant: variant)
}
func declareGeneratedSwiftObjectiveCHeaderFile(_ path: Path, architecture: String) {
delegate.declareGeneratedSwiftObjectiveCHeaderFile(path, architecture: architecture)
}
func declareGeneratedSwiftConstMetadataFile(_ path: Path, architecture: String) {
delegate.declareGeneratedSwiftConstMetadataFile(path, architecture: architecture)
}
var additionalCodeSignInputs: OrderedSet<Path> {
return delegate.additionalCodeSignInputs
}
var buildDirectories: Set<Path> {
return delegate.buildDirectories
}
func createTask(_ builder: inout PlannedTaskBuilder) {
// Determine if this is a task which needs to attach to the generated headers node.
var producesHeader = false, mayConsumeHeader = false
for output in builder.outputs {
if [".o", ".gch"].contains(output.path.fileSuffix) {
mayConsumeHeader = true
} else if [".h", ".hpp", ".tpp", ".ipp", ".inc", ".pch"].contains(output.path.fileSuffix) {
producesHeader = true
}
}
for input in builder.inputs {
if [".c", ".m", ".mm", ".cp", ".cpp", ".cxx", ".cc"].contains(input.path.fileSuffix) {
mayConsumeHeader = true
}
}
let hasSwiftInputs = builder.inputs.contains { $0.path.fileSuffix == ".swift" }
// If this command is known to produce a header, ensure it precedes the generated headers completion node.
if producesHeader {
builder.mustPrecede.append(hasSwiftInputs ? producer.swiftGeneratedHeadersCompletionTask : producer.nonSwiftGeneratedHeadersCompletionTask)
if !builder.additionalTaskOrderingOptions.contains(.blockedByTargetHeaders) {
builder.mustPrecede.append(producer.copyHeadersCompletionTask)
}
} else if mayConsumeHeader {
// If this command might consume a header, ensure it follows the generated headers completion node.
if hasSwiftInputs {
builder.inputs.append(producer.nonSwiftGeneratedHeadersCompletionNode)
} else {
builder.inputs.append(contentsOf: [producer.nonSwiftGeneratedHeadersCompletionNode, producer.swiftGeneratedHeadersCompletionNode])
}
}
if builder.additionalTaskOrderingOptions.contains(.blockedByTargetHeaders) {
builder.inputs.append(producer.copyHeadersCompletionNode)
}
// This calls into PhasedProducerBasedTaskGenerationDelegate.createTask(), which unifies this createTask() call with those from other task producers (for example, so they all use the TaskOrderingOptions for its producer).
delegate.createTask(&builder)
if builder.additionalTaskOrderingOptions.contains(.compilationForIndexableSourceFile) {
producer.addPrepareForIndexInputs(builder.inputs)
}
}
func createGateTask(inputs: [any PlannedNode], output: any PlannedNode, name: String?, mustPrecede: [any PlannedTask], taskConfiguration: (inout PlannedTaskBuilder) -> Void) {
delegate.createGateTask(inputs: inputs, output: output, name: name, mustPrecede: mustPrecede, taskConfiguration: taskConfiguration)
}
var taskActionCreationDelegate: any TaskActionCreationDelegate {
return delegate.taskActionCreationDelegate
}
var clientDelegate: any CoreClientDelegate {
return delegate.clientDelegate
}
func createOrReuseSharedNodeWithIdentifier(_ ident: String, creator: () -> (any PlannedNode, any Sendable)) -> (any PlannedNode, any Sendable) {
return delegate.createOrReuseSharedNodeWithIdentifier(ident, creator: creator)
}
func access(path: Path) {
producer.access(path: path)
}
func readFileContents(_ path: Path) throws -> ByteString {
return try producer.readFileContents(path)
}
func fileExists(at path: Path) -> Bool {
return producer.fileExists(at: path)
}
func recordAttachment(contents: SWBUtil.ByteString) -> SWBUtil.Path {
return delegate.recordAttachment(contents: contents)
}
}
package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, FilesBasedBuildPhaseTaskProducer {
typealias ManagedBuildPhase = SourcesBuildPhase
/// A virtual node representing the (conservative) construction of all non-Swift-generated headers.
let nonSwiftGeneratedHeadersCompletionNode: any PlannedNode
/// A gate task driving the production of the non-Swift-generated headers order node.
let nonSwiftGeneratedHeadersCompletionTask: any PlannedTask
/// A virtual node representing the (conservative) construction of all Swift-generated headers.
let swiftGeneratedHeadersCompletionNode: any PlannedNode
/// A gate task driving the production of the Swift-generated headers order node.
let swiftGeneratedHeadersCompletionTask: any PlannedTask
/// A virtual node representing the completion of all tasks of a target that have (a) header file(s) as output
let copyHeadersCompletionNode: any PlannedNode
/// A gate task driving the completion of copying/generating all headers of a target
let copyHeadersCompletionTask: any PlannedTask
/// The frameworks build phase this task producer is working with. This is optional since the target might not have one.
unowned private(set) var frameworksBuildPhase: FrameworksBuildPhase?
/// During source file processing, collects the tasks necessary to 'prepare-for-index' the target.
/// This is only getting populated if `INDEX_ENABLE_BUILD_ARENA` was set to true.
var prepareTargetForIndexInputs: [any PlannedNode] = []
/// Used to efficiently check whether a `PlannedNode` has already been added to the `prepareTargetForIndexInputs` array.
private var prepareTargetForIndexInputsObjectSet: Set<ObjectIdentifier> = []
var hasEnabledIndexBuildArena: Bool { targetContext.preparedForIndexPreCompilationNode != nil }
init(_ context: TargetTaskProducerContext, sourcesBuildPhase: SourcesBuildPhase, frameworksBuildPhase: FrameworksBuildPhase?, phaseStartNodes: [any PlannedNode], phaseEndNode: any PlannedNode, phaseEndTask: any PlannedTask) {
self.frameworksBuildPhase = frameworksBuildPhase
// Create a task to serve as the gate to order commands which may produce headers versus those that may consume them.
//
// We force this node to precede the target end task just to avoid it counting as a root in the graph.
//
// FIXME: We need a good way to make a unique -- but stable -- node name here, even across configured targets.
nonSwiftGeneratedHeadersCompletionNode = context.createVirtualNode("\(context.configuredTarget!.guid)-generated-headers")
nonSwiftGeneratedHeadersCompletionTask = context.createGateTask(output: nonSwiftGeneratedHeadersCompletionNode, mustPrecede: [context.targetEndTask])
swiftGeneratedHeadersCompletionNode = context.createVirtualNode("\(context.configuredTarget!.guid)-swift-generated-headers")
swiftGeneratedHeadersCompletionTask = context.createGateTask(output: swiftGeneratedHeadersCompletionNode, mustPrecede: [context.targetEndTask])
copyHeadersCompletionNode = context.createVirtualNode("\(context.configuredTarget!.guid)-copy-headers-completion")
copyHeadersCompletionTask = context.createGateTask(output: copyHeadersCompletionNode, mustPrecede: [context.targetEndTask])
super.init(context, buildPhase: sourcesBuildPhase, phaseStartNodes: phaseStartNodes, phaseEndNode: phaseEndNode, phaseEndTask: phaseEndTask)
}
override func additionalBuildFiles(_ scope: MacroEvaluationScope) async -> [BuildFile] {
// Some files might generate sources (e.g. generating symbols) and thus need to be in the Sources phase.
let standardTarget = targetContext.configuredTarget?.target as? StandardTarget
let sourceFiles = standardTarget?.sourcesBuildPhase?.buildFiles ?? []
let resourceFiles = standardTarget?.resourcesBuildPhase?.buildFiles ?? []
guard !sourceFiles.isEmpty && !resourceFiles.isEmpty else {
return []
}
return await sourceGenerationInputFiles(from: resourceFiles, scope: scope, hasSourcesPhase: true)
}
override func additionalFilesToBuild(_ scope: MacroEvaluationScope) -> [FileToBuild] {
var additionalFilesToBuild: [FileToBuild] = []
let sourceFiles = (self.targetContext.configuredTarget?.target as? StandardTarget)?.sourcesBuildPhase?.buildFiles.count ?? 0
if sourceFiles > 0 && scope.evaluate(BuiltinMacros.APP_PLAYGROUND_GENERATE_ASSET_CATALOG) {
// Add the generated xcassets since we'll be handling the other xcassets here as well.
let assetCatalogToBeGenerated = scope.evaluate(BuiltinMacros.APP_PLAYGROUND_GENERATED_ASSET_CATALOG_FILE)
additionalFilesToBuild.append(
FileToBuild(absolutePath: assetCatalogToBeGenerated, inferringTypeUsing: context)
)
}
return additionalFilesToBuild
}
/// The default `TaskOrderingOptions` are used for the compile tasks in the architecture-variant loop, as the default options are primarily concerned with compilation. Other tasks set up by this producer use the `nonCompilationTaskOrderingOptions` below. But be mindful of which options are being used when adding new tasks to this producer.
///
/// The rule of thumb is that using the default options imposes _more_ ordering constraints on a task.
package override var defaultTaskOrderingOptions: TaskOrderingOptions {
let scope = self.context.settings.globalScope
var options = [TaskOrderingOptions]()
// This producer's tasks both require that its own modules be ready, and the modules of targets it depends on be ready, before they can run. (.compilation)
if scope.evaluate(BuiltinMacros.EAGER_PARALLEL_COMPILATION_DISABLE) {
// If eager parallel compilation is off, they must also block downstream compiling targets because Swift compilation generates a module. (.compilationRequirement)
options.append(.compilation)
options.append(.compilationRequirement)
} else {
// Swift will still use .compilationRequirement because it's compiling tasks are generating a module.
options.append(.compilation)
}
return TaskOrderingOptions(options).union(nonCompilationTaskOrderingOptions)
}
/// Tasks which don't care about being ordered as compile tasks should usually use these options when calling `appendGeneratedTasks()`. This will order tasks before the unsigned-products-ready gate task, which means that hosted targets (e.g., test targets using `TEST_HOST`) can rely on them being present.
///
/// Note that tasks which genuinely don't care about this ordering (e.g., because nothing downstream depends on them) can have `appendGeneratedTasks()` passed no options.
var nonCompilationTaskOrderingOptions: TaskOrderingOptions {
return .unsignedProductRequirement
}
/// Convenience function for appending tasks using from a spec.
///
/// We override this to auto-attach tasks to the generated headers completion ordering gate.
@discardableResult
package override func appendGeneratedTasks( _ tasks: inout [any PlannedTask], options: TaskOrderingOptions? = nil, staleFileRemovalScope: StaleFileRemovalScope = .target, body: (any TaskGenerationDelegate) async -> Void) async -> (tasks: [any PlannedTask], outputs: [FileToBuild]) {
return await super.appendGeneratedTasks(&tasks, options: options, staleFileRemovalScope: staleFileRemovalScope) { delegate in
await body(SourcesPhaseBasedTaskGenerationDelegate(producer: self, userPreferences: context.workspaceContext.userPreferences, delegate: delegate))
}
}
/// Returns `true` if the target which defines the settings in the given `scope` should generate a dSYM file.
/// - remark: This method allows this task producer to ask this question about other targets by passing a `scope` for the target in question.
private func shouldGenerateDSYM(_ scope: MacroEvaluationScope) -> Bool {
guard scope.evaluate(BuiltinMacros.PLATFORM_USES_DSYMS) else {
return false
}
let dSYMForDebugInfo = scope.evaluate(BuiltinMacros.GCC_GENERATE_DEBUGGING_SYMBOLS) && scope.evaluate(BuiltinMacros.DEBUG_INFORMATION_FORMAT) == "dwarf-with-dsym"
// When emitting remarks, for now, a dSYM is required (<rdar://problem/45458590>)
let dSYMForRemarks = scope.evaluate(BuiltinMacros.CLANG_GENERATE_OPTIMIZATION_REMARKS)
let dSYM = dSYMForDebugInfo || dSYMForRemarks
return dSYM && !["staticlib", "mh_object", "objectlib"].contains(scope.evaluate(BuiltinMacros.MACH_O_TYPE))
}
/// Computes and returns a list of libraries to include when linking.
func computeLibraries(_ buildFilesContext: BuildFilesProcessingContext, _ scope: MacroEvaluationScope, allowSearchPaths: Bool) async -> [LinkerSpec.LibrarySpecifier] {
guard let frameworksPhase = frameworksBuildPhase else { return [] }
// Compute the flattened list of build files after expanding package product targets.
let buildFiles = context.computeFlattenedFrameworksPhaseBuildFiles(buildFilesContext)
// Add libraries specifiers for each item in the phase.
//
// FIXME: Xcode uses the filtered references here, but our implementation isn't yet factored in a way we can do that.
var librarySpecifiers: [LinkerSpec.LibrarySpecifier] = []
for buildFile in buildFiles {
// Resolve the buildable reference.
let (_, settingsForRef, absolutePath, fileType): (Reference, Settings?, Path, FileTypeSpec)
switch buildFile.buildableItem {
case .reference, .targetProduct:
do {
(_, settingsForRef, absolutePath, fileType) = try self.context.resolveBuildFileReference(buildFile)
} catch WorkspaceErrors.missingPackageProduct(let packageName) {
context.missingPackageProduct(packageName, buildFile, frameworksPhase)
continue
} catch {
context.error("Unable to resolve build file: \(buildFile) (\(error))")
continue
}
case .namedReference(let name, let fileTypeIdentifier):
settingsForRef = nil
absolutePath = Path(name) // This path isn't actually absolute, but `LinkerSpec.LibrarySpecifier` supports that case.
if let type = context.lookupFileType(identifier: fileTypeIdentifier) {
fileType = type
} else {
context.error("Could not lookup file type '\(fileTypeIdentifier)'")
continue
}
}
// Link using search paths unless the reference is in a different project, in which case we use a full path (to support legacy build locations, primarily).
let useSearchPaths: Bool
if allowSearchPaths {
switch buildFile.buildableItem {
case .reference:
useSearchPaths = true
case .targetProduct(guid: let guid):
if let referenceTarget = self.context.workspaceContext.workspace.target(for: guid) {
let referenceProject = self.context.workspaceContext.workspace.project(for: referenceTarget)
useSearchPaths = referenceProject === self.context.project
} else {
useSearchPaths = true
}
case .namedReference:
useSearchPaths = true
}
} else {
useSearchPaths = false
}
/// If this `BuildFile` is being produced by a target, capture the effective `Settings` for it. `TaskProducerContext.settingsForProductReferenceTarget()` will resolve this to the actual settings for the `ConfiguredTarget` in the target dependency graph (as opposed to an older approach of applying the current configured target's parameters to the other target, which won't work if there are context-dependent overrides).
var producingTargetSettings: Settings? = nil
if let configuredTarget = self.context.configuredTarget, let producingTarget = context.globalProductPlan.planRequest.buildGraph.producingTarget(for: buildFile.buildableItem, in: configuredTarget)?.target.target {
let parameters = configuredTarget.parameters
let settings = self.context.settingsForProductReferenceTarget(producingTarget, parameters: parameters)
producingTargetSettings = settings
}
// For each target product reference, find the corresponding target and if:
// - it uses Swift
// - it is built from source
// - it _does not_ build using explicit modules or the compiler doesn't support explicit module based dependency info
// determine the path to the corresponding .swiftmodule file per arch, needed for debugging.
var isKnownToUseSwift = false
var swiftModulePaths: [String: Path] = [:]
var swiftModuleAdditionalLinkerArgResponseFilePaths: [String: Path] = [:]
switch buildFile.buildableItem {
case .reference:
break
case .targetProduct(guid: let guid):
if let referenceTarget = self.context.workspaceContext.workspace.dynamicTarget(for: guid, dynamicallyBuildingTargets: context.globalProductPlan.dynamicallyBuildingTargets) {
let parameters = self.context.configuredTarget?.parameters ?? BuildParameters(configuration: nil)
let settings = self.context.settingsForProductReferenceTarget(referenceTarget, parameters: parameters)
if referenceTarget.usesSwift(context: self.context, settings: settings) {
isKnownToUseSwift = true
var architectures = scope.evaluate(BuiltinMacros.ARCHS)
var processedArchitectures: Set<String> = []
while !architectures.isEmpty {
let originalArch = architectures.removeFirst()
guard !processedArchitectures.contains(originalArch) else {
continue
}
processedArchitectures.insert(originalArch)
let scope = settings.globalScope.subscopeBindingArchAndTriple(arch: originalArch)
let toolInfo = await context.swiftCompilerSpec.discoveredCommandLineToolSpecInfo(context, scope, context.globalProductPlan.delegate)
if await self.context.swiftCompilerSpec.swiftExplicitModuleBuildEnabled(self.context, scope, self.context.globalProductPlan.delegate) && toolInfo?.hasFeature(DiscoveredSwiftCompilerToolSpecInfo.FeatureFlag.debugInfoExplicitDependency.rawValue) == true {
// When building with explicit modules, the driver/frontend will use -debug-module-path to record the information
// necessary to find swiftmodules when debugging. Skip the redundant linker swiftmodule registration here.
continue
}
// Binding the architecture may change how we evaluate $(ARCHS). Ensure we process any new architectures.
for potentiallyNewArch in scope.evaluate(BuiltinMacros.ARCHS) {
if !processedArchitectures.contains(potentiallyNewArch) {
architectures.append(potentiallyNewArch)
}
}
// Recompute arch as it will be interpolated in settings below, in case binding the architecture condition produced a value other than the original literal.
let arch = scope.evaluate(BuiltinMacros.CURRENT_ARCH)
if arch == "undefined_arch" {
// If we end up with an undefined architecture here, the settings we use to compute the
// AST path and additional linker args below will be incorrect. This state is undesirable,
// but recoverable, so do not record these incorrect paths.
// FIXME: The `settingsForProductReferenceTarget` call above should always find settings for a
// concrete configured target. However, there appears to be an issue currently when a root-level target
// with package dependencies uses non-standard architectures. We should remove the `settingsForProductReferenceTarget` once that issue is resolved, and then this check should no longer
// be needed.
if context.workspaceContext.userPreferences.enableDebugActivityLogs {
context.warning("Could not determine settings for \(referenceTarget.name) when computing Swift AST paths for \(context.configuredTarget?.target.name ?? "<unknown>")")
}
continue
}
let moduleName = scope.evaluate(BuiltinMacros.SWIFT_MODULE_NAME)
let moduleFileDir = scope.evaluate(BuiltinMacros.PER_ARCH_MODULE_FILE_DIR)
swiftModulePaths[arch] = moduleFileDir.join(moduleName + ".swiftmodule")
if await self.context.swiftCompilerSpec.swiftShouldGenerateAdditionalLinkerArgsResponseFile(self.context, scope, self.context.globalProductPlan.delegate) {
swiftModuleAdditionalLinkerArgResponseFilePaths[arch] = moduleFileDir.join("\(moduleName)-linker-args.resp")
}
}
// Check if we can fill in information for missing architectures using a compatible architecture.
for (arch, moduleFileDir) in swiftModulePaths {
if let archSpec = context.workspaceContext.core.specRegistry.getSpec(arch, domain: scope.evaluate(BuiltinMacros.PLATFORM_NAME)) as? ArchitectureSpec {
for compatibilityArch in archSpec.compatibilityArchs {
if swiftModulePaths[compatibilityArch] == nil {
swiftModulePaths[compatibilityArch] = moduleFileDir
}
}
}
}
for (arch, moduleFileDir) in swiftModuleAdditionalLinkerArgResponseFilePaths {
if let archSpec = context.workspaceContext.core.specRegistry.getSpec(arch, domain: scope.evaluate(BuiltinMacros.PLATFORM_NAME)) as? ArchitectureSpec {
for compatibilityArch in archSpec.compatibilityArchs {
if swiftModuleAdditionalLinkerArgResponseFilePaths[compatibilityArch] == nil {
swiftModuleAdditionalLinkerArgResponseFilePaths[compatibilityArch] = moduleFileDir
}
}
}
}
}
}
case .namedReference:
break
}
// If the target is set to aggregate tracked domains, then the path of the dependencies needs to be noted so that it can be scanned.
let privacyFile: Path? = {
if scope.evaluate(BuiltinMacros.AGGREGATE_TRACKED_DOMAINS) {
switch buildFile.buildableItem {
case .reference(_), .targetProduct(_):
do {
let (_, absolutePath, fileType) = try self.context.resolveBuildFileReference(buildFile)
// Currently only items that are embeddable are supported for scanning.
if fileType.isEmbeddableInProduct {
return absolutePath
}
}
catch {}
default:
return nil
}
}
return nil
}()
func linkageModeForDylib() -> LinkerSpec.LibrarySpecifier.Mode {
if scope.evaluate(BuiltinMacros.MERGE_LINKED_LIBRARIES) {
// Right now, we only use reexport_merge and merge modes if the BuildFile was produced by a target and that target is a mergeable library. This may change in the future as we add more scenarios.
if let settings = producingTargetSettings {
// Every mergeable framework or library which was built by a target which we're linking in this manner should be either reexported-merged or normally merged (because otherwise they wouldn't be a mergeable library).
if settings.globalScope.evaluate(BuiltinMacros.MERGEABLE_LIBRARY) {
return settings.globalScope.evaluate(BuiltinMacros.MAKE_MERGEABLE) ? .merge : .reexport_merge
}
}
}
// In all other cases then we treat it normally.
return buildFile.shouldLinkWeakly ? .weak : .normal
}
if fileType.conformsTo(context.lookupFileType(identifier: "archive.ar")!) {
let mode: LinkerSpec.LibrarySpecifier.Mode
if buildFile.shouldLinkWeakly {
mode = .weak
} else if context.productType?.conformsTo(identifier: "com.apple.product-type.tool.swiftpm-test-runner") == true {
// Test runners should force load all archives they depend on to ensure they never drop test implementations.
mode = .wholeArchive
} else {
mode = .normal
}
librarySpecifiers.append(LinkerSpec.LibrarySpecifier(
kind: .static,
path: absolutePath,
mode: mode,
useSearchPaths: useSearchPaths,
isKnownToUseSwift: isKnownToUseSwift,
swiftModulePaths: swiftModulePaths,
swiftModuleAdditionalLinkerArgResponseFilePaths: swiftModuleAdditionalLinkerArgResponseFilePaths,
prefix: fileType.prefix,
privacyFile: privacyFile
))
} else if fileType.conformsTo(context.lookupFileType(identifier: "compiled.mach-o.dylib")!) {
let adjustedAbsolutePath: Path
// On Windows, ensure import libraries (.lib) are used instead of DLLs.
if context.sdkVariant?.llvmTargetTripleSys == "windows" && absolutePath.fileSuffix.lowercased() == ".dll" {
adjustedAbsolutePath = Path(absolutePath.withoutSuffix + ".lib")
} else {
adjustedAbsolutePath = absolutePath
}
librarySpecifiers.append(LinkerSpec.LibrarySpecifier(
kind: .dynamic,
path: adjustedAbsolutePath,
mode: linkageModeForDylib(),
useSearchPaths: useSearchPaths,
isKnownToUseSwift: isKnownToUseSwift,
swiftModulePaths: [:],
swiftModuleAdditionalLinkerArgResponseFilePaths: [:],
prefix: fileType.prefix,
privacyFile: privacyFile
))
} else if fileType.conformsTo(context.lookupFileType(identifier: "sourcecode.text-based-dylib-definition")!) {
librarySpecifiers.append(LinkerSpec.LibrarySpecifier(
kind: .textBased,
path: absolutePath,
mode: buildFile.shouldLinkWeakly ? .weak : .normal,
useSearchPaths: useSearchPaths,
isKnownToUseSwift: isKnownToUseSwift,
swiftModulePaths: [:],
swiftModuleAdditionalLinkerArgResponseFilePaths: [:],
prefix: fileType.prefix,
privacyFile: privacyFile
))
} else if fileType.conformsTo(context.lookupFileType(identifier: "wrapper.framework")!) {
func kindFromSettings(_ settings: Settings) -> (kind: LinkerSpec.LibrarySpecifier.Kind, prefix: String?)? {
switch settings.globalScope.evaluate(BuiltinMacros.MACH_O_TYPE) {
case "staticlib":
return (.static, context.lookupFileType(identifier: "archive.ar")?.prefix)
case "mh_dylib":
return (.dynamic, context.lookupFileType(identifier: "compiled.mach-o.dylib")?.prefix)
case "mh_object":
return (.object, nil)
default:
return nil
}
}
let kind: LinkerSpec.LibrarySpecifier.Kind
let path: Path
let dsymPath: Path?
let topLevelItemPath: Path?
let prefix: String?
if let settingsForRef, let presumedKind = kindFromSettings(settingsForRef), !useSearchPaths {
// If we have a Settings from a cross-project reference, use the _actual_ library path. This prevents downstream code from reconstituting the framework path by joining the framework path with the basename of the framework, which won't be correct for deep frameworks which also need the Versions/A path component.
kind = presumedKind.kind
prefix = presumedKind.prefix
path = settingsForRef.globalScope.evaluate(BuiltinMacros.TARGET_BUILD_DIR).join(settingsForRef.globalScope.evaluate(BuiltinMacros.EXECUTABLE_PATH)).normalize()
topLevelItemPath = absolutePath
if shouldGenerateDSYM(settingsForRef.globalScope) {
dsymPath = scope.evaluate(BuiltinMacros.DWARF_DSYM_FOLDER_PATH).join(scope.evaluate(BuiltinMacros.DWARF_DSYM_FILE_NAME))
}
else {
dsymPath = nil
}
} else {
kind = .framework
path = absolutePath
topLevelItemPath = nil
dsymPath = nil
prefix = nil
}
librarySpecifiers.append(LinkerSpec.LibrarySpecifier(
kind: kind,
path: path,
mode: linkageModeForDylib(),
useSearchPaths: useSearchPaths,
isKnownToUseSwift: isKnownToUseSwift,
swiftModulePaths: [:],
swiftModuleAdditionalLinkerArgResponseFilePaths: [:],
prefix: prefix,
topLevelItemPath: topLevelItemPath,
dsymPath: dsymPath,
privacyFile: privacyFile
))
} else if fileType.conformsTo(context.lookupFileType(identifier: "compiled.mach-o.objfile")!) {
librarySpecifiers.append(LinkerSpec.LibrarySpecifier(
kind: .object,
path: absolutePath,
mode: buildFile.shouldLinkWeakly ? .weak : .normal,
useSearchPaths: false,
isKnownToUseSwift: isKnownToUseSwift,
swiftModulePaths: swiftModulePaths,
swiftModuleAdditionalLinkerArgResponseFilePaths: swiftModuleAdditionalLinkerArgResponseFilePaths,
privacyFile: privacyFile
))
} else if fileType.conformsTo(context.lookupFileType(identifier: "wrapper.xcframework")!) {
// The XCFramework needs to be inspected here to determine what type of linkage is actually going to be done. This is more work than I'd like to do here, but there isn't really an alternative.
guard let xcframeworkPath = (try? context.resolveBuildFileReference(buildFile))?.absolutePath else {
// Even if a suitable library cannot be found for the build flavor being asked for, the actual reference to the xcframework should always be found. This is an internal model error if this occurs.
assertionFailure("unable to get the xcframeworkPath")
continue
}
guard let xcframework = try? context.globalProductPlan.planRequest.buildRequestContext.getCachedXCFramework(at: xcframeworkPath) else {
// Let the XCFrameworkTaskProducer log an errors here as this should only occur when an XCFramework is referenced on disk but is not actually present.
continue
}
guard let library = xcframework.findLibrary(sdk: context.sdk, sdkVariant: context.sdkVariant, architectures: scope.evaluate(BuiltinMacros.ARCHS)) else {
// Let the XCFrameworkTaskProducer log an error here
continue
}
guard let target = context.configuredTarget, let outputFilePaths = context.globalProductPlan.xcframeworkContext.outputFiles(xcframeworkPath: xcframeworkPath, target: target) else {
// Let the XCFrameworkTaskProducer log an error here
continue
}
let outputPath = XCFramework.computeOutputDirectory(scope)
let libraryTargetPath = outputPath.join(library.libraryPath)
// Check if the architectures provided by the xcframework have at least all of the ones we're building for, but only emit a note if this isn't the case since it may still be link-compatible. In future this could be made an error if we can confidently replicate the linker's rules on what architectures are link-compatible (CompatibilityArchitectures is NOT necessarily 1:1 with those rules).
let archs = scope.evaluate(BuiltinMacros.ARCHS)
let missingArchs = Set(archs).subtracting(library.supportedArchitectures)
if !missingArchs.isEmpty {
context.note("'\(xcframeworkPath.str)' is missing architecture(s) required by this target (\(missingArchs.joined(separator: ", "))), but may still be link-compatible.")
}
let libraryKind: LinkerSpec.LibrarySpecifier.Kind
let prefix: String?
switch library.libraryType {
case .framework: libraryKind = .framework; prefix = nil
case .dynamicLibrary:
libraryKind = .dynamic;
prefix = context.lookupFileType(identifier: "compiled.mach-o.dylib")?.prefix
case .staticLibrary:
libraryKind = .static
prefix = context.lookupFileType(identifier: "archive.ar")?.prefix
break
case let .unknown(fileExtension):
// An error of type this type should have already been manifested.
assertionFailure("unknown xcframework type: \(fileExtension)")
continue
}
let mode: LinkerSpec.LibrarySpecifier.Mode = {
// If we're merging libraries, and this XCFramework library has been built as mergeable, then we merge or reexport it based on IS_UNOPTIMIZED_BUILD.
// This means there's to way to force a merge in debug builds, but it's not clear whether that is desirable behavior anyway.
if scope.evaluate(BuiltinMacros.MERGE_LINKED_LIBRARIES) {
if library.mergeableMetadata {
return scope.evaluate(BuiltinMacros.IS_UNOPTIMIZED_BUILD) ? .reexport_merge : .merge
}
}
// In all other cases then we treat it normally.
return buildFile.shouldLinkWeakly ? .weak : .normal
}()
// NOTE: XCFrameworks are not currently being searched for the `PrivacyInfo.xcprivacy` files, instead, they searched by the inclusion of the item within the "Copy Phase".
librarySpecifiers.append(LinkerSpec.LibrarySpecifier(
kind: libraryKind,
path: libraryTargetPath,
mode: mode,
useSearchPaths: useSearchPaths,
isKnownToUseSwift: isKnownToUseSwift,
swiftModulePaths: [:],
swiftModuleAdditionalLinkerArgResponseFilePaths: [:],
prefix: prefix,
explicitDependencies: outputFilePaths,
xcframeworkSourcePath: xcframeworkPath,
privacyFile: nil
))
} else if fileType.conformsTo(identifier: "wrapper.artifactbundle") {
let metadata: ArtifactBundleMetadata
do {
metadata = try context.globalProductPlan.artifactBundleMetadataCache.getOrInsert(absolutePath) {
try ArtifactBundleMetadata.parse(at: absolutePath, fileSystem: context.fs)
}
} catch {
context.error("failed to parse artifact bundle metadata for '\(absolutePath)': \(error.localizedDescription)")
continue
}
for (name, artifact) in metadata.artifacts {
switch artifact.type {
case .crossCompilationDestination, .swiftSDK:
context.warning("ignoring artifact '\(name)' of type '\(artifact.type)' because it cannot be linked", location: .path(absolutePath))
continue
case .executable, .experimentalWindowsDLL:
// Just ignore, these are used by SwiftPM
continue
case .staticLibrary:
var foundMatch = false
let currentTripleString = scope.evaluate(BuiltinMacros.SWIFT_TARGET_TRIPLE)
for variant in artifact.variants {
if variant.supportedTriples == nil || variant.supportedTriples?.contains(where: {
normalizedTriplesCompareDisregardingOSVersions($0, currentTripleString)
}) == true {
foundMatch = true
librarySpecifiers.append(LinkerSpec.LibrarySpecifier(
kind: .static,
path: absolutePath.join(variant.path),
mode: .normal,
useSearchPaths: false,
isKnownToUseSwift: isKnownToUseSwift,
swiftModulePaths: [:],
swiftModuleAdditionalLinkerArgResponseFilePaths: [:],
privacyFile: privacyFile
))
}
}
if !foundMatch {
context.warning("ignoring '\(name)' because the artifact bundle did not contain a matching variant", location: .path(absolutePath))
}
}
}
continue
} else if fileType.conformsTo(identifier: "compiled.object-library") {
librarySpecifiers.append(LinkerSpec.LibrarySpecifier(
kind: .objectLibrary,
path: absolutePath,
mode: .normal,
useSearchPaths: false,
isKnownToUseSwift: isKnownToUseSwift,
swiftModulePaths: swiftModulePaths,
swiftModuleAdditionalLinkerArgResponseFilePaths: swiftModuleAdditionalLinkerArgResponseFilePaths,
explicitDependencies: [absolutePath],
))
} else {
// FIXME: Error handling.
continue
}
}
return librarySpecifiers
}
/// Record the inputs to 'prepare-for-index' target node, that were not already recorded so far.
func addPrepareForIndexInputs(_ inputs: [any PlannedNode]) {
guard hasEnabledIndexBuildArena else { return }
let newInputs = inputs.filter { !prepareTargetForIndexInputsObjectSet.contains(ObjectIdentifier($0)) }
prepareTargetForIndexInputs.append(contentsOf: newInputs)
prepareTargetForIndexInputsObjectSet.formUnion(newInputs.map{ ObjectIdentifier($0) })
}
package func prepare() {
// CodeSigning in a monster... really, it is. Further, we don't actually have a model where we can perform proper pre-planning to determine what inputs will cause downstream outputs to end up in a particular location. Every source file has the potential to contribute some type of output that ends up in the wrapper.
// For example, every metal file creates a library that is embedded into the product wrapper. However, the build system doesn't actually *know* that, as the outputs aren't really tracked in a way that the CodeSign task itself can depend on it.
// What the build system does _know_, is that _all_ source files run tools that can potentially end up invalidating the code signature. Until we have a proper pre-planning (e.g. or dry-run) model (or a model that allows us to have dependencies on task producers), we need to be more conservative in our tracking of inputs, even if they can result in additional codesign work.
// An additional note: if out output file, such as a metallib, is changed by something other than a source input, the codesign task will still not know about it, and that incremental build will fail to sign properly. That should be less likely to happen, but just goes to point out that we need to re-work how our codesign model works, in general.
let scope = context.settings.globalScope
// This is to provide a fallback to prevent any unexpected side-effects; this is for consistency with other codesign tracking...
guard scope.evaluate(BuiltinMacros.ENABLE_ADDITIONAL_CODESIGN_INPUT_TRACKING) else { return }
// If we're not building a wrapper, then just bail.
guard context.settings.productType?.isWrapper == true else { return }
// Add all source files as they can contribute output into the wrapper: see comment above for further details.
context.addAdditionalCodeSignInputs(buildPhase.buildFiles, context)
}
/// Compute a flattened list of build files to use
package func generateTasks() async -> [any PlannedTask] {
// NOTE: The sources build phase uses its own generateTasks() to deal with the per-variant and per-arch nature.
let scope = context.settings.globalScope
// Sources are processed only when the "build", "api", or "headers" component is present.
//
// FIXME: Actually, we can limit the "API" part to just when Swift API is installed: <rdar://problem/33735618> [InstallAPI] Avoid headermap / sources task construction when Swift isn't present
var isForAPI = false
var isForHeaders = false
var isForInstallLoc = false
var isForExportLoc = false
let components = scope.evaluate(BuiltinMacros.BUILD_COMPONENTS)
if !components.contains("build") {
if components.contains("api"), context.settings.allowInstallAPIForTargetsSkippedInInstall(in: scope) {
isForAPI = true
}
// <rdar://problem/59862065> Remove EXPERIMENTAL_ALLOW_INSTALL_HEADERS_FILTERING after validation.
if components.contains("headers"), context.settings.allowInstallAPIForTargetsSkippedInInstall(in: scope), (components.contains("api") || scope.evaluate(BuiltinMacros.EXPERIMENTAL_ALLOW_INSTALL_HEADERS_FILTERING)) {
isForHeaders = true
}
if components.contains("installLoc") {
isForInstallLoc = true
}
// The exportLoc build action is only for extracting localizations from .swift files
if components.contains("exportLoc") {
isForExportLoc = true
}
guard isForAPI || isForHeaders || isForInstallLoc || isForExportLoc else {
return []
}
}
var tasks: [any PlannedTask] = []
// Generate any auxiliary files whose content is not per-arch or per-variant.
// For the index build arena it is important to avoid adding this because it forces creation of the Swift module due to the generated ObjC header being an input dependency. This is unnecessary work since we don't need to generate the Swift module of the target to be able to successfully create a compiler AST for the Swift files of the target.
let generateVersionInfoFileTask = await (isForAPI || hasEnabledIndexBuildArena) ? nil : generateVersionInfoFile(scope)
if let generateVersionInfoFileTask {
tasks.append(generateVersionInfoFileTask)
}
let generateKernelExtensionModuleInfoFileTask = await (isForAPI || hasEnabledIndexBuildArena) ? nil : self.generateKernelExtensionModuleInfoFileTask(scope, buildPhase)
if let generateKernelExtensionModuleInfoFileTask {
tasks.append(generateKernelExtensionModuleInfoFileTask)
}
let packageTargetBundleAccessorResults = await generatePackageTargetBundleAccessorResult(scope)
for result in packageTargetBundleAccessorResults {
tasks += result.tasks
}
let bundleLookupHelperResult = await generateBundleLookupHelper(scope)
tasks += bundleLookupHelperResult?.tasks ?? []
let testAnchorResult = await generateTestAnchor(scope)
tasks += testAnchorResult?.tasks ?? []
let embedInCodeAccessorResult: GeneratedSourceCodeResult?
if scope.evaluate(BuiltinMacros.GENERATE_EMBED_IN_CODE_ACCESSORS), let configuredTarget = context.configuredTarget, buildPhase.containsSwiftSources(context.workspaceContext.workspace, context, scope, context.filePathResolver) {
let ownTargetBuildFilesToEmbed = ((context.workspaceContext.workspace.target(for: configuredTarget.target.guid) as? StandardTarget)?.buildPhases.compactMap { $0 as? BuildPhaseWithBuildFiles }.flatMap { $0.buildFiles }.filter { $0.resourceRule == .embedInCode }) ?? []
let bundleDependencies = configuredTarget.target.dependencies.map { $0.guid }.compactMap { context.workspaceContext.workspace.target(for: $0) as? StandardTarget }.filter {
let settings = context.globalProductPlan.planRequest.buildRequestContext.getCachedSettings(configuredTarget.parameters, target: $0)
return settings.globalScope.evaluate(BuiltinMacros.PRODUCT_TYPE) == "com.apple.product-type.bundle"
}
let buildFilesToEmbed = ownTargetBuildFilesToEmbed + bundleDependencies.compactMap { $0.buildPhases.only as? BuildPhaseWithBuildFiles }.flatMap { $0.buildFiles }.filter { $0.resourceRule == .embedInCode }
do {
embedInCodeAccessorResult = try await generateEmbedInCodeAccessorResult(scope, resourceBuildFiles: buildFilesToEmbed)
tasks += embedInCodeAccessorResult?.tasks ?? []
} catch {
embedInCodeAccessorResult = nil
context.error("failed to generate embed-in-code accessor: \(error)")
}
} else {
embedInCodeAccessorResult = nil
}
// Add the generated headers completion gate task.
tasks.append(nonSwiftGeneratedHeadersCompletionTask)
tasks.append(swiftGeneratedHeadersCompletionTask)
tasks.append(copyHeadersCompletionTask)
let archs: [String] = scope.evaluate(BuiltinMacros.ARCHS)
let baseArchs: [String] = scope.evaluate(BuiltinMacros.ARCHS_BASE)
let moduleOnlyArchs: [String] = scope.evaluate(BuiltinMacros.SWIFT_MODULE_ONLY_ARCHS)
let targetBuildDir = scope.evaluate(BuiltinMacros.TARGET_BUILD_DIR)
// Generate tasks.
let buildVariants = scope.evaluate(BuiltinMacros.BUILD_VARIANTS)
var dsymBundle: Path!
var dsymutilOutputs = [Path]()
var allLinkedLibraries = [LinkerSpec.LibrarySpecifier]()
for variant in buildVariants {
// Enter the per-variant scope.
let scope = scope.subscope(binding: BuiltinMacros.variantCondition, to: variant)
// Create a virtual node to represent the final linked binary, needed to enforce ordering w.r.t. dsymutil.
let binaryOutput = targetBuildDir.join(scope.evaluate(BuiltinMacros.EXECUTABLE_PATH))
let linkedBinaryNode = context.createVirtualNode("Linked Binary \(binaryOutput.str)")
var dsymutilInputNodes: [any PlannedNode] = []
let executablePreviewDylibPathString = scope.evaluate(BuiltinMacros.EXECUTABLE_DEBUG_DYLIB_PATH)
let binaryPreviewDylibOutput = executablePreviewDylibPathString.isEmpty ? nil : targetBuildDir.join(executablePreviewDylibPathString)
let linkedBinaryPreviewDylibNode = binaryPreviewDylibOutput.map { context.createVirtualNode("Linked Binary Debug Dylib \($0.str)") }
let executablePreviewBlankInjectionDylibPathString = scope.evaluate(BuiltinMacros.EXECUTABLE_BLANK_INJECTION_DYLIB_PATH)
let binaryPreviewBlankInjectionDylibOutput = executablePreviewBlankInjectionDylibPathString.isEmpty ? nil : targetBuildDir.join(executablePreviewBlankInjectionDylibPathString).normalize()
let linkedBinaryPreviewBlankInjectionDylibNode = binaryPreviewBlankInjectionDylibOutput.map { context.createVirtualNode("Linked Binary Preview Injection Dylib \($0.str)") }
assert(
(linkedBinaryPreviewDylibNode == nil && linkedBinaryPreviewBlankInjectionDylibNode == nil)
|| (linkedBinaryPreviewDylibNode != nil && linkedBinaryPreviewBlankInjectionDylibNode != nil),
"A debug dylib and blank injection dylib are either both present or absent."
)
// Process all the archs.
let preferredArch = context.settings.preferredArch
// Record the individual binaries we create. These may be thin (single-arch), or they might be fat (produced using the cohort arch support).
var perArchBinaries = [Path]()
var perArchPreviewDylibBinaries = [Path]()
var perArchInjectionDylibBinaries = [Path]()
// Tracks whether a TBD used for eager linking must be processed by lipo or copied to the build products so downstream targets can link against it.
var shouldPrepareEagerLinkingTBD = false
// We only iterate over the "base" architectures (solo archs and the archs which are the base ones in a cohort), not the other archs in the cohorts.
// It's not clear that any tool can usefully run on a per-arch basis for a cohort arch if its output can't be compiled or otherwise used, since we don't create any compile or link tasks for those archs.
for arch in baseArchs {
// Enter the per-arch scope.
let scope = scope.subscopeBindingArchAndTriple(arch: arch)
let currentArchSpec = context.getSpec(arch) as! ArchitectureSpec?
// Reset the set of used tools.
usedTools.removeAll(keepingCapacity: true)
// Process all of the groups.
//
// FIXME: We should do this in parallel.
let buildFilesContext = BuildFilesProcessingContext(scope, belongsToPreferredArch: preferredArch == nil || preferredArch == arch, currentArchSpec: currentArchSpec)
var perArchTasks: [any PlannedTask] = []
await groupAndAddTasksForFiles(self, buildFilesContext, scope, filterToAPIRules: isForAPI, filterToHeaderRules: isForHeaders, &perArchTasks, extraResolvedBuildFiles: {
var result: [(Path, FileTypeSpec, Bool)] = []
if let generateVersionInfoFileTask {
result.append((generateVersionInfoFileTask.outputs.first!.path, context.lookupFileType(languageDialect: .c)!, /* shouldUsePrefixHeader */ false))
}
if let generateKernelExtensionModuleInfoFileTask {
result.append((generateKernelExtensionModuleInfoFileTask.outputs.first!.path, context.lookupFileType(languageDialect: .c)!, /* shouldUsePrefixHeader */ false))
}
for packageTargetBundleAccessorResult in packageTargetBundleAccessorResults {
result.append((packageTargetBundleAccessorResult.fileToBuild, packageTargetBundleAccessorResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false))
}
if let bundleLookupHelperResult {
result.append((bundleLookupHelperResult.fileToBuild, bundleLookupHelperResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false))
}
if let embedInCodeAccessorResult {
result.append((embedInCodeAccessorResult.fileToBuild, embedInCodeAccessorResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false))
}
if let testAnchorResult {
result.append((testAnchorResult.fileToBuild, testAnchorResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false))
}
if scope.evaluate(BuiltinMacros.GENERATE_TEST_ENTRY_POINT) {
result.append((scope.evaluate(BuiltinMacros.GENERATED_TEST_ENTRY_POINT_PATH), context.lookupFileType(identifier: "sourcecode.swift")!, /* shouldUsePrefixHeader */ false))
}
return result
}())
// Collect the list of object files.
var linkerInputNodes: [any PlannedNode] = []
let ltoSetting = scope.evaluate(BuiltinMacros.SWIFT_LTO)
for task in perArchTasks {
for object in task.outputs {
// FIXME: We should be able to do this in terms of actual file types, once we get actual typed objects as the outputs from tasks.
switch object.path.fileExtension {
case "o":
linkerInputNodes.append(object)
case "bc" where ltoSetting == .yes || ltoSetting == .yesThin:
linkerInputNodes.append(object)
case "swiftmodule":
dsymutilInputNodes.append(object)
break
default:
break
}
}
}
if scope.evaluate(BuiltinMacros.INVOKE_SSAF) {
// Collect only the .ssaf-tu.json sidecars that clang actually planned as task outputs.
let ssafInputs = perArchTasks.flatMap { $0.outputs }