Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,17 @@ final class BazelTargetCompilerArgsExtractor {
}
return config.mnemonic == platformInfo.topLevelParentConfig.configurationName
}
// When a dependency exists under multiple top-level targets, each gets a unique
// settings transition hash (the -ST-<hex> suffix in the config mnemonic).
// The parent config stored during cquery may not match the one from aquery.
// Fall back to matching without the ST hash — same platform/arch/minOS.
if candidateActions.isEmpty {
let requestedBase = platformInfo.topLevelParentConfig.configurationName.strippingSettingsTransitionHash
candidateActions = actions.filter {
guard let config = aquery.configurations[$0.configurationID] else { return false }
return config.mnemonic.strippingSettingsTransitionHash == requestedBase
}
}
let contentBeingQueried: String
switch strategy {
case .swiftModule(_), .cHeader:
Expand All @@ -245,13 +256,12 @@ final class BazelTargetCompilerArgsExtractor {
return false
}
}
guard candidateActions.count > 0 else {
// After all filtering, multiple candidates means different ST variants
// for the same platform/file — compiler args are effectively identical.
guard let action = candidateActions.first else {
throw BazelTargetCompilerArgsExtractorError.relevantTargetActionsNotFound(contentBeingQueried)
}
guard candidateActions.count == 1 else {
throw BazelTargetCompilerArgsExtractorError.multipleTargetActions(contentBeingQueried, target.id)
}
return candidateActions[0]
return action
}

func clearCache() {
Expand Down Expand Up @@ -446,3 +456,17 @@ extension BazelTargetCompilerArgsExtractor {
lines[idx + 1] = new
}
}

extension String {
/// Strips the Bazel settings transition hash suffix from a configuration mnemonic.
///
/// Bazel appends a `-ST-<hex>` suffix to configuration mnemonics to distinguish builds
/// that share the same platform/arch/minOS but have different build settings due to
/// different top-level targets (e.g., an app vs. an extension). The hash is deterministic
/// for a given set of settings but differs across top-level targets.
///
/// See: https://bazel.build/extending/config#user-defined-transitions
fileprivate var strippingSettingsTransitionHash: String {
self.replacingOccurrences(of: #"-ST-[a-f0-9]+$"#, with: "", options: .regularExpression)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,59 @@ struct BazelTargetCompilerArgsExtractorTests {
#expect(language == "objective-c")
}

@Test
func swiftModuleWithDifferentSTHash() throws {
let extractor = Self.makeMockExtractor()
let testSourceUri = URI(
filePath: "/Users/user/Documents/demo-ios-project/HelloWorld/HelloWorldLib/Sources/AddTodoView.swift",
isDirectory: false
)
// Use a different ST hash than the one in the aquery (2842469f5300).
// This simulates a dependency that exists under multiple top-level targets
// where the cquery stored a different parent's config than the aquery has.
let differentSTConfig = BazelTargetConfigurationInfo(
configurationName: "ios_sim_arm64-dbg-ios-sim_arm64-min17.0-ST-aaaaaaaaaaaa",
minimumOsVersion: "17.0",
platform: "iphonesimulator",
cpuArch: "sim_arm64",
sdkName: "iphonesimulator"
)
let result = try extractor.extractCompilerArgs(
fromAquery: aqueryResult,
forTarget: BazelTargetPlatformInfo(
label: "//HelloWorld:HelloWorldLib",
topLevelParentLabel: "//HelloWorld:HelloWorld",
topLevelParentConfig: differentSTConfig
),
withStrategy: .swiftModule(testSourceUri),
indexOutputPath: nil
)
#expect(!result.isEmpty)
}

@Test
func objcFileWithDifferentSTHash() throws {
let extractor = Self.makeMockExtractor()
let differentSTConfig = BazelTargetConfigurationInfo(
configurationName: "ios_sim_arm64-dbg-ios-sim_arm64-min17.0-ST-bbbbbbbbbbbb",
minimumOsVersion: "17.0",
platform: "iphonesimulator",
cpuArch: "sim_arm64",
sdkName: "iphonesimulator"
)
let result = try extractor.extractCompilerArgs(
fromAquery: aqueryResult,
forTarget: BazelTargetPlatformInfo(
label: "//HelloWorld:TodoObjCSupport",
topLevelParentLabel: "//HelloWorld:HelloWorld",
topLevelParentConfig: differentSTConfig
),
withStrategy: .cImpl("HelloWorld/TodoObjCSupport/Sources/SKObjCUtils.m", "objective-c"),
indexOutputPath: nil
)
#expect(!result.isEmpty)
}

@Test
func missingSwiftModule() throws {
let extractor = Self.makeMockExtractor()
Expand Down
Loading