forked from swiftlang/swift-build
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProductPlan.swift
More file actions
1464 lines (1262 loc) · 92.6 KB
/
Copy pathProductPlan.swift
File metadata and controls
1464 lines (1262 loc) · 92.6 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 SWBUtil
package import SWBCore
import SWBMacro
package import SWBProtocol
import Foundation
/// The `GlobalProductPlanDelegate` is a subset of the more ubiquitous `TaskPlanningDelegate` which provides functionality only needed by a `GlobalProductPlan`, even if it exists outside the context of a build.
package protocol GlobalProductPlanDelegate: CoreClientTargetDiagnosticProducingDelegate {
/// Check if the construction has been cancelled and should abort as soon as possible.
var cancelled: Bool { get }
/// Update the progress of an ongoing task planning operation.
func updateProgress(statusMessage: String, showInLog: Bool)
}
/// Information on the global build plan.
///
/// This class encapsulates the information on the global product plan which is made available to each individual product to use during planning.
package final class GlobalProductPlan: GlobalTargetInfoProvider
{
/// The build plan request.
package let planRequest: BuildPlanRequest
/// The target task info for each configured target.
private(set) var targetGateNodes: [ConfiguredTarget: TargetGateNodes]
/// The imparted build properties for each configured target.
///
/// The array of imparted build properties is ordered so that direct dependencies come first, followed by their direct
/// transitive dependencies and so forth.
private var impartedBuildPropertiesByTarget: [ConfiguredTarget: [SWBCore.ImpartedBuildProperties]]
/// The set of shared intermediate nodes that have been created, keyed by their sharing identifier.
///
/// This is a limited mechanism for concurrent task construction processes to coordinate on the production of a shared node.
let sharedIntermediateNodes = Registry<String, (any PlannedNode, any Sendable)>()
let delegate: any GlobalProductPlanDelegate
struct VFSContentsKey: Hashable {
let project: SWBCore.Project
let effectivePlatformName: String
}
/// A shared map of VFS contents, keyed by project.
//
// FIXME: <rdar://problem/28303817> [Swift Build] Produce VFS as a per-project task
let sharedVFSContents = Registry<VFSContentsKey, ByteString>()
/// The mapping of user-module info for targets.
private let moduleInfo = Registry<ConfiguredTarget, ModuleInfo?>()
/// Cache of parsed artifact bundle metadata.
package let artifactBundleMetadataCache = Registry<Path, ArtifactBundleMetadata>()
/// Context for copying experimentalWindowsDLL artifact bundle variants to the build directory.
let windowsDLLCopyContext = WindowsDLLCopyContext()
/// The information about XCFrameworks used throughout the task planning process.
let xcframeworkContext: XCFrameworkContext
/// The recursive search path resolver to use.
let recursiveSearchPathResolver: RecursiveSearchPathResolver
let buildDirectories: BuildDirectoryContext
/// Whether this build requires a VFS.
var needsVFS: Bool { return needsVFSCache.getValue(self) }
private var needsVFSCache = LazyCache { (plan: GlobalProductPlan) -> Bool in
// We enable the VFS iff some target provides a user-defined module.
for ct in plan.targetGateNodes.keys {
if plan.getTargetSettings(ct).globalScope.evaluate(BuiltinMacros.DEFINES_MODULE) {
return true
}
}
return false
}
/// Maps targets to the set of macro implementations they should load when compiling Swift code.
package private(set) var swiftMacroImplementationDescriptorsByTarget: [ConfiguredTarget: Set<SwiftMacroImplementationDescriptor>]
/// The set of targets which must build during prepare-for-indexing.
package private(set) var targetsRequiredToBuildForIndexing: Set<ConfiguredTarget>
/// The set of targets which need to build a swiftmodule during installAPI
package private(set) var targetsWhichShouldBuildModulesDuringInstallAPI: Set<ConfiguredTarget>?
/// Maps each target to its artifactbundles (direct and transitive).
package private(set) var artifactBundlesByTarget: [ConfiguredTarget: [ArtifactBundleInfo]]
/// All targets in the product plan.
/// - remark: This property is preferred over the `TargetBuildGraph` in the `BuildPlanRequest` as it performs additional computations for Swift packages.
package private(set) var allTargets: [ConfiguredTarget] = []
/// Get the dependencies of a target in the graph.
package func dependencies(of target: ConfiguredTarget) -> [ConfiguredTarget] {
resolvedDependencies(of: target).map { $0.target }
}
package var targetDependenciesByGuid: [TargetDependencyRelationship] {
allTargets.map { target in
.init(
.init(
name: target.target.name,
guid: target.guid.stringValue
),
dependencies: resolvedDependencies(of: target).map {
.init(
name: $0.target.target.name,
guid: $0.target.guid.stringValue
)
}
.sorted(by: \.guid)
)
}
.sorted(by: \.target.guid)
}
package func resolvedDependencies(of target: ConfiguredTarget) -> [ResolvedTargetDependency] {
// Use the static target for lookup if necessary.
let configuredTarget: ConfiguredTarget
if let staticTarget = staticallyBuildingTargetsWithDiamondLinkage[target.target] {
configuredTarget = target.replacingTarget(staticTarget)
} else {
configuredTarget = target
}
// Return dynamic targets where necessary.
return planRequest.buildGraph.resolvedDependencies(of: configuredTarget).map { resolvedDependency in
if let dynamicTarget = dynamicallyBuildingTargetsWithDiamondLinkage[resolvedDependency.target.target] {
return resolvedDependency.replacingTarget(dynamicTarget)
} else {
return resolvedDependency
}
}
}
/// The mapping of original targets to dynamically building targets because of diamond-style linkage.
private var dynamicallyBuildingTargetsWithDiamondLinkage = [SWBCore.Target: SWBCore.Target]()
/// Reverse mapping which allows looking up the corresponding dynamically building target for a static one.
private var staticallyBuildingTargetsWithDiamondLinkage = [SWBCore.Target: SWBCore.Target]()
/// All targets in the product plan which are building dynamically because of diamond-style linkage or because of the client's build request.
///
/// Note: currently this will only be package product targets.
package var dynamicallyBuildingTargets: Set<SWBCore.Target> {
return Set(planRequest.buildGraph.dynamicallyBuildingTargets + Array(dynamicallyBuildingTargetsWithDiamondLinkage.keys))
}
struct ErrorComponents: Hashable {
let name: String
let targetName: String
let andOther: String
let conflicts: Bool
}
var errorComponentsList = Set<ErrorComponents>()
/// A map from the `Path` of each products created by a target in the dependency closure (using both `TARGET_BUILD_DIR` and `BUILT_PRODUCTS_DIR`) to the `ConfiguredTarget` which creates it.
/// - remark: If there are multiple targets which create products at the same path, that is not reflected here, and will be a build error elsewhere.
let productPathsToProducingTargets: [Path: ConfiguredTarget]
/// A map from `ConfiguredTarget`s which are being built as mergeable libraries to `ConfiguredTarget`s which are merging them.
let mergeableTargetsToMergingTargets: [ConfiguredTarget: Set<ConfiguredTarget>]
/// A map from each target to all of the targets whose products its product hosts.
///
/// Presently the only mechanism supported for this sort of hosting is XCTest app-hosted tests using `TEST_HOST`.
let hostedTargetsForTargets: [ConfiguredTarget: OrderedSet<ConfiguredTarget>]
/// A map from each hosted target to the target whose product hosts its product.
///
/// Presently the only mechanism supported for this sort of hosting is XCTest app-hosted tests using `TEST_HOST`.
let hostTargetForTargets: [ConfiguredTarget: ConfiguredTarget]
/// The set of `PRODUCT_NAME`s used by more than one target. This is permitted, but may require special consideration in some contexts to avoid conflicting tasks/outputs.
let duplicatedProductNames: Set<String>
/// A map from targets to the the target which produces the nearest enclosing product.
let targetToProducingTargetForNearestEnclosingProduct: [ConfiguredTarget: ConfiguredTarget]
/// A map of `MH_BUNDLE` targets to any clients of that target.
let clientsOfBundlesByTarget: [ConfiguredTarget:[ConfiguredTarget]]
private static let dynamicMachOTypes = ["mh_execute", "mh_dylib", "mh_bundle"]
// Checks that we have either been passed a configuration override for packages or we are building Debug/Release.
private static func verifyPackageConfigurationOverride(planRequest: BuildPlanRequest) {
#if DEBUG
if !planRequest.workspaceContext.workspace.projects.filter({ $0.isPackage }).isEmpty {
let parameters = planRequest.buildRequest.parameters
assert(parameters.configuration == nil || parameters.packageConfigurationOverride != nil || parameters.configuration == "Debug" || parameters.configuration == "Release")
}
#endif
}
enum LinkedDependency: Hashable {
case direct(ConfiguredTarget)
case staticTransitive(ConfiguredTarget)
case bundleLoader(ConfiguredTarget)
var target: ConfiguredTarget {
switch self {
case .direct(let target): return target
case .staticTransitive(let target): return target
case .bundleLoader(let target): return target
}
}
}
package init(planRequest: BuildPlanRequest, delegate: any GlobalProductPlanDelegate, nodeCreationDelegate: (any TaskPlanningNodeCreationDelegate)?) async {
Self.verifyPackageConfigurationOverride(planRequest: planRequest)
self.planRequest = planRequest
self.delegate = delegate
self.recursiveSearchPathResolver = RecursiveSearchPathResolver(fs: planRequest.workspaceContext.fs)
self.xcframeworkContext = XCFrameworkContext(workspaceContext: planRequest.workspaceContext, buildRequestContext: planRequest.buildRequestContext)
self.buildDirectories = BuildDirectoryContext()
enum LinkageGraphCacheKey: Hashable { case only }
let linkageGraphCache = AsyncCache<LinkageGraphCacheKey, TargetLinkageGraph>()
let getLinkageGraph: @Sendable () async throws -> TargetLinkageGraph = {
try await linkageGraphCache.value(forKey: LinkageGraphCacheKey.only) {
await TargetLinkageGraph(workspaceContext: planRequest.workspaceContext, buildRequest: planRequest.buildRequest, buildRequestContext: planRequest.buildRequestContext, delegate: WrappingDelegate(delegate: delegate))
}
}
// Perform post-processing analysis of the build graph
self.clientsOfBundlesByTarget = Self.computeBundleClients(buildGraph: planRequest.buildGraph, buildRequestContext: planRequest.buildRequestContext)
self.artifactBundlesByTarget = await Self.computeArtifactBundleInfo(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequest: planRequest.buildRequest, buildRequestContext: planRequest.buildRequestContext, workspaceContext: planRequest.workspaceContext, getLinkageGraph: getLinkageGraph, metadataCache: self.artifactBundleMetadataCache, delegate: delegate)
let directlyLinkedDependenciesByTarget: [ConfiguredTarget: OrderedSet<LinkedDependency>]
(self.impartedBuildPropertiesByTarget, directlyLinkedDependenciesByTarget) = await Self.computeImpartedBuildProperties(planRequest: planRequest, getLinkageGraph: getLinkageGraph, delegate: delegate)
self.mergeableTargetsToMergingTargets = Self.computeMergeableLibraries(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext)
self.productPathsToProducingTargets = Self.computeProducingTargetsForProducts(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext)
self.targetGateNodes = Self.constructTargetGateNodes(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext, impartedBuildPropertiesByTarget: impartedBuildPropertiesByTarget, enableIndexBuildArena: planRequest.buildRequest.enableIndexBuildArena, nodeCreationDelegate: nodeCreationDelegate)
(self.hostedTargetsForTargets, self.hostTargetForTargets) = Self.computeHostingRelationships(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext, delegate: delegate)
self.duplicatedProductNames = Self.computeDuplicateProductNames(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext)
self.targetToProducingTargetForNearestEnclosingProduct = Self.computeTargetsProducingEnclosingProducts(buildGraph: planRequest.buildGraph, productPathsToProducingTargets: productPathsToProducingTargets, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext)
self.swiftMacroImplementationDescriptorsByTarget = Self.computeSwiftMacroImplementationDescriptors(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext, delegate: delegate)
self.targetsRequiredToBuildForIndexing = Self.computeTargetsRequiredToBuildForIndexing(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext)
self.targetsWhichShouldBuildModulesDuringInstallAPI = Self.computeTargetsWhichShouldBuildModulesInInstallAPI(buildRequest: planRequest.buildRequest, buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext)
// Diagnostics reporting
diagnoseInvalidDeploymentTargets(diagnosticDelegate: delegate)
diagnoseSwiftPMUnsafeFlags(diagnosticDelegate: delegate)
Self.diagnoseValidArchsEnforcementStatus(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext, delegate: delegate)
if UserDefaults.enableDiagnosingDiamondProblemsWhenUsingPackages && !planRequest.buildRequest.enableIndexBuildArena {
resolveDiamondProblemsInPackages(dependenciesByTarget: directlyLinkedDependenciesByTarget, diagnosticDelegate: delegate)
}
// Sort based on order from build graph to preserve any fixed ordering from the scheme.
self.allTargets = Array(self.targetGateNodes.keys).sorted { (a, b) -> Bool in
guard let first = planRequest.buildGraph.allTargets.firstIndex(of: a) else {
return false
}
guard let second = planRequest.buildGraph.allTargets.firstIndex(of: b) else {
return true
}
return first < second
}
}
// Compute the dependents of targets producing bundles. This information is used later to propagate Info.plist entries from codeless bundles to their clients.
private static func computeBundleClients(buildGraph: TargetBuildGraph, buildRequestContext: BuildRequestContext) -> [ConfiguredTarget:[ConfiguredTarget]] {
var clientsOfBundlesByTarget = [ConfiguredTarget:[ConfiguredTarget]]()
let bundleTargets = Set(buildGraph.allTargets.filter {
let settings = buildRequestContext.getCachedSettings($0.parameters, target: $0.target)
return settings.globalScope.evaluate(BuiltinMacros.MACH_O_TYPE) == "mh_bundle"
})
for configuredTarget in buildGraph.allTargets {
for match in bundleTargets.intersection(buildGraph.dependencies(of: configuredTarget)) {
clientsOfBundlesByTarget[match, default: []].append(configuredTarget)
}
}
return clientsOfBundlesByTarget
}
/// Compute target hosting relationships. This information is used to correctly order postprocessing tasks, and to plan tasks needed by test bundle targets, like copying the testing frameworks.
private static func computeHostingRelationships(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext, delegate: any GlobalProductPlanDelegate) -> ([ConfiguredTarget: OrderedSet<ConfiguredTarget>], [ConfiguredTarget: ConfiguredTarget]) {
var targetsForProductPaths = [Path: ConfiguredTarget]()
var targetsForTestHostPaths = [Path: OrderedSet<ConfiguredTarget>]()
for configuredTarget in buildGraph.allTargets {
let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget])
let scope = settings.globalScope
// Compute various interesting paths to the product for this target and save them for use below to match them with targets whose product they are hosting.
for buildDirSetting in [BuiltinMacros.BUILT_PRODUCTS_DIR, BuiltinMacros.TARGET_BUILD_DIR] {
let buildDirPath = scope.evaluate(buildDirSetting)
for subpathSetting in [BuiltinMacros.FULL_PRODUCT_NAME, BuiltinMacros.EXECUTABLE_PATH] {
let subpath = scope.evaluate(subpathSetting)
if !subpath.isEmpty, !subpath.isAbsolute {
let path = buildDirPath.join(subpath)
// Note that if we find multiple targets in the build graph which create products at the same path, only one will be recorded here. But in that case llbuild should complain at build time about multiple producers for some output. Since this logic is somewhat niche at present (only used for hosted test targets), we're not emitting an issue here, since it's a more general potential problem and I think we should fall through to the llbuild behavior. But I'm leaving this comment in so that if we have a future unanticipated scenario where this is relevant, a future visitor to this code can note that this code isn't handling that scenario.
targetsForProductPaths[path] = configuredTarget
}
}
}
// If this is a target whose product will be embedded by that target inside another target, then capture information about the target it will embed into.
// Note that we don't invoke XCTestBundleProductTypeSpec.usesTestHost() here because it returns false when doing a deployment build.
let hostProductPath = Path(scope.evaluate(BuiltinMacros.TEST_HOST)).normalize()
if settings.productType is XCTestBundleProductTypeSpec && !hostProductPath.isEmpty {
if !hostProductPath.isEmpty {
// This is a test target which uses TEST_HOST, so we add it to the list of targets which are using that host.
targetsForTestHostPaths[hostProductPath, default: OrderedSet<ConfiguredTarget>()].append(configuredTarget)
}
}
}
// Compute the map of targets to the list of targets they host, and vice versa.
var hostedTargetsForTarget = [ConfiguredTarget: OrderedSet<ConfiguredTarget>]()
var hostTargetForTargets = [ConfiguredTarget: ConfiguredTarget]()
for (hostPath, hostedTargets) in targetsForTestHostPaths {
// If somehow we don't have any hosted targets for this path, just skip this.
guard !hostedTargets.isEmpty else {
continue
}
if let hostTarget = targetsForProductPaths[hostPath] {
hostedTargetsForTarget[hostTarget] = hostedTargets
for target in hostedTargets {
hostTargetForTargets[target] = hostTarget
}
}
else {
// Emit a warning for a target which defines a TEST_HOST which can't be mapped to a target.
for hostedTarget in hostedTargets {
delegate.warning(.overrideTarget(hostedTarget), "Unable to find a target which creates the host product for value of $(TEST_HOST) '\(hostPath.str)'", location: .buildSetting(BuiltinMacros.TEST_HOST), component: .targetIntegrity)
}
}
}
return (hostedTargetsForTarget, hostTargetForTargets)
}
/// Find duplicated target names across the build graph which force us to be more conservative in applying some build performance optimizations.
private static func computeDuplicateProductNames(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext) -> Set<String> {
var productNames: Set<String> = []
var duplicatedNames: Set<String> = []
for configuredTarget in buildGraph.allTargets {
let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget])
let targetProductName = settings.globalScope.evaluate(BuiltinMacros.PRODUCT_NAME)
if productNames.contains(targetProductName) {
duplicatedNames.insert(targetProductName)
} else {
productNames.insert(targetProductName)
}
}
return duplicatedNames
}
/// Track targets whose products enclose the products of other targets. This is used to correctly order codesigning and disable certain build performance optimizations.
private static func computeTargetsProducingEnclosingProducts(buildGraph: TargetBuildGraph, productPathsToProducingTargets: [Path: ConfiguredTarget], provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext) -> [ConfiguredTarget: ConfiguredTarget] {
// Record targets with nested build directories.
var targetToProducingTargetForNearestEnclosingProduct: [ConfiguredTarget: ConfiguredTarget] = [:]
for configuredTarget in buildGraph.allTargets {
let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget])
var dir = settings.globalScope.evaluate(BuiltinMacros.TARGET_BUILD_DIR).normalize()
while !dir.isRoot && !dir.isEmpty {
if let enclosingTarget = productPathsToProducingTargets[dir] {
targetToProducingTargetForNearestEnclosingProduct[configuredTarget] = enclosingTarget
break
}
dir = dir.dirname
}
}
return targetToProducingTargetForNearestEnclosingProduct
}
// Compute the description of Swift macro implemenbtations required by targets in the build graph.
private static func computeSwiftMacroImplementationDescriptors(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext, delegate: any GlobalProductPlanDelegate) -> [ConfiguredTarget: Set<SwiftMacroImplementationDescriptor>] {
var swiftMacroImplementationDescriptorsByTarget: [ConfiguredTarget: Set<SwiftMacroImplementationDescriptor>] = [:]
// Consider the targets in topological order, collecting information about host tool usage.
for configuredTarget in buildGraph.allTargets {
// If this target loads binary macros, add a descriptor for this target
let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget])
let macros = settings.globalScope.evaluate(BuiltinMacros.SWIFT_LOAD_BINARY_MACROS)
if !macros.isEmpty {
for macro in macros {
if let descriptor = SwiftMacroImplementationDescriptor(value: macro) {
swiftMacroImplementationDescriptorsByTarget[configuredTarget, default: []].insert(descriptor)
} else {
delegate.error("'\(macro)' does not describe a valid Swift macro implementation")
}
}
}
// Because macro implementations are host tools, we consider all dependencies rather than restricting ourselves to only linkage dependencies like imparted build properties. As a result, we do not require special handling of bundle loader targets here.
for dependency in buildGraph.dependencies(of: configuredTarget) {
// Add any macro implementation targets of the dependency to the dependent. The dependency is guaranteed to have its descriptors (if any) populated because the targets are being processed in topological order.
swiftMacroImplementationDescriptorsByTarget[configuredTarget, default: []].formUnion(swiftMacroImplementationDescriptorsByTarget[dependency, default: []])
// If the dependency vends a macro, add a descriptor for this target.
let dependencySettings = buildRequestContext.getCachedSettings(dependency.parameters, target: dependency.target, provisioningTaskInputs: provisioningInputs[dependency])
let declaringModules = dependencySettings.globalScope.evaluate(BuiltinMacros.SWIFT_IMPLEMENTS_MACROS_FOR_MODULE_NAMES)
if !declaringModules.isEmpty {
swiftMacroImplementationDescriptorsByTarget[configuredTarget, default: []].insert(.init(declaringModuleNames: declaringModules, path: dependencySettings.globalScope.evaluate(BuiltinMacros.TARGET_BUILD_DIR).join(dependencySettings.globalScope.evaluate(BuiltinMacros.EXECUTABLE_PATH)).normalize()))
}
}
}
return swiftMacroImplementationDescriptorsByTarget
}
/// Determine which targets in the build are required to correctly prepare indexing functionality (e.g. because they're host tools which produce sources contributing to a module).
private static func computeTargetsRequiredToBuildForIndexing(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext) -> Set<ConfiguredTarget> {
// Collect information about tools produced by the build which may be run by script phases or custom tasks (including those derived from package build tool plugins.
// Consider the targets in topological order
var targetsRequiredToBuildForIndexing: Set<ConfiguredTarget> = []
var targetsByCommandLineToolProductPath: [Path: ConfiguredTarget] = [:]
for configuredTarget in buildGraph.allTargets {
let targetSettings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget])
// If this target is a host tool, it and its dependencies must build as part of prepare-for-indexing.
if targetSettings.productType?.conformsTo(identifier: "com.apple.product-type.tool.host-build") == true {
targetsRequiredToBuildForIndexing.insert(configuredTarget)
targetsRequiredToBuildForIndexing.formUnion(transitiveClosure([configuredTarget], successors: buildGraph.dependencies(of:)).0)
}
if targetSettings.platform?.name == targetSettings.globalScope.evaluate(BuiltinMacros.HOST_PLATFORM),
targetSettings.productType?.conformsTo(identifier: "com.apple.product-type.tool") == true {
let executablePath = targetSettings.globalScope.evaluate(BuiltinMacros.TARGET_BUILD_DIR).join(targetSettings.globalScope.evaluate(BuiltinMacros.EXECUTABLE_PATH)).normalize()
targetsByCommandLineToolProductPath[executablePath] = configuredTarget
}
for scriptPhase in (configuredTarget.target as? SWBCore.BuildPhaseTarget)?.buildPhases.compactMap({ $0 as? SWBCore.ShellScriptBuildPhase }) ?? [] {
for scriptPhaseInput in scriptPhase.inputFilePaths.map({ Path(targetSettings.globalScope.evaluate($0)).normalize() }) {
if let producingTarget = targetsByCommandLineToolProductPath[scriptPhaseInput] {
targetsRequiredToBuildForIndexing.insert(producingTarget)
targetsRequiredToBuildForIndexing.formUnion(transitiveClosure([producingTarget], successors: buildGraph.dependencies(of:)).0)
}
}
}
for customTask in configuredTarget.target.customTasks {
guard customTask.preparesForIndexing else { continue }
for input in customTask.inputFilePaths.map({ Path(targetSettings.globalScope.evaluate($0)).normalize() }) {
if let producingTarget = targetsByCommandLineToolProductPath[input] {
targetsRequiredToBuildForIndexing.insert(producingTarget)
targetsRequiredToBuildForIndexing.formUnion(transitiveClosure([producingTarget], successors: buildGraph.dependencies(of:)).0)
}
}
}
}
return targetsRequiredToBuildForIndexing
}
/// Determine which targets in the build graph should build modules during installAPI.
private static func computeTargetsWhichShouldBuildModulesInInstallAPI(buildRequest: BuildRequest, buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext) -> Set<ConfiguredTarget> {
if buildRequest.parameters.action == .installAPI {
var targetsWhichShouldEmitModulesDuringInstallAPI: Set<ConfiguredTarget> = []
// Consider all targets in topological order, visiting dependents before dependencies to ensure all transitive dependencies of a target which emits a module also emit modules.
for configuredTarget in buildGraph.allTargets.reversed() {
let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget])
// If this target will emit a TBD during installAPI, it must emit a module
if settings.globalScope.evaluate(BuiltinMacros.SUPPORTS_TEXT_BASED_API) && settings.allowInstallAPIForTargetsSkippedInInstall(in: settings.globalScope) && settings.productType?.supportsInstallAPI == true {
targetsWhichShouldEmitModulesDuringInstallAPI.insert(configuredTarget)
}
// If this target should emit a module during installAPI, its dependencies should as well because this target's module may depend on them.
if targetsWhichShouldEmitModulesDuringInstallAPI.contains(configuredTarget) {
for dependency in buildGraph.dependencies(of: configuredTarget) {
targetsWhichShouldEmitModulesDuringInstallAPI.insert(dependency)
}
}
}
return targetsWhichShouldEmitModulesDuringInstallAPI
} else {
return []
}
}
/// Compute artifactbundle metadata for each target in the build graph.
private static func computeArtifactBundleInfo(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequest: BuildRequest, buildRequestContext: BuildRequestContext, workspaceContext: WorkspaceContext, getLinkageGraph: @Sendable () async throws -> TargetLinkageGraph, metadataCache: Registry<Path, ArtifactBundleMetadata>, delegate: any GlobalProductPlanDelegate) async -> [ConfiguredTarget: [ArtifactBundleInfo]] {
var artifactBundlesInfoByTarget: [ConfiguredTarget: Set<ArtifactBundleInfo>] = [:]
// Process targets in topological order
for configuredTarget in buildGraph.allTargets {
let buildPhases: [SWBCore.BuildPhaseWithBuildFiles]
if let standardTarget = configuredTarget.target as? SWBCore.StandardTarget {
buildPhases = [standardTarget.frameworksBuildPhase, standardTarget.resourcesBuildPhase].compactMap(\.self)
} else if let packageProductTarget = configuredTarget.target as? SWBCore.PackageProductTarget {
buildPhases = [packageProductTarget.frameworksBuildPhase].compactMap(\.self)
} else {
continue
}
let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget])
let scope = settings.globalScope
let specLookupContext = SpecLookupCtxt(specRegistry: workspaceContext.core.specRegistry, platform: settings.platform)
guard let artifactBundleFileType = specLookupContext.lookupFileType(identifier: "wrapper.artifactbundle") else {
continue
}
// Parse artifact bundle info from any bundles this target directly depends upon.
//
// We consider both the frameworks phase and resources phase because when building a relocatable object, SwiftPM PIF generation
// adds binary targets to the resources phase so their static content isn't pulled into the object. This model
// is confusing and we should reconsider it.
for phase in buildPhases {
for buildFile in phase.buildFiles {
let currentPlatformFilter = PlatformFilter(scope)
guard currentPlatformFilter.matches(buildFile.platformFilters) else { continue }
guard case .reference(let referenceGUID) = buildFile.buildableItem else { continue }
guard let reference = workspaceContext.workspace.lookupReference(for: referenceGUID) else { continue }
let resolvedPath = settings.filePathResolver.resolveAbsolutePath(reference)
// TODO: Remove the fileExtension check once SwiftPM has been updated to consistently set the file type on artifact bundle references in PIF
if resolvedPath.fileExtension == "artifactbundle" || specLookupContext.lookupFileType(reference: reference)?.conformsTo(artifactBundleFileType) == true {
do {
let metadata = try metadataCache.getOrInsert(resolvedPath) {
try ArtifactBundleMetadata.parse(at: resolvedPath, fileSystem: buildRequestContext.fs)
}
let info = ArtifactBundleInfo(bundlePath: resolvedPath, metadata: metadata)
artifactBundlesInfoByTarget[configuredTarget, default: []].insert(info)
} catch {
delegate.error("Failed to parse artifactbundle at '\(resolvedPath.str)': \(error.localizedDescription)")
}
}
}
}
// Propagate artifact bundle info from dependencies to dependents. Because we're considering targets in topological order, the info recorded for direct dependencies is already complete.
if !artifactBundlesInfoByTarget.isEmpty {
do {
let linkageGraph = try await getLinkageGraph()
for dependency in linkageGraph.dependencies(of: configuredTarget) {
if let dependencyArtifactInfo = artifactBundlesInfoByTarget[dependency] {
artifactBundlesInfoByTarget[configuredTarget, default: []].formUnion(dependencyArtifactInfo)
}
}
} catch {
delegate.error("failed to determine linkage dependencies of '\(configuredTarget.target.name)' when computing artifact bundle usage: \(error.localizedDescription)")
}
}
}
return artifactBundlesInfoByTarget.mapValues {
$0.sorted(by: \.bundlePath.str)
}
}
/// Compute the build properties imparted on each target in the graph.
private static func computeImpartedBuildProperties(planRequest: BuildPlanRequest, getLinkageGraph: @Sendable () async throws -> TargetLinkageGraph, delegate: any GlobalProductPlanDelegate) async -> ([ConfiguredTarget:[SWBCore.ImpartedBuildProperties]], [ConfiguredTarget:OrderedSet<LinkedDependency>]) {
var impartedBuildPropertiesByTarget = [ConfiguredTarget:[SWBCore.ImpartedBuildProperties]]()
var directlyLinkedDependenciesByTarget = [ConfiguredTarget:OrderedSet<LinkedDependency>]()
// We can skip computing contributing properties entirely if no target declares any and if there are no package products in the graph.
let targetsContributingProperties = planRequest.buildGraph.allTargets.filter { !$0.target.hasImpartedBuildProperties || $0.target.type == .packageProduct }
if !targetsContributingProperties.isEmpty {
do {
let linkageGraph = try await getLinkageGraph()
let configuredTargets = planRequest.buildGraph.allTargets
let targetsByBundleLoader = Self.computeBundleLoaderDependencies(
planRequest: planRequest,
configuredTargets: AnyCollection(configuredTargets),
diagnosticDelegate: delegate
)
var bundleLoaderByTarget = [ConfiguredTarget:ConfiguredTarget]()
for (bundleLoaderTarget, targetsUsingThatBundleLoader) in targetsByBundleLoader {
for targetUsingBundleLoader in targetsUsingThatBundleLoader {
bundleLoaderByTarget[targetUsingBundleLoader] = bundleLoaderTarget
}
}
for configuredTarget in configuredTargets {
// Compute the transitive dependencies of the configured target.
let dependencies: OrderedSet<ConfiguredTarget>
if UserDefaults.useTargetDependenciesForImpartedBuildSettings {
dependencies = transitiveClosure([configuredTarget], successors: planRequest.buildGraph.dependencies(of:)).0
} else {
dependencies = transitiveClosure([configuredTarget], successors: linkageGraph.dependencies(of:)).0
}
let linkedDependencies: [LinkedDependency] = linkageGraph.dependencies(of: configuredTarget).map { .direct($0) }
let transitiveStaticDependencies: [LinkedDependency] = transitiveClosure(linkedDependencies.map(\.target)) {
let settings = planRequest.buildRequestContext.getCachedSettings($0.parameters, target: $0.target)
guard !Self.dynamicMachOTypes.contains(settings.globalScope.evaluate(BuiltinMacros.MACH_O_TYPE)) else {
return []
}
return linkageGraph.dependencies(of: $0)
}.0.map { .staticTransitive($0) }
directlyLinkedDependenciesByTarget[configuredTarget] = OrderedSet(linkedDependencies + transitiveStaticDependencies + (bundleLoaderByTarget[configuredTarget].map { [.bundleLoader($0)] } ?? []))
impartedBuildPropertiesByTarget[configuredTarget] = dependencies.compactMap { $0.getImpartedBuildProperties(using: planRequest) }
}
for (targetToImpart, bundleLoaderTargets) in targetsByBundleLoader {
for bundleLoaderTarget in bundleLoaderTargets {
// Bundle loader target gets all of the build settings that are imparted to the target it is referencing _and_ the build settings that are being imparted by that referenced target.
let currentImpartedBuildProperties = targetToImpart.getImpartedBuildProperties(using: planRequest).map { [$0] } ?? []
impartedBuildPropertiesByTarget[bundleLoaderTarget, default: []] += currentImpartedBuildProperties + (impartedBuildPropertiesByTarget[targetToImpart] ?? [])
}
}
} catch {
delegate.error("failed to compute imparted build properties: \(error.localizedDescription)")
}
}
return (impartedBuildPropertiesByTarget, directlyLinkedDependenciesByTarget)
}
/// Determine which target produced each product in the build.
private static func computeProducingTargetsForProducts(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext) -> [Path: ConfiguredTarget] {
var productPathsToProducingTargets = [Path: ConfiguredTarget]()
for configuredTarget in buildGraph.allTargets {
let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget])
// Record the target's products using both TARGET_BUILD_DIR and BUILT_PRODUCTS_DIR.
if let standardTarget = configuredTarget.target as? SWBCore.StandardTarget {
productPathsToProducingTargets[settings.globalScope.evaluate(BuiltinMacros.TARGET_BUILD_DIR).join(standardTarget.productReference.name).normalize()] = configuredTarget
productPathsToProducingTargets[settings.globalScope.evaluate(BuiltinMacros.BUILT_PRODUCTS_DIR).join(standardTarget.productReference.name).normalize()] = configuredTarget
}
}
return productPathsToProducingTargets
}
/// Construct the semantic gate nodes used to order work across targets.
private static func constructTargetGateNodes(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext, impartedBuildPropertiesByTarget: [ConfiguredTarget:[SWBCore.ImpartedBuildProperties]], enableIndexBuildArena: Bool, nodeCreationDelegate: (any TaskPlanningNodeCreationDelegate)?) -> [ConfiguredTarget: TargetGateNodes] {
var targetGateNodes = [ConfiguredTarget: TargetGateNodes]()
for configuredTarget in buildGraph.allTargets {
// If we have a delegate to do so, then create virtual nodes for the target used to order this target's tasks with respect to other target's tasks - both fundamental target ordering, and orderings for eager compilation.
if let nodeCreationDelegate {
let targetNodeBase = configuredTarget.guid.stringValue
let targetStartNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-entry")
let targetEndNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-end")
let targetStartCompilingNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-begin-compiling")
let targetStartLinkingNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-begin-linking")
let targetStartScanningNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-begin-scanning")
let targetModulesReadyNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-modules-ready")
let targetLinkerInputsReadyNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-linker-inputs-ready")
let targetScanInputsReadyNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-scan-inputs-ready")
let targetStartImmediateNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-immediate")
var preparedForIndexPreCompilationNode: (any PlannedNode)?
var preparedForIndexModuleContentNode: (any PlannedNode)?
if enableIndexBuildArena, configuredTarget.target.type == .standard {
let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, impartedBuildProperties: impartedBuildPropertiesByTarget[configuredTarget])
let scope = settings.globalScope
preparedForIndexPreCompilationNode = nodeCreationDelegate.createNode(absolutePath: Path(scope.evaluate(BuiltinMacros.INDEX_PREPARED_TARGET_MARKER_PATH)))
preparedForIndexModuleContentNode = nodeCreationDelegate.createNode(absolutePath: Path(scope.evaluate(BuiltinMacros.INDEX_PREPARED_MODULE_CONTENT_MARKER_PATH)))
}
let targetUnsignedProductReadyNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-unsigned-product-ready")
let targetWillSignNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-will-sign")
// Create the target task info collector.
targetGateNodes[configuredTarget] = TargetGateNodes(startNode: targetStartNode, endNode: targetEndNode, startCompilingNode: targetStartCompilingNode, startLinkingNode: targetStartLinkingNode, startScanningNode: targetStartScanningNode, modulesReadyNode: targetModulesReadyNode, linkerInputsReadyNode: targetLinkerInputsReadyNode, scanInputsReadyNode: targetScanInputsReadyNode, startImmediateNode: targetStartImmediateNode, unsignedProductReadyNode: targetUnsignedProductReadyNode, willSignNode: targetWillSignNode, preparedForIndexPreCompilationNode: preparedForIndexPreCompilationNode, preparedForIndexModuleContentNode: preparedForIndexModuleContentNode)
}
}
return targetGateNodes
}
/// Determine which mergeable libraries will be merged into their dependents.
private static func computeMergeableLibraries(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext) -> [ConfiguredTarget: Set<ConfiguredTarget>] {
var mergeableTargetsToMergingTargets = [ConfiguredTarget: Set<ConfiguredTarget>]()
for configuredTarget in buildGraph.allTargets {
let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget])
let scope = settings.globalScope
// If this target is a merged binary target, then record its dependencies which are being built as mergeable libraries.
if scope.evaluate(BuiltinMacros.MERGE_LINKED_LIBRARIES) {
for dependency in buildGraph.dependencies(of: configuredTarget) {
let settings = buildRequestContext.getCachedSettings(dependency.parameters, target: dependency.target, provisioningTaskInputs: provisioningInputs[dependency])
let scope = settings.globalScope
if scope.evaluate(BuiltinMacros.MERGEABLE_LIBRARY) {
mergeableTargetsToMergingTargets[dependency, default: Set<ConfiguredTarget>()].insert(configuredTarget)
}
}
}
}
return mergeableTargetsToMergingTargets
}
private static func computeBundleLoaderDependencies(
planRequest: BuildPlanRequest,
configuredTargets: AnyCollection<ConfiguredTarget>,
diagnosticDelegate: any TargetDiagnosticProducingDelegate
) -> [ConfiguredTarget: [ConfiguredTarget]] {
let targetsWithBundleLoader: [(Path, ConfiguredTarget)] = configuredTargets.compactMap {
let settings = planRequest.buildRequestContext.getCachedSettings($0.parameters, target: $0.target)
let bundleLoader = settings.globalScope.evaluate(BuiltinMacros.BUNDLE_LOADER)
return bundleLoader.isEmpty ? nil : (bundleLoader, $0)
}
// We can return early if there are no targets that uses the bundle loader setting.
if targetsWithBundleLoader.isEmpty {
return [:]
}
let targetsWithBundleLoaderMap = Dictionary(grouping: targetsWithBundleLoader, by: { $0.0 }).mapValues{ $0.compactMap{ $0.1 } }
// We only need to care about targets that actually have some imparted properties.
let executablePathToTarget: [(Path, ConfiguredTarget)] = configuredTargets.map {
let settings = planRequest.buildRequestContext.getCachedSettings($0.parameters, target: $0.target)
let path = settings.globalScope.evaluate(BuiltinMacros.BUILT_PRODUCTS_DIR).join(settings.globalScope.evaluate(BuiltinMacros.EXECUTABLE_PATH)).normalize()
return (path, $0)
}
let executablePathToTargetsMap = Dictionary(grouping: executablePathToTarget, by: { $0.0 }).mapValues{ $0.compactMap{ $0.1 } }
var result: [ConfiguredTarget: [ConfiguredTarget]] = [:]
for (execName, targets) in executablePathToTargetsMap {
// Skip targets that are not referenced in any target with a bundle loader.
guard let bundleLoaderTargets = targetsWithBundleLoaderMap[execName] else { continue }
guard targets.count == 1 else {
// This means multiple targets are going to write to the same location. This would be diagnosed during task construction.
continue
}
result[targets[0]] = bundleLoaderTargets
}
return result
}
/// Ensures that no non-package target is depending on a package target
/// that uses unsafe flags in the package manifest.
func diagnoseSwiftPMUnsafeFlags(diagnosticDelegate: any TargetDiagnosticProducingDelegate) {
let targets = planRequest.buildGraph.allTargets
// Find targets that use unsafe flags.
let unsafeFlagsTargets = targets.filter {
return getTargetSettings($0).globalScope.evaluate(BuiltinMacros.USES_SWIFTPM_UNSAFE_FLAGS)
}
for target in targets {
// Ignore package targets as SwiftPM already checks for this.
let project = planRequest.workspaceContext.workspace.project(for: target.target)
if project.isPackage {
continue
}
// Emit an error if this target depends on a package target that uses unsafe flags.
//
// We'll probably want to allow this for root packages in the future.
let dependencies = planRequest.buildGraph.dependencies(of: target)
let dependsOnAnUnsafeTarget = dependencies.first{ unsafeFlagsTargets.contains($0) }
if let unsafeTarget = dependsOnAnUnsafeTarget {
diagnosticDelegate.error(.overrideTarget(target), "The package product '\(unsafeTarget.target.name)' cannot be used as a dependency of this target because it uses unsafe build flags.", component: .targetIntegrity)
}
}
}
private static func diagnoseValidArchsEnforcementStatus(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext, delegate: any GlobalProductPlanDelegate) {
var shouldEmitValidArchsEnforcementNote = false
for configuredTarget in buildGraph.allTargets {
let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget])
let scope = settings.globalScope
if !scope.evaluate(BuiltinMacros.ENFORCE_VALID_ARCHS) {
shouldEmitValidArchsEnforcementNote = true
}
}
if shouldEmitValidArchsEnforcementNote {
delegate.note("Not enforcing VALID_ARCHS because ENFORCE_VALID_ARCHS = NO")
}
}
func diagnoseInvalidDeploymentTargets(diagnosticDelegate: any TargetDiagnosticProducingDelegate) {
for target in planRequest.buildGraph.allTargets.filter({
planRequest.workspaceContext.workspace.project(for: $0.target).isPackage && $0.target.type == .standard
}) {
let settings = getTargetSettings(target)
let platformDeploymentTargetMacro: StringMacroDeclaration?
let platformDisplayName: String
if settings.sdkVariant?.isMacCatalyst == true {
platformDeploymentTargetMacro = BuiltinMacros.IPHONEOS_DEPLOYMENT_TARGET
platformDisplayName = BuildVersion.Platform.macCatalyst.displayName(infoLookup: planRequest.workspaceContext.core)
} else {
platformDeploymentTargetMacro = settings.platform?.deploymentTargetMacro
platformDisplayName = settings.platform?.familyDisplayName ?? "No Platform"
}
guard let deploymentTargetMacro = platformDeploymentTargetMacro, let deploymentTarget = try? Version(settings.globalScope.evaluate(deploymentTargetMacro)) else {
continue
}
let dependencies = planRequest.buildGraph.dependencies(of: target)
for dependency in dependencies {
guard let dependencyDeploymentTarget = try? Version(self.getTargetSettings(dependency).globalScope.evaluate(deploymentTargetMacro)) else {
continue
}
if dependencyDeploymentTarget > deploymentTarget {
diagnosticDelegate.error(.overrideTarget(target), "The package product '\(dependency.target.name)' requires minimum platform version \(dependencyDeploymentTarget) for the \(platformDisplayName) platform, but this target supports \(deploymentTarget)", component: .targetIntegrity)
}
}
}
}
func resolveDiamondProblemsInPackages(dependenciesByTarget: [ConfiguredTarget:OrderedSet<LinkedDependency>], diagnosticDelegate: any TargetDiagnosticProducingDelegate) {
// Repeatedly check for diamonds until we do not find any more. This should always converge since worst case we will stop once all static package targets have been converted to dynamic ones, but we will also terminate if we somehow end up in a stable state with more than zero remaining diamonds.
var lastDiamonds = 0, currentDiamonds = 0
repeat {
lastDiamonds = currentDiamonds
currentDiamonds = checkForDiamondProblemsInPackageProductLinkage(dependenciesByTarget: dependenciesByTarget, diagnosticDelegate: diagnosticDelegate)
} while currentDiamonds > 0 && lastDiamonds != currentDiamonds
// If all package targets of a product ended up being building dynamically, we do not need to build the product itself dynamically. In fact doing so would fail, because the binary would have no contents.
for staticTarget in dynamicallyBuildingTargetsWithDiamondLinkage.keys {
guard staticTarget.type == .packageProduct else { continue }
let packageTargetDependencies = staticTarget.dependencies.compactMap { targetDependency in
planRequest.workspaceContext.workspace.target(for: targetDependency.guid)
}.filter {
$0.type != .packageProduct
}
let staticallyLinkedTargets = packageTargetDependencies.filter { !dynamicallyBuildingTargets.contains($0) }.filter {
// Note: we cannot easily get to the "right" build parameters here, but it seems fine to work with the generic ones because we will only inspect settings that shouldn't depend on specific parameters.
let settings = planRequest.buildRequestContext.getCachedSettings(planRequest.buildRequest.parameters, target: $0)
let isObjectFile = settings.globalScope.evaluate(BuiltinMacros.MACH_O_TYPE) == "mh_object"
let isStaticLibrary = settings.globalScope.evaluate(BuiltinMacros.PRODUCT_TYPE) == "com.apple.product-type.library.static"
return isObjectFile || isStaticLibrary
}
guard staticallyLinkedTargets.isEmpty else { continue }
guard let guid = dynamicallyBuildingTargetsWithDiamondLinkage[staticTarget]?.guid else { continue }
for configuredTarget in self.targetGateNodes.keys {
guard configuredTarget.target.guid == guid else { continue }
let dynamicConfiguredTarget = configuredTarget
let staticConfiguredTarget = dynamicConfiguredTarget.replacingTarget(staticTarget)
guard let taskInfo = self.targetGateNodes[dynamicConfiguredTarget] else { continue }
self.targetGateNodes.removeValue(forKey: dynamicConfiguredTarget)
self.targetGateNodes[staticConfiguredTarget] = taskInfo
let impartedBuildProperties = self.impartedBuildPropertiesByTarget[dynamicConfiguredTarget]
self.impartedBuildPropertiesByTarget.removeValue(forKey: dynamicConfiguredTarget)
self.impartedBuildPropertiesByTarget[staticConfiguredTarget] = impartedBuildProperties
self.dynamicallyBuildingTargetsWithDiamondLinkage.removeValue(forKey: staticConfiguredTarget.target)
self.staticallyBuildingTargetsWithDiamondLinkage.removeValue(forKey: dynamicConfiguredTarget.target)
}
}
}
// Checks for duplicated package products or package targets in the graph.
func checkForDiamondProblemsInPackageProductLinkage(dependenciesByTarget: [ConfiguredTarget:OrderedSet<LinkedDependency>], diagnosticDelegate: any TargetDiagnosticProducingDelegate) -> Int {
func emitError(for name: String, targetName: String, andOther: String, conflicts: Bool = false) {
if errorComponentsList.insert(ErrorComponents(name: name, targetName: targetName, andOther: andOther, conflicts: conflicts)).inserted {
if conflicts {
diagnosticDelegate.error("Swift package \(name) is linked as a static library by '\(targetName)' \(andOther), but cannot be built dynamically because there is a package product with the same name.")
} else {
diagnosticDelegate.error("Swift package \(name) is linked as a static library by '\(targetName)' \(andOther). This will result in duplication of library code.")
}
}
}
struct PackageLinkingTargets {
var products: [ConfiguredTarget: Set<ConfiguredTarget>] = [:]
var targets: [ConfiguredTarget: Set<ConfiguredTarget>] = [:]
}
struct PackageDiamondUpdates {
var numberOfDiamonds = 0
var productsToBuildDynamically = [ConfiguredTarget: (SWBCore.PackageProductTarget, SWBCore.Target)]()
var targetsToBuildDynamically = [ConfiguredTarget: (SWBCore.StandardTarget, SWBCore.Target)]()
}
// MARK: - Compute top-level linking boundaries for package products and package targets
let reverseDirectDependenciesByTarget = dependenciesByTarget.reduce(
into: [ConfiguredTarget: Set<ConfiguredTarget>]()
) { result, element in
let (configuredTarget, dependencies) = element
for case .direct(let dependencyTarget) in dependencies {
result[dependencyTarget, default: []].insert(configuredTarget)
}
}
let topLevelLinkingTargets = Set(
dependenciesByTarget.keys.filter { configuredTarget in
let settings = getTargetSettings(configuredTarget)
return Self.dynamicMachOTypes.contains(
settings.globalScope.evaluate(BuiltinMacros.MACH_O_TYPE)
)
}
)
let packageLinkingTargets = {
var results = PackageLinkingTargets()
let resolver = TopLevelLinkingTargetResolver(
reverseDirectDependenciesByTarget: reverseDirectDependenciesByTarget,
topLevelLinkingTargets: topLevelLinkingTargets,
isDynamicallyBuildingTarget: { configuredTarget in
self.dynamicallyBuildingTargets.contains(configuredTarget.target)
}
)
for configuredTarget in reverseDirectDependenciesByTarget.keys.sorted() {
if isStaticallyLinkedPackageProduct(configuredTarget) {
results.products[configuredTarget] = resolver
.resolve(
for: configuredTarget
)
} else if isStaticallyLinkedPackageTarget(configuredTarget) {
results.targets[configuredTarget] = resolver
.resolve(
for: configuredTarget
)
}
}
return results
}()
// MARK: - Determine diamond problems for each dynamic Mach-O target and collect replacement candidates
var packageDiamondUpdates = PackageDiamondUpdates()
for (configuredTarget, dependencies) in dependenciesByTarget {
// We are only interested in targets which link dynamically this time.
let settings = getTargetSettings(configuredTarget)
guard Self.dynamicMachOTypes.contains(settings.globalScope.evaluate(BuiltinMacros.MACH_O_TYPE)) else {
continue
}
// Do not check this target if the diagnostic is disabled.
guard !settings.globalScope.evaluate(BuiltinMacros.DISABLE_DIAMOND_PROBLEM_DIAGNOSTIC) else {
continue
}
let targetsLinkedIntoCurrentDynamicTarget = Set(dependencies.map { $0.target } + [configuredTarget])
func checkLinkage(topLevelTargets: Set<ConfiguredTarget>) -> String? {
// Determine which top-level targets ultimately end up in the current dynamic Mach-O target's linked closure.
let linkedTopLevelTargets = Array(
topLevelTargets.intersection(targetsLinkedIntoCurrentDynamicTarget)
)
.sorted()
if linkedTopLevelTargets.count > 1 {
let andOther: String
if linkedTopLevelTargets.count == 2, let otherTargetName = linkedTopLevelTargets.filter({ $0 != configuredTarget }).first?.target.name {
andOther = "and '\(otherTargetName)'"
} else {
andOther = "and \(linkedTopLevelTargets.count - 1) other targets"
}
return andOther
} else {
return nil
}
}
// Package targets already diagnosed at the product level.
// They are still eligible for dynamic replacement.
// This only suppresses duplicate diagnostics.
var packageTargetsToIgnore = Set<ConfiguredTarget>()
for (product, topLevelTargets) in packageLinkingTargets.products {
if let andOther = checkLinkage(topLevelTargets: topLevelTargets) {
packageTargetsToIgnore.formUnion(dependenciesByTarget[product]?.map { $0.target } ?? [])
let workspaceContext = self.planRequest.workspaceContext
if let packageProductTarget = product.target as? SWBCore.PackageProductTarget, let guid = packageProductTarget.dynamicTargetVariantGuid, let dynamicTarget = workspaceContext.workspace.target(for: guid) {
packageDiamondUpdates.productsToBuildDynamically[product] = (packageProductTarget, dynamicTarget)
} else {
// If we can't determine a dynamic target, still emit the error.
emitError(for: "product '\(product.target.name)'", targetName: configuredTarget.target.name, andOther: andOther)
}
packageDiamondUpdates.numberOfDiamonds += 1
}
}
for (target, topLevelTargets) in packageLinkingTargets.targets {
if let andOther = checkLinkage(topLevelTargets: topLevelTargets) {
let workspaceContext = self.planRequest.workspaceContext
if let standardTarget = target.target as? SWBCore.StandardTarget, let guid = standardTarget.dynamicTargetVariantGuid, let dynamicTarget = workspaceContext.workspace.target(for: guid) {
packageDiamondUpdates.targetsToBuildDynamically[target] = (standardTarget, dynamicTarget)
packageDiamondUpdates.numberOfDiamonds += 1
} else {
if !packageTargetsToIgnore.contains(target) {
// If we can't determine a dynamic target, still emit the error, unless the diagnostic is already covered by the product.
emitError(for: "target '\(target.target.name)'", targetName: configuredTarget.target.name, andOther: andOther, conflicts: self.getTargetSettings(target).globalScope.evaluate(BuiltinMacros.PACKAGE_TARGET_NAME_CONFLICTS_WITH_PRODUCT_NAME))
packageDiamondUpdates.numberOfDiamonds += 1
}
}
}
}
}
// MARK: - Apply the collected replacement candidates to the build graph
let updateConfiguration = { (configuredTarget: ConfiguredTarget, dynamicTarget: SWBCore.Target) in
// If the `targetTaskInfo` for the static target isn't present, we already made the decision to make this target dynamic.
if self.targetGateNodes[configuredTarget] == nil { return }
// There can be multiple configured targets for the dynamic target, we have to update all of them.
let matchingConfiguredTargets = self.targetGateNodes.keys.filter { $0.target.guid == configuredTarget.target.guid }
for matchingTarget in matchingConfiguredTargets {
guard let taskInfo = self.targetGateNodes[matchingTarget] else { return }
let dynamicConfiguredTarget = matchingTarget.replacingTarget(dynamicTarget)