Skip to content
Merged
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
22 changes: 21 additions & 1 deletion Example/HelloWorld/BUILD
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
load("@aspect_bazel_lib//lib:expand_template.bzl", "expand_template_rule")
load("@rules_apple//apple:ios.bzl", "ios_application", "ios_unit_test", "ios_build_test", "ios_ui_test")
load("@rules_apple//apple:ios.bzl", "ios_application", "ios_extension", "ios_unit_test", "ios_build_test", "ios_ui_test")
load("@rules_apple//apple:macos.bzl", "macos_application", "macos_command_line_application", "macos_unit_test")
load("@rules_apple//apple:watchos.bzl", "watchos_application", "watchos_extension", "watchos_unit_test")
load("@rules_cc//cc:defs.bzl", "cc_library")
Expand Down Expand Up @@ -115,6 +115,7 @@ swift_library(
":TodoModelsAlias",
":TodoObjCSupport",
":ExpandedTemplate",
"@yams//:Yams", # External dep to test bzlmod + version suffix in BSP URIs
] + select({
"//conditions:default": [],
":dbg_mode": [":GeneratedDummy"],
Expand Down Expand Up @@ -180,6 +181,15 @@ swift_library(
srcs = [":ExpandTemplateSwiftFile"],
)

## MARK: iOS extension targets

swift_library(
name = "NotificationServiceExtensionLib",
module_name = "NotificationServiceExtensionLib",
srcs = glob(["HelloWorldNotificationExtension/Sources/*.swift"]),
deps = [":TodoModels"],
)

## MARK: WatchOS targets

swift_library(
Expand Down Expand Up @@ -247,9 +257,19 @@ ios_application(
infoplists = ["Resources/Info.plist"],
minimum_os_version = IOS_MINIMUM_OS_VERSION,
watch_application = ":HelloWorldWatchApp",
extensions = [":HelloWorldNotificationServiceExtension"],
deps = [":HelloWorldLib"],
)

ios_extension(
name = "HelloWorldNotificationServiceExtension",
bundle_id = "com.example.HelloWorld.NotificationService",
families = ["iphone", "ipad"],
infoplists = ["Resources/IosExt-Info.plist"],
minimum_os_version = IOS_MINIMUM_OS_VERSION,
deps = [":NotificationServiceExtensionLib"],
)

watchos_application(
name = "HelloWorldWatchApp",
bundle_id = "com.example.HelloWorld.Watch",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import SwiftUI
import TodoModels
import Yams

struct AddTodoView: View {

Expand Down Expand Up @@ -113,6 +114,8 @@ struct AddTodoView: View {
todoTitle = ""
isPresented = false

print("Added task: \(trimmedTitle)")
// Test Yams external dependency
let yaml = try? Yams.dump(object: ["task": trimmedTitle])
print("Added task: \(trimmedTitle), yaml: \(yaml ?? "nil")")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import TodoModels
import UserNotifications

class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?

override func didReceive(
_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

if let bestAttemptContent = bestAttemptContent {
let manager = TodoListManager()
bestAttemptContent.title = "\(bestAttemptContent.title) [\(manager.todoItems.count) todos]"
contentHandler(bestAttemptContent)
}
}

override func serviceExtensionTimeWillExpire() {
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
27 changes: 27 additions & 0 deletions Example/HelloWorld/Resources/IosExt-Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.service</string>
<key>NSExtensionPrincipalClass</key>
<string>NotificationServiceExtensionLib.NotificationService</string>
</dict>
</dict>
</plist>
4 changes: 4 additions & 0 deletions Example/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ archive_override(

bazel_dep(name = "apple_support", version = "2.2.0")
bazel_dep(name = "rules_cc", version = "0.2.16")

# External Swift library to test the external repo case
bazel_dep(name = "yams", version = "5.0.6")

bazel_dep(name = "aspect_bazel_lib", version = "2.13.0")
bazel_dep(name = "rules_multirun", version = "0.13.0")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ final class BazelTargetQuerier {

// We use cquery here because we are interested on what's actually compiled.
// Also, this shares more analysis cache compared to the regular query.
let cmd = "cquery '\(topLevelTargetsQuery)' --noinclude_aspects --notool_deps --noimplicit_deps --output proto"
let cmd =
"cquery '\(topLevelTargetsQuery)' --consistent_labels --noinclude_aspects --notool_deps --noimplicit_deps --output proto"
let output: Data = try commandRunner.bazelIndexAction(
baseConfig: config.baseConfig,
outputBase: config.outputBase,
Expand All @@ -139,7 +140,8 @@ final class BazelTargetQuerier {
workspaceName: config.workspaceName,
executionRoot: config.executionRoot,
toolchainPath: config.devToolchainPath,
outputPath: config.outputPath
outputPath: config.outputPath,
outputBase: config.outputBase
)

logger.debug("Cqueried \(processedCqueryResult.buildTargets.count, privacy: .public) targets")
Expand All @@ -165,6 +167,7 @@ final class BazelTargetQuerier {

let baseFlags =
[
"--consistent_labels",
"--noinclude_artifacts",
"--noinclude_aspects",
] + config.baseConfig.aqueryFlags
Expand Down Expand Up @@ -265,7 +268,8 @@ final class BazelTargetQuerier {
cmd: query,
rootUri: config.rootUri,
additionalFlags: [
"--output=proto"
"--consistent_labels",
"--output=proto",
]
)

Expand All @@ -277,7 +281,8 @@ final class BazelTargetQuerier {
rootUri: config.rootUri,
workspaceName: config.workspaceName,
executionRoot: config.executionRoot,
outputPath: config.outputPath
outputPath: config.outputPath,
outputBase: config.outputBase
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ protocol BazelTargetQuerierParser: AnyObject {
workspaceName: String,
executionRoot: String,
toolchainPath: String,
outputPath: String
outputPath: String,
outputBase: String
) throws -> ProcessedCqueryResult

func processAquery(
Expand All @@ -95,7 +96,8 @@ protocol BazelTargetQuerierParser: AnyObject {
rootUri: String,
workspaceName: String,
executionRoot: String,
outputPath: String
outputPath: String,
outputBase: String
) throws -> ProcessedCqueryAddedFilesResult
}

Expand All @@ -111,7 +113,8 @@ final class BazelTargetQuerierParserImpl: BazelTargetQuerierParser {
workspaceName: String,
executionRoot: String,
toolchainPath: String,
outputPath: String
outputPath: String,
outputBase: String
) throws -> ProcessedCqueryResult {
let cquery = try BazelProtobufBindings.parseCqueryResult(data: data)

Expand Down Expand Up @@ -255,7 +258,7 @@ final class BazelTargetQuerierParserImpl: BazelTargetQuerierParser {
let id = try label.toTargetId(
rootUri: rootUri,
workspaceName: workspaceName,
executionRoot: executionRoot,
outputBase: outputBase,
configMnemonic: configuration
)
bspUriToParentConfigMap[id] = configuration
Expand Down Expand Up @@ -358,7 +361,7 @@ final class BazelTargetQuerierParserImpl: BazelTargetQuerierParser {
canDebug: false
)

let isExternal = rule.name.hasPrefix("@")
let isExternal = rule.name.isExternalBazelLabel()
let tags: [BuildTargetTag] = {
var tags: [BuildTargetTag] = [.library]
if isExternal {
Expand Down Expand Up @@ -866,7 +869,8 @@ extension BazelTargetQuerierParserImpl {
rootUri: String,
workspaceName: String,
executionRoot: String,
outputPath: String
outputPath: String,
outputBase: String
) throws -> ProcessedCqueryAddedFilesResult {
let cquery = try BazelProtobufBindings.parseCqueryResult(data: data)

Expand Down Expand Up @@ -914,7 +918,7 @@ extension BazelTargetQuerierParserImpl {
let id = try displayName.toTargetId(
rootUri: rootUri,
workspaceName: workspaceName,
executionRoot: executionRoot,
outputBase: outputBase,
configMnemonic: configMnemonic
)

Expand All @@ -937,12 +941,12 @@ extension String {
/// Converts a Bazel label into a URI and returns a unique target id.
///
/// For local labels: bazel://<path-to-root>/<package-name>___<target-name>
/// For external labels: bazel://<execution-root>/external/<repo-name>/<package-name>___<target-name>
/// For external labels: bazel://<output-base>/external/<repo-name>/<package-name>___<target-name>
///
fileprivate func toTargetId(
rootUri: String,
workspaceName: String,
executionRoot: String,
outputBase: String,
configMnemonic: String
) throws -> URI {
let (repoName, packageName, targetName) = try splitTargetLabel(workspaceName: workspaceName)
Expand All @@ -953,7 +957,7 @@ extension String {
} else {
// External repo: use execution root + external path
path =
"bazel://" + executionRoot + "/external/" + repoName + packagePath + "/" + targetName + "_"
"bazel://" + outputBase + "/external/" + repoName + packagePath + "/" + targetName + "_"
+ configMnemonic
}
guard let uri = try? URI(string: path) else {
Expand All @@ -980,17 +984,17 @@ extension String {
let repoName: String
let packageName: String

if repoAndPackage.hasPrefix("@//") {
// Alias for the main repo.
repoName = workspaceName
packageName = String(repoAndPackage.dropFirst(3))
} else if repoAndPackage.hasPrefix("//") {
// Also the main repo.
var withoutAt = repoAndPackage
while withoutAt.first == "@" {
withoutAt = withoutAt.dropFirst()
}

if withoutAt.hasPrefix("//") {
// Main repo label.
repoName = workspaceName
packageName = String(repoAndPackage.dropFirst(2))
} else if repoAndPackage.hasPrefix("@") && repoAndPackage.contains("//") {
// External label
let withoutAt = repoAndPackage.dropFirst()
packageName = String(withoutAt.dropFirst(2))
} else if !withoutAt.isEmpty {
// External repo label.
guard let slashIndex = withoutAt.firstIndex(of: "/") else {
throw BazelTargetQuerierParserError.incorrectName(self)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ extension BazelTargetStoreImpl {
}()
reportTopLevel.append(
.init(
label: label,
label: label.removingLeadingAtForMainRepoBazelLabel(),
launchType: launchType,
configMnemonic: configMnemonic,
testSources: testSources
Expand Down Expand Up @@ -429,7 +429,7 @@ extension BazelTargetStoreImpl {
}
reportDependencies.append(
.init(
label: label,
label: label.removingLeadingAtForMainRepoBazelLabel(),
configMnemonic: configMnemonic,
topLevelParent: topLevelParent,
extraBuildArgs: extraBuildArgs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,11 @@ enum IndexOutputPathBuilder {

// Extract external repo name if present (e.g., "@abseil-cpp" from "@abseil-cpp//absl/base")
let externalRepoName: String?
if beforeColon.hasPrefix("@") {
let afterAt = beforeColon.dropFirst() // drop "@"
if beforeColon.isExternalBazelLabel() {
var afterAt = beforeColon.dropFirst()
while afterAt.first == "@" {
afterAt = afterAt.dropFirst()
}
if let slashIdx = afterAt.firstIndex(of: "/") {
externalRepoName = String(afterAt[..<slashIdx])
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,15 +202,25 @@ final class BazelTargetCompilerArgsExtractor {
fromAquery aquery: ProcessedAqueryResult,
strategy: ParsingStrategy
) throws -> Analysis_Action {
let bazelTarget: String = {
let base = platformInfo.label
guard base.hasPrefix("@") else {
return base
}
// External labels show up as `@@` in the aquery.
return "@\(base)"
}()
guard let target = aquery.targets[bazelTarget] else {
let bazelTarget: String = platformInfo.label
// The aquery protobuf uses labels without the @ prefix for main repo targets even when
// --consistent_labels is used, but cquery returns labels with @@ or @ prefix.
// External repos keep their @@ prefix in both. Normalize labels for lookup.
// Older Bazel versions may return single @ for external repos, so try both formats.
let normalizedTarget: String
if bazelTarget.hasPrefix("@@//") {
normalizedTarget = String(bazelTarget.dropFirst(2))
} else if bazelTarget.hasPrefix("@//") {
normalizedTarget = String(bazelTarget.dropFirst(1))
} else if bazelTarget.hasPrefix("@") && !bazelTarget.hasPrefix("@@") {
// External repo with single @ - convert to @@ for lookup
normalizedTarget = "@" + bazelTarget
} else {
normalizedTarget = bazelTarget
}
// Try normalized target first, then fall back to original for compatibility
let targetToLookup = aquery.targets[normalizedTarget] ?? aquery.targets[bazelTarget]
guard let target = targetToLookup else {
throw BazelTargetCompilerArgsExtractorError.targetNotFound(bazelTarget)
}
guard let actions = aquery.actions[target.id] else {
Expand Down
Loading
Loading