Skip to content

Commit 2d541ed

Browse files
Stop requiring unpublished native runtimes for Flutter consumers
Compile Android and Apple capture runtimes from monorepo sources inside the plugin, and stub native capture when those trees are absent. Drop the Maven and CocoaPods coordinates, keep the plugin iOS floor at 12, and wait for PixelCopy callbacks before recycling the destination bitmap. Co-authored-by: Chinmay Kabi <chinmay@blend.to>
1 parent ac754fd commit 2d541ed

24 files changed

Lines changed: 438 additions & 61 deletions

File tree

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ The Tugboat CLI is maintained separately.
2424
| `docs/` | Architecture, integration, privacy, performance, releases |
2525
| `tool/ci` | Host test, generate, and release-control scripts |
2626

27-
Do not copy the C++ core into the pub package. Flutter consumes the Android
28-
AAR and the Apple CocoaPod (local path during development) rather than
29-
vendoring native sources.
27+
Do not copy the C++ core into the published pub package. In this monorepo the
28+
Flutter plugin compiles Android and Apple runtimes from `platforms/` (and
29+
`core/`) when those trees resolve. Published pub consumers get a stub HostApi
30+
that reports unsupported native capture and keep the default Flutter backend.
3031

3132
## Capture backends
3233

@@ -62,8 +63,9 @@ bash tool/ci/build-android-runtime.sh
6263
bash tool/ci/run-flutter-tests.sh
6364
```
6465

65-
The Android AAR publishes to untracked `.local-maven/`. Run the Android build
66-
before `flutter build apk` on the example.
66+
Native Android apps still consume the AAR from untracked `.local-maven/` after
67+
`bash tool/ci/build-android-runtime.sh`. The Flutter plugin no longer needs
68+
that artifact; it compiles `platforms/android/capture-runtime` from source.
6769

6870
## Development
6971

docs/architecture/repository-scope.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,10 @@ In scope for the Android and Apple CPU betas:
9696
- Send mask metadata and previous dHash; receive masked JPEG only
9797
- Automatic fallback; never publish native and Flutter results for one request
9898
- Preserve session schema, frame transport, scheduling, and mask policy
99-
- Android: local Maven AAR. iOS: local CocoaPods path to
100-
`TugboatCaptureRuntime`
99+
- Android: the Flutter plugin compiles `capture-runtime` from source in the
100+
monorepo. Native Android apps still use the local Maven AAR. iOS: the
101+
plugin compiles `TugboatCaptureRuntime` sources; the root CocoaPod remains
102+
for native Apple apps.
101103

102104
`TugboatCaptureBoundary` stays. Flutter may still supply mask geometry and
103105
capture scheduling.

docs/integration/android-development.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Kotlin or C ABI change, bump the Maven version and regenerate `api.txt`:
2323
python3 tool/ci/dump-android-runtime-api.py > platforms/android/capture-runtime/api.txt
2424
```
2525

26-
The Flutter plugin at `sdks/flutter/packages/tugboat/android` wraps
27-
`CaptureRuntime` behind the Pigeon `NativeCaptureHostApi`. Do not log pixel
28-
or JPEG data.
26+
The Flutter plugin at `sdks/flutter/packages/tugboat/android` compiles
27+
`CaptureRuntime` from this source tree (no unpublished Maven coordinate) and
28+
wraps it behind the Pigeon `NativeCaptureHostApi`. Published pub checkouts
29+
without `platforms/android` stub native capture as unsupported. Do not log
30+
pixel or JPEG data.

docs/integration/apple-development.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,14 @@ Experimental CPU path for iOS 15+. Default Flutter capture remains
1414
| `platforms/apple/Sources/TugboatImageCoreBridge` | Objective-C++ bridge to the C ABI |
1515
| `platforms/apple/Tests` | XCTest (MaskMapper, runtime constants) |
1616
| `platforms/apple/Sample` | Usage notes, not a full Xcode app |
17-
| `sdks/flutter/packages/tugboat/ios` | Pigeon plugin; depends on the CocoaPod |
18-
19-
Do not copy `core/image-processing` into the pub package. The Flutter example
20-
uses a local CocoaPods path override:
21-
22-
```ruby
23-
pod 'TugboatCaptureRuntime', :path => File.expand_path('../../../../../../', File.dirname(__FILE__))
24-
```
25-
26-
in `sdks/flutter/packages/tugboat/example/ios/Podfile`.
17+
| `sdks/flutter/packages/tugboat/ios` | Pigeon plugin; compiles Apple sources via `NativeRuntime` when present |
18+
19+
Do not copy `core/image-processing` into the published pub package. The
20+
Flutter plugin podspec does not depend on unpublished `TugboatCaptureRuntime`.
21+
Monorepo checkouts compile Apple runtime sources through
22+
`ios/NativeRuntime` (symlinks into `platforms/apple` and `core/`). The plugin
23+
deployment target stays iOS 12; native capture still reports unsupported
24+
below iOS 15.
2725

2826
## Capture
2927

