@@ -472,9 +472,12 @@ extension SpecializationParameters {
472472 let specializationParams : SpecializationParameters
473473 }
474474
475- /// Cached build and specialization parameters created for each platform.
476- /// This is populated only for an index build.
477- let platformBuildParameters : [ PlatformBuildParameters ]
475+ /// Cached build and specialization parameters created for each platform. This is populated only for an index
476+ /// workspace build.
477+ let platformBuildParametersForIndex : [ PlatformBuildParameters ]
478+
479+ /// Cached parameters for the host platform. This is only populated for an index workspace build.
480+ let hostParametersForIndex : PlatformBuildParameters ?
478481
479482 /// Targets which should build dynamically according to the build request.
480483 /// This is only used by Playgrounds and previews.
@@ -505,8 +508,11 @@ extension SpecializationParameters {
505508 self . defaultImposedParameters = SpecializationParameters . default ( workspaceContext: workspaceContext, buildRequestContext: buildRequestContext, parameters: buildRequest. parameters)
506509
507510 if buildRequest. enableIndexBuildArena {
511+ let hostOS = ( try ? workspaceContext. core. hostOperatingSystem. xcodePlatformName) ?? " unknown "
512+
508513 // Create the build parameters for each available platform.
509514 var platformBuildParameters : [ PlatformBuildParameters ] = [ ]
515+ var hostBuildParameters : PlatformBuildParameters ? = nil
510516 for platform in workspaceContext. core. platformRegistry. platforms {
511517 // Find the corresponding SDK for this platform
512518 let potentialSDKNames = [ platform . sdkCanonicalName ] . compactMap { $0 } + workspaceContext. core. sdkRegistry. supportedSDKCanonicalNameSuffixes ( ) . compactMap {
@@ -528,11 +534,18 @@ extension SpecializationParameters {
528534 let buildParams = buildRequest. parameters. replacing ( activeRunDestination: runDestination, activeArchitecture: archName)
529535 let specializationParams = SpecializationParameters . default ( workspaceContext: workspaceContext, buildRequestContext: buildRequestContext, parameters: buildParams)
530536 platformBuildParameters. append ( PlatformBuildParameters ( buildParams: buildParams, specializationParams: specializationParams) )
537+
538+ if runDestination. platform == hostOS && runDestination. sdkVariant == matchingSDK. defaultVariant? . name {
539+ hostBuildParameters = platformBuildParameters. last
540+ }
531541 }
532542 }
533- self . platformBuildParameters = platformBuildParameters
543+
544+ self . platformBuildParametersForIndex = platformBuildParameters
545+ self . hostParametersForIndex = hostBuildParameters
534546 } else {
535- self . platformBuildParameters = [ ]
547+ self . platformBuildParametersForIndex = [ ]
548+ self . hostParametersForIndex = nil
536549 }
537550
538551 self . dynamicallyBuildingTargets = Set ( buildRequest. buildTargets. filter {
@@ -582,7 +595,7 @@ extension SpecializationParameters {
582595 let unconfiguredSettings = buildRequestContext. getCachedSettings ( targetInfo. parameters, target: target)
583596 let unconfiguredSupportedPlatforms = unconfiguredSettings. globalScope. evaluate ( BuiltinMacros . SUPPORTED_PLATFORMS)
584597
585- for platformParams in platformBuildParameters {
598+ for platformParams in platformBuildParametersForIndex {
586599 // Before forming all new settings for this platform, do a fast check using `SUPPORTED_PLATFORMS` of `unconfiguredSettings`.
587600 // This significantly cuts down the work that this function is doing.
588601 if platformParams. buildParams. activeRunDestination? . sdkVariant == MacCatalystInfo . sdkVariantName {
@@ -597,7 +610,7 @@ extension SpecializationParameters {
597610 }
598611 }
599612
600- guard isTargetSuitableForPlatform ( target, parameters: platformParams. buildParams, imposedParameters: platformParams. specializationParams) else {
613+ guard isTargetSuitableForPlatformForIndex ( target, parameters: platformParams. buildParams, imposedParameters: platformParams. specializationParams) else {
601614 continue
602615 }
603616 // Configure the target for this platform.
@@ -607,27 +620,65 @@ extension SpecializationParameters {
607620 return configuredTargets
608621 }
609622
610- nonisolated func isTargetSuitableForPlatform( _ target: Target , parameters: BuildParameters , imposedParameters: SpecializationParameters ? ) -> Bool {
623+ /// Determines whether a target should be configured for the given platform in the index arena.
624+ ///
625+ /// The arena is used for two purposes:
626+ /// 1. To retrieve settings for a given target
627+ /// 2. To produce products of source dependencies for compilation purposes (it does not produce binaries)
628+ ///
629+ /// Thus, in general if a target doesn't support a platform, we don't want to configure it for that platform. If a
630+ /// dependency is not supported for the platform of the dependent, presumably the dependent will not be able
631+ /// to use its products for compilation purposes, since the source products will be put in a different platform
632+ /// directory and/or they will not be usable by the dependent (e.g. the module will not be importable from a
633+ /// different platform). If the dependency was intended to be usable from that platform for compilation purposes,
634+ /// it would be a supported platform.
635+ ///
636+ /// There's an exception for this for a dependent host tool, which are required for compilation and must therefore
637+ /// be configured (and registered as a dependency) regardless.
638+ nonisolated func isTargetSuitableForPlatformForIndex( _ target: Target , parameters: BuildParameters , imposedParameters: SpecializationParameters ? , dependencies: OrderedSet < ConfiguredTarget > ? = nil ) -> Bool {
639+ if !buildRequest. enableIndexBuildArena {
640+ return true
641+ }
642+
643+ // Host tools case, always supported we'll override the parameters with that of the host regardless.
644+ if target. isHostBuildTool || dependencies? . contains ( where: { $0. target. isHostBuildTool } ) == true {
645+ return true
646+ }
647+
648+ guard let runDestination = parameters. activeRunDestination else { return false }
649+
611650 // Filter any overrides from the parameters, because they can shadow any original "auto" values.
612651 let filteredParameters = parameters. withoutOverrides
613652 var settings = buildRequestContext. getCachedSettings ( filteredParameters, target: target)
653+
614654 if settings. enableTargetPlatformSpecialization, let imposedParameters {
615655 // This is for targets that have SDKROOT set to 'auto' (like Swift Packages) and get settings "imposed" on them.
616656 settings = buildRequestContext. getCachedSettings ( imposedParameters. imposed ( on: filteredParameters, workspaceContext: workspaceContext) , target: target)
617657 }
618- // Check that the target supports this platform.
619- guard let targetPlatform = settings. platform else { return false }
620- guard targetPlatform. name == parameters. activeRunDestination? . platform else { return false }
621- guard settings. sdkVariant == nil || settings. sdkVariant? . name == parameters. activeRunDestination? . sdkVariant else { return false }
658+
659+ guard let targetPlatform = settings. platform else {
660+ return false
661+ }
662+ if targetPlatform. name != runDestination. platform {
663+ return false
664+ }
665+ if settings. sdkVariant? . name != runDestination. sdkVariant {
666+ return false
667+ }
668+
622669 let supportedPlatforms = settings. globalScope. evaluate ( BuiltinMacros . SUPPORTED_PLATFORMS)
623- if supportedPlatforms. count > 0 {
624- // Without this additional check, Swift package targets would be configured for simulator platforms, even if only 'macosx' is supported.
625- guard supportedPlatforms. contains ( targetPlatform. name) else { return false }
670+ if !supportedPlatforms. isEmpty && !supportedPlatforms. contains ( targetPlatform. name) {
671+ return false
626672 }
673+
627674 return true
628675 }
629676
630677 func compatibleConfiguredTarget( _ dependency: Target , for parameters: BuildParameters , baseTarget: Target ) -> ConfiguredTarget ? {
678+ guard let configuredTargetsForDependency = configuredTargetsByTarget [ dependency] else {
679+ return nil
680+ }
681+
631682 // Check compatibility with the `baseTarget`, so get the settings for it.
632683 let settings = buildRequestContext. getCachedSettings ( parameters, target: baseTarget)
633684 let platform = Ref ( settings. platform)
@@ -636,9 +687,11 @@ extension SpecializationParameters {
636687 let unimposedSettingsOfDependency = buildRequestContext. getCachedSettings ( parameters. withoutOverrides, target: dependency)
637688 let dependencyHasAutoSDKRoot = unimposedSettingsOfDependency. enableTargetPlatformSpecialization
638689
639- let configuredTargetsForDependency = configuredTargetsByTarget [ dependency]
640- let compatibleTargets = configuredTargetsForDependency? . filter {
641- let dependencySettings = buildRequestContext. getCachedSettings ( $0. parameters, target: $0. target)
690+ let compatibleTargets = configuredTargetsForDependency. filter { ct in
691+ if ct. specializeGuidForActiveRunDestination && parameters. activeRunDestination != ct. parameters. activeRunDestination {
692+ return false
693+ }
694+ let dependencySettings = buildRequestContext. getCachedSettings ( ct. parameters, target: ct. target)
642695 let dependencyPlatform = dependencySettings. platform
643696 let dependencySdkVariant = dependencySettings. sdkVariant? . name
644697 guard Ref ( dependencyPlatform) == platform && dependencySdkVariant == sdkVariant else { return false }
@@ -649,7 +702,9 @@ extension SpecializationParameters {
649702 }
650703 return true
651704 }
652- return compatibleTargets? . first
705+
706+ // Make sure the returned target is stable
707+ return compatibleTargets. only ?? Array ( compatibleTargets) . sorted ( ) . first
653708 }
654709
655710 /// Find the configured target for a build request item.
@@ -676,13 +731,20 @@ extension SpecializationParameters {
676731 for previousConfiguredTarget in previousConfiguredTargets {
677732 let previousSettings = buildRequestContext. getCachedSettings ( previousConfiguredTarget. parameters, target: previousConfiguredTarget. target)
678733 if currentSettings. platform? . displayName == previousSettings. platform? . displayName && currentSettings. sdkVariant? . name == previousSettings. sdkVariant? . name {
679- // We do allow multiple configured targets with different SDK suffixes, this gets sorted out at the very
734+ // Allow multiple configured targets with separate run destinations (this is only the case for
735+ // the workspace build description today).
736+ if previousConfiguredTarget. specializeGuidForActiveRunDestination && parameters. activeRunDestination != previousConfiguredTarget. parameters. activeRunDestination {
737+ continue
738+ }
739+ // We allow multiple configured targets with different SDK suffixes, this gets sorted out at the very
680740 // end of computing the dependency graph.
681- if currentSettings. sdk? . canonicalNameSuffix == previousSettings. sdk? . canonicalNameSuffix {
682- let data = DiagnosticData ( " multiple configured targets of ' \( target. name) ' are being created for \( currentSettings. platform? . displayName ?? " " ) " )
683- delegate. emit ( Diagnostic ( behavior: . error, location: . unknown, data: data) )
684- hasMultipleTargets = true
741+ if currentSettings. sdk? . canonicalNameSuffix != previousSettings. sdk? . canonicalNameSuffix {
742+ continue
685743 }
744+
745+ let data = DiagnosticData ( " multiple configured targets of ' \( target. name) ' are being created for \( currentSettings. platform? . displayName ?? " " ) " )
746+ delegate. emit ( Diagnostic ( behavior: . error, location: . unknown, data: data) )
747+ hasMultipleTargets = true
686748 }
687749 }
688750
@@ -709,14 +771,28 @@ extension SpecializationParameters {
709771 return lookupConfiguredTarget ( forTarget, parameters: parameters, superimposedProperties: nil )
710772 }
711773
774+ var parameters = parameters
775+ if buildRequest. enableIndexBuildArena {
776+ // Avoid forced propagation of "auto" overrides to targets that don't need them. If this is a host tool,
777+ // force its parameters to the host platform to avoid creating duplicate configured targets after it has
778+ // been specialized.
779+ let platformParams : BuildParameters
780+ if forTarget. isHostBuildTool {
781+ platformParams = hostParametersForIndex? . buildParams ?? parameters
782+ } else {
783+ platformParams = parameters
784+ }
785+ parameters = buildRequest. parameters. replacing ( activeRunDestination: platformParams. activeRunDestination, activeArchitecture: platformParams. activeArchitecture)
786+ }
787+
712788 // If we have an existing target compatible with the specialization parameters, we always return it.
713789 //
714790 // This is unfortunate, because it introduces an ordering dependency on the computation, but it is critical for correctness because we want to ensure that we only create new targets when it results in a meaningful difference, not just because the hash of the build parameters differs.
715791 //
716792 // Checking !isTopLevelLookup prevents incorrectly returning a "compatible" target when looking up a top level target where the run destination may be influencing the platform to become iOS vs Mac Catalyst. Previously, this only worked by accident due to concurrency, where top level lookups never returned an existing target due to configuredTargetsByTarget virtually always being empty due to the thread interaction.
717793 if let configuredTargets = configuredTargetsByTarget [ forTarget] , !isTopLevelLookup {
718794 for ct in configuredTargets {
719- if buildRequest . enableIndexBuildArena && parameters. activeRunDestination != ct. parameters. activeRunDestination { continue }
795+ if ct . specializeGuidForActiveRunDestination && parameters. activeRunDestination != ct. parameters. activeRunDestination { continue }
720796 let parametersWithoutRunDestination = ct. parameters. replacing ( activeRunDestination: nil , activeArchitecture: nil )
721797 let settings = buildRequestContext. getCachedSettings ( parametersWithoutRunDestination, target: ct. target)
722798 if specialization. isCompatible ( with: ct, settings: settings, workspaceContext: workspaceContext) {
@@ -728,8 +804,6 @@ extension SpecializationParameters {
728804 }
729805 }
730806
731- // For index build, avoid forced propagation of "auto" overrides to targets that don't need them.
732- let parameters = buildRequest. enableIndexBuildArena ? buildRequest. parameters. replacing ( activeRunDestination: parameters. activeRunDestination, activeArchitecture: parameters. activeArchitecture) : parameters
733807 // Filter any overrides from the parameters, because they can shadow any original "auto" values.
734808 let filteredParameters = parameters. withoutOverrides
735809
@@ -797,8 +871,7 @@ extension SpecializationParameters {
797871 // a target which doesn't support building for that variant. This is complicated by the fact that
798872 // iOS targets get SUPPORTS_MACCATALYST by default, but SDKROOT=auto targets get an inconsistent view.
799873
800- let isHostTool = settings. productType? . conformsTo ( identifier: " com.apple.product-type.tool.host-build " ) == true
801- if specializationIsSupported && !isHostTool {
874+ if specializationIsSupported && !forTarget. isHostBuildTool {
802875 imposedSdkVariant = specialization. sdkVariant ?? imposedPlatform? . defaultSDKVariant
803876 } else {
804877 imposedSdkVariant = imposedPlatform? . defaultSDKVariant
@@ -1031,3 +1104,10 @@ extension Platform {
10311104 return sdks. filter { $0. isBaseSDK && $0. canonicalName. hasPrefix ( sdkCanonicalName) } . first? . defaultVariant
10321105 }
10331106}
1107+
1108+ fileprivate extension Target {
1109+ var isHostBuildTool : Bool {
1110+ guard let standardTarget = self as? StandardTarget else { return false }
1111+ return ProductTypeIdentifier ( standardTarget. productTypeIdentifier) . isHostBuildTool
1112+ }
1113+ }
0 commit comments