Skip to content

Commit a0f7bf1

Browse files
committed
Always add linkage dependencies to host tool targets for the index arena
Settings are propagated to dependent targets through the linkage graph rather than the target graph (unless `UseTargetDependenciesForImpartedBuildSettings` is set). While the target graph was updated to allow host tool dependencies even when configuring for a different platform in the index arena, the linkage graph was not. This meant that we'd miss imparting settings in this case (eg. include paths up from dependencies) and thus fail to build the host tool in cases this is required. As the host tool is built without the preparation settings (since it needs to actually build the executable), this would then continually run for every preparation request. The fix here is to add linkage dependencies for host tools as well and to unify the two "is compatible for index arena" checks to avoid this in the future. That exposes another issue, however, which is that doing so would then result in having multiple configured targets all with the same (host) platform. Avoid that by forcing the parameters to that of the host platform for the index arena. Resolves rdar://140882270.
1 parent 5aeca6f commit a0f7bf1

File tree

4 files changed

+246
-98
lines changed

4 files changed

+246
-98
lines changed

Sources/SWBCore/DependencyResolution.swift

Lines changed: 109 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)