docs/integration/common-build.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ bash tool/ci/run-flutter-tests.sh
1010
bash tool/ci/verify-native-capture-pigeon.sh
1111
```
1212

13-
The Android AAR publishes to untracked `.local-maven/`. The Flutter Android
14-
plugin consumes `com.tugboat.sdk:capture-runtime:0.1.0` from that repository.
15-
Run `build-android-runtime.sh` before `flutter build apk` on the example.
13+
The Android AAR publishes to untracked `.local-maven/` for native Android
14+
apps and the sample. The Flutter plugin compiles `capture-runtime` from
15+
source when `platforms/android` is present, so example APKs do not need
16+
that Maven artifact.
1617

1718
Release-control scripts (also run in CI):
1819

docs/integration/flutter-development.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ outputs.
2020

2121
Screenshot pixels still default to `RepaintBoundary`. Enable native CPU
2222
capture only through
23-
`TugboatScreenshotCaptureBackend.nativeCpuExperimental`. Android consumes
24-
the local Maven AAR. iOS consumes `TugboatCaptureRuntime` via the example
25-
Podfile path override. See
23+
`TugboatScreenshotCaptureBackend.nativeCpuExperimental`. In the monorepo the
24+
Flutter plugin compiles the Android and Apple runtimes from source. It does
25+
not depend on unpublished Maven or CocoaPods artifacts. Published pub
26+
consumers get a stub that reports native capture unsupported. See
2627
[native-cpu-experimental.md](native-cpu-experimental.md) and
2728
[apple-development.md](apple-development.md).
2829

docs/integration/native-cpu-experimental.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ privacy rules in
3131
hold for the target app. Physical-device privacy and performance sign-off is
3232
an internal lab gate.
3333

34-
The Apple plugin reports native capture as supported on iOS 15+. Physical
35-
device privacy and performance rows are still open. Do not
36-
publish the Apple 0.1.0 CocoaPod yet.
34+
The Apple plugin reports native capture as supported on iOS 15+. The plugin
35+
deployment target remains iOS 12 so default `RepaintBoundary` apps are not
36+
forced onto iOS 15. Physical device privacy and performance rows are still
37+
open. Do not publish the Apple 0.1.0 CocoaPod yet.

docs/releases/compatibility.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
| Adapter | Adapter version | Native runtime |
44
| --- | --- | --- |
5+
| Flutter `tugboat` | 0.8.13 | Android `capture-runtime` 0.1.x compiled from monorepo sources (unpublished Maven is not required). Apple `TugboatCaptureRuntime` 0.1.x compiled from monorepo sources (unpublished CocoaPod is not required). Plugin iOS floor 12; native capture still reports unsupported below iOS 15. |
56
| Flutter `tugboat` | 0.8.12 | Android `com.tugboat.sdk:capture-runtime` 0.1.x (local Maven; experimental). Apple `TugboatCaptureRuntime` 0.1.x (local CocoaPod / SwiftPM; experimental). |
67
| Flutter `tugboat` | 0.9.0 (planned) | Android `capture-runtime` 0.1.x and Apple `TugboatCaptureRuntime` 0.1.x |
78
| Apple `TugboatCaptureRuntime` | 0.1.0 | unpublished local CocoaPod / SwiftPM; iOS 15 live Flutter-layer CPU path |

platforms/android/capture-runtime/src/main/java/com/tugboat/capture/internal/PixelCopyCapture.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,22 @@ internal object PixelCopyCapture {
3131
latch.countDown()
3232
return@post
3333
}
34-
PixelCopy.request(surfaceView, destination, { result ->
35-
code.set(result)
36-
latch.countDown()
37-
}, handler)
34+
PixelCopy.request(
35+
surfaceView,
36+
destination,
37+
{ result ->
38+
code.set(result)
39+
latch.countDown()
40+
},
41+
handler,
42+
)
3843
}
3944
val completed = latch.await(timeoutMs, TimeUnit.MILLISECONDS)
45+
if (!completed) {
46+
// PixelCopy may still write to `destination`. Block until the
47+
// callback runs so CaptureRuntime's finally can recycle safely.
48+
latch.await()
49+
}
4050
if (!isCurrent()) return CaptureStatus.Cancelled
4151
if (!completed) return CaptureStatus.Timeout
4252
return when (code.get()) {

platforms/apple/Sources/TugboatCaptureRuntime/Internal/ContentHash.swift

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
1+
import CommonCrypto
12
import CryptoKit
23
import Foundation
34

45
enum ContentHash {
56
static func sha256Hex(_ data: Data) -> String {
6-
let digest = SHA256.hash(data: data)
7+
if #available(iOS 13.0, *) {
8+
return cryptoKitHex(data)
9+
}
10+
return commonCryptoHex(data)
11+
}
12+
13+
@available(iOS 13.0, *)
14+
private static func cryptoKitHex(_ data: Data) -> String {
15+
hex(SHA256.hash(data: data))
16+
}
17+
18+
private static func commonCryptoHex(_ data: Data) -> String {
19+
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
20+
data.withUnsafeBytes { raw in
21+
_ = CC_SHA256(raw.baseAddress, CC_LONG(data.count), &hash)
22+
}
23+
return hex(hash)
24+
}
25+
26+
private static func hex<S: Sequence>(_ bytes: S) -> String where S.Element == UInt8 {
727
var hex = String()
828
hex.reserveCapacity(64)
9-
for byte in digest {
29+
for byte in bytes {
1030
hex.append(String(format: "%02x", byte))
1131
}
1232
return hex

0 commit comments

Comments
 (0)