Skip to content
Draft
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
15 changes: 15 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ let package = Package(
name: "Nubrick",
targets: ["Nubrick"]
),
.plugin(
name: "NubrickDevTool",
targets: ["NubrickDevTool"]
),
],
dependencies: [
.package(url: "https://github.com/facebook/yoga.git", .upToNextMinor(from: "3.2.1")),
Expand Down Expand Up @@ -47,6 +51,17 @@ let package = Package(
name: "NubrickTests",
dependencies: ["NubrickLocal"]
),

// Build tool plugin for extracting embedding IDs at compile time
.plugin(
name: "NubrickDevTool",
capability: .buildTool(),
dependencies: ["NubrickDevToolRunner"]
),
.executableTarget(
name: "NubrickDevToolRunner",
path: "Plugins/NubrickDevToolRunner"
),
],
cxxLanguageStandard: CXXLanguageStandard(rawValue: "c++20")
)
24 changes: 24 additions & 0 deletions Plugins/NubrickDevTool/plugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import PackagePlugin
import Foundation

@main
struct NubrickDevTool: BuildToolPlugin {
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
guard let sourceTarget = target as? SourceModuleTarget else { return [] }
let swiftFiles = sourceTarget.sourceFiles(withSuffix: "swift").map(\.path)
guard !swiftFiles.isEmpty else { return [] }

let tool = try context.tool(named: "NubrickDevToolRunner")
let output = context.pluginWorkDirectory.appending("NubrickEmbeddingIds.generated.swift")

return [
.buildCommand(
displayName: "NubrickDevTool: Extracting embedding IDs",
executable: tool.path,
arguments: ["--output", output.string] + swiftFiles.map { $0.string },
inputFiles: swiftFiles,
outputFiles: [output]
)
]
}
}
61 changes: 61 additions & 0 deletions Plugins/NubrickDevToolRunner/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import Foundation

func extractEmbeddingIds(from source: String) -> [String] {
let pattern = #"NubrickSDK\s*\.\s*embedding(?:UIView)?\s*\(\s*"((?:[^"\\]|\\.)*)""#
guard let regex = try? NSRegularExpression(pattern: pattern) else { return [] }
let ns = source as NSString
let matches = regex.matches(in: source, range: NSRange(location: 0, length: ns.length))
return matches.compactMap { match -> String? in
guard match.numberOfRanges > 1, match.range(at: 1).location != NSNotFound else { return nil }
let raw = ns.substring(with: match.range(at: 1))
guard !raw.isEmpty else { return nil }
return raw.replacingOccurrences(of: #"\""#, with: "\"")
.replacingOccurrences(of: #"\\"#, with: "\\")
}
}

func generateSwiftFile(ids: [String]) -> String {
let escaped = ids.joined(separator: ",")
.replacingOccurrences(of: "\\", with: "\\\\")
.replacingOccurrences(of: "\"", with: "\\\"")
return """
// Generated by NubrickDevTool — DO NOT EDIT.
import Foundation

@_cdecl("_nubrick_embedding_ids")
public func _nubrickEmbeddingIds() -> UnsafeMutablePointer<CChar>? {
strdup("\(escaped)")
}

"""
}

// MARK: - Main

guard CommandLine.arguments.count >= 3,
CommandLine.arguments[1] == "--output" else {
fputs("Usage: NubrickDevToolRunner --output <path> <swift files...>\n", stderr)
exit(1)
}

let outputPath = CommandLine.arguments[2]
let inputFiles = Array(CommandLine.arguments.dropFirst(3))

var allIds: [String] = []
var seen = Set<String>()

for filePath in inputFiles {
guard let data = FileManager.default.contents(atPath: filePath),
let source = String(data: data, encoding: .utf8) else {
continue
}
for id in extractEmbeddingIds(from: source) {
if seen.insert(id).inserted {
allIds.append(id)
}
}
}

let generated = generateSwiftFile(ids: allIds)
let outputURL = URL(fileURLWithPath: outputPath)
try! generated.write(to: outputURL, atomically: true, encoding: .utf8)
15 changes: 15 additions & 0 deletions Sources/Nubrick/Data/embedding-discovery.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

private typealias EmbeddingIdsFunc = @convention(c) () -> UnsafeMutablePointer<CChar>?

func discoverBuildTimeEmbeddingIds() -> [String] {
guard let handle = dlopen(nil, RTLD_LAZY) else { return [] }
defer { dlclose(handle) }
guard let sym = dlsym(handle, "_nubrick_embedding_ids") else { return [] }
let fn = unsafeBitCast(sym, to: EmbeddingIdsFunc.self)
guard let cStr = fn() else { return [] }
defer { free(cStr) }
let joined = String(cString: cStr)
guard !joined.isEmpty else { return [] }
return joined.components(separatedBy: ",").filter { !$0.isEmpty }
}
12 changes: 12 additions & 0 deletions Sources/Nubrick/Data/track.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ public enum CrashSeverity: String, Sendable {
protocol TrackRepository2 : Actor {
func trackExperimentEvent(_ event: TrackExperimentEvent)
func trackEvent(_ event: TrackUserEvent)
func trackDiscoveredEmbeddingIds(_ ids: [String])

func processMetricKitCrash(
callStackTreeJSON: Data,
Expand All @@ -171,6 +172,7 @@ struct TrackEvent: Encodable {
case Event = "event"
case Experiment = "experiment"
case Crash = "crash"
case EmbeddingDiscovery = "embedding_discovery"
}
var typename: Typename
var experimentId: String?
Expand All @@ -182,6 +184,7 @@ struct TrackEvent: Encodable {
var platform: String?
var flutterSdkVersion: String?
var severity: String?
var embeddingIds: [String]?
}

@_spi(FlutterBridge)
Expand Down Expand Up @@ -255,6 +258,15 @@ actor TrackRespositoryImpl: TrackRepository2 {
timestamp: getCurrentDate().ISO8601Format()
))
}

func trackDiscoveredEmbeddingIds(_ ids: [String]) {
guard !ids.isEmpty else { return }
self.pushToQueue(TrackEvent(
typename: .EmbeddingDiscovery,
timestamp: getCurrentDate().ISO8601Format(),
embeddingIds: ids
))
}

private func scheduleFlushTaskIfNeeded() {
guard self.flushTask == nil else {return }
Expand Down
13 changes: 13 additions & 0 deletions Sources/Nubrick/sdk.swift
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,12 @@ final class NubrickCore {
}
}

func sendDiscoveredEmbeddingIds(_ ids: [String]) {
Task {
await self.dependencies.trackRepository.trackDiscoveredEmbeddingIds(ids)
}
}

func setUserProperties(_ properties: [String: Any]) {
self.dependencies.user.setProperties(properties)
}
Expand Down Expand Up @@ -469,6 +475,13 @@ public enum NubrickSDK {

self.runtime = runtime

#if DEBUG
let discoveredIds = discoverBuildTimeEmbeddingIds()
if !discoveredIds.isEmpty {
runtime.sendDiscoveredEmbeddingIds(discoveredIds)
}
#endif

if trackCrashes {
AppMetrics.shared.register()
}
Expand Down
Loading