From ec54302a731e2dd32dbd136552314ffa360cffab Mon Sep 17 00:00:00 2001 From: Dustin Byrne Date: Mon, 7 Sep 2026 17:42:31 -0400 Subject: [PATCH 1/5] ci: exercise core, Android and Java SDK harness profiles --- .changeset/true-cases-say.md | 3 + .github/workflows/sdk-compliance-tests.yml | 105 +++- sdk_compliance_adapter/CONTRIBUTING.md | 138 +++-- sdk_compliance_adapter/Dockerfile | 22 +- .../android/build.gradle.kts | 38 ++ .../android/gradle.lockfile | 206 ++++++++ .../android/src/main/AndroidManifest.xml | 12 + .../compliance/android/AdapterActivity.kt | 66 +++ sdk_compliance_adapter/build.gradle.kts | 33 +- sdk_compliance_adapter/check-report.py | 19 + .../common/build.gradle.kts | 29 ++ sdk_compliance_adapter/common/gradle.lockfile | 100 ++++ .../posthog/compliance/ComplianceAdapter.kt | 332 ++++++++++++ .../com/posthog/compliance/SdkProfiles.kt | 121 +++++ .../main/resources/sdk-versions.properties | 2 + sdk_compliance_adapter/docker-compose.yml | 7 +- sdk_compliance_adapter/gradle.lockfile | 125 ++--- sdk_compliance_adapter/run-android.sh | 16 + sdk_compliance_adapter/run-harness.sh | 13 + .../posthog/compliance/ComplianceAdapter.kt | 478 ------------------ .../kotlin/com/posthog/compliance/Main.kt | 17 + .../com/posthog/compliance/ServerProfile.kt | 54 ++ .../compliance/ComplianceAdapterTest.kt | 113 +++++ .../com/posthog/compliance/ObservationTest.kt | 28 + .../test/resources/json/capture-offset.json | 6 + .../src/test/resources/json/flags-v2.json | 12 + settings.gradle.kts | 8 + 27 files changed, 1468 insertions(+), 635 deletions(-) create mode 100644 .changeset/true-cases-say.md create mode 100644 sdk_compliance_adapter/android/build.gradle.kts create mode 100644 sdk_compliance_adapter/android/gradle.lockfile create mode 100644 sdk_compliance_adapter/android/src/main/AndroidManifest.xml create mode 100644 sdk_compliance_adapter/android/src/main/kotlin/com/posthog/compliance/android/AdapterActivity.kt create mode 100644 sdk_compliance_adapter/check-report.py create mode 100644 sdk_compliance_adapter/common/build.gradle.kts create mode 100644 sdk_compliance_adapter/common/gradle.lockfile create mode 100644 sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt create mode 100644 sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/SdkProfiles.kt create mode 100644 sdk_compliance_adapter/common/src/main/resources/sdk-versions.properties create mode 100644 sdk_compliance_adapter/run-android.sh create mode 100644 sdk_compliance_adapter/run-harness.sh delete mode 100644 sdk_compliance_adapter/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt create mode 100644 sdk_compliance_adapter/src/main/kotlin/com/posthog/compliance/Main.kt create mode 100644 sdk_compliance_adapter/src/main/kotlin/com/posthog/compliance/ServerProfile.kt create mode 100644 sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ComplianceAdapterTest.kt create mode 100644 sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ObservationTest.kt create mode 100644 sdk_compliance_adapter/src/test/resources/json/capture-offset.json create mode 100644 sdk_compliance_adapter/src/test/resources/json/flags-v2.json diff --git a/.changeset/true-cases-say.md b/.changeset/true-cases-say.md new file mode 100644 index 000000000..853d812bb --- /dev/null +++ b/.changeset/true-cases-say.md @@ -0,0 +1,3 @@ +--- + +--- diff --git a/.github/workflows/sdk-compliance-tests.yml b/.github/workflows/sdk-compliance-tests.yml index e6a2179b3..0fcc13766 100644 --- a/.github/workflows/sdk-compliance-tests.yml +++ b/.github/workflows/sdk-compliance-tests.yml @@ -2,29 +2,104 @@ name: SDK Compliance Tests on: push: - branches: [ main ] - paths: + branches: [main] + paths: &paths - 'posthog/**' + - 'posthog-android/**' + - 'posthog-server/**' - 'sdk_compliance_adapter/**' + - 'buildSrc/**' + - 'build.gradle.kts' + - 'settings.gradle.kts' + - 'gradle.properties' + - 'gradle/**' + - 'gradlew*' - '.github/workflows/sdk-compliance-tests.yml' pull_request: - branches: [ main ] - paths: - - 'posthog/**' - - 'sdk_compliance_adapter/**' - - '.github/workflows/sdk-compliance-tests.yml' + branches: [main] + paths: *paths workflow_dispatch: permissions: contents: read packages: read - pull-requests: write jobs: - test-android-sdk: - uses: PostHog/posthog-sdk-test-harness/.github/workflows/test-sdk-action.yml@6d19abb9c81e2262dacbe340e7dddda9c871c178 - with: - adapter-dockerfile: sdk_compliance_adapter/Dockerfile - adapter-context: . - test-harness-version: "0.10.0" - report-name: android-sdk-compliance-report + jvm: + name: ${{ matrix.profile }} / V0 gzip + runs-on: ubuntu-latest + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + profile: [core, server] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + with: + java-version: '17' + distribution: temurin + - name: Build and test adapter + run: ./gradlew -Pcompliance :sdk_compliance_adapter:test :sdk_compliance_adapter:installDist --no-daemon + - name: Start public SDK profile + env: + SDK_PROFILE: ${{ matrix.profile }} + PORT: '18290' + run: | + mkdir -p report + sdk_compliance_adapter/build/install/sdk_compliance_adapter/bin/sdk_compliance_adapter > report/adapter.log 2>&1 & + echo $! > report/adapter.pid + - name: Run harness + continue-on-error: true + run: bash sdk_compliance_adapter/run-harness.sh 18290 19290 + - name: Verify complete inventory + if: always() + run: python3 sdk_compliance_adapter/check-report.py report/compliance.json + - name: Upload profile evidence + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ${{ matrix.profile }}-sdk-compliance-report + path: report/ + if-no-files-found: error + - name: Stop adapter + if: always() + run: test ! -f report/adapter.pid || kill "$(cat report/adapter.pid)" + + android-entry: + name: Android public entry / V0 gzip + runs-on: ubuntu-latest + timeout-minutes: 35 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + with: + java-version: '17' + distribution: temurin + - name: Build native adapter APK + # The SDK disables debug variants when CI=true. + run: CI=false ./gradlew -Pcompliance -PcomplianceAndroid :sdk_compliance_adapter:android:assembleDebug --no-daemon + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + - name: Run harness on Android emulator + continue-on-error: true + uses: reactivecircus/android-emulator-runner@4c44018e59b437e86cdfc41da381398f93ed8808 # v2 + with: + api-level: 35 + arch: x86_64 + target: google_apis + disable-animations: true + script: bash sdk_compliance_adapter/run-android.sh + - name: Verify complete inventory + if: always() + run: python3 sdk_compliance_adapter/check-report.py report/compliance.json + - name: Upload Android evidence + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: android-entry-sdk-compliance-report + path: report/ + if-no-files-found: error diff --git a/sdk_compliance_adapter/CONTRIBUTING.md b/sdk_compliance_adapter/CONTRIBUTING.md index 3c1a676b9..d43dbc405 100644 --- a/sdk_compliance_adapter/CONTRIBUTING.md +++ b/sdk_compliance_adapter/CONTRIBUTING.md @@ -1,43 +1,117 @@ -# Contributing - -This package contains the PostHog Android SDK compliance adapter used with the PostHog SDK Test Harness. - -## Prerequisites - -- x86_64 architecture for the full local test flow because of the Java 8 dependency -- Java 8, 11, and 17 for local Gradle builds - -On Apple Silicon, Docker will use emulation. It is slower, but it works. - -## Building - -### Local build - -```bash -./gradlew :sdk_compliance_adapter:build +# SDK compliance profiles + +These adapters run the repository's SDK source with harness **1.0.0**, contract 1.2. +Each profile selects **30 capture V0 + 17 feature flag tests**, server wire, gzip. +Assertions are advisory; missing, empty, or incomplete reports fail CI. + +| Profile | Actual entry and runtime | +| --- | --- | +| `core` | `com.posthog.PostHog.with` on JVM, integrated context and file queue | +| `server` | `com.posthog.server.PostHog.with` on JVM, SDK memory queue | +| Android entry | `PostHogAndroid.with(applicationContext, config)` in an emulator APK | + +Android uses native context, networking, preferences and date providers. Its report +is separate from the core integration and Java server reports. CI uses Android 35 +x86_64; the APK requires API 26 or newer for the adapter's Java time/HTTP runtime. + +## Configuration and observation + +Capture forwards identity, properties and an optional parsed `Date` to the public +SDK. The SDK owns event creation, UUIDs, serialization, compression and retry policy. +The before-send hook observes the final UUID produced by a public call (stateful +capture also builds an intermediate event). It does not assign or edit IDs. + +A passive, per-initialization HTTP ingress forwards the SDK's request bytes and +end-to-end headers to the mock, and returns its response unchanged. It does not +retry or follow redirects. The SDK retains its default HTTP client. Host and +hop-by-hop framing headers necessarily belong to each proxy connection. The +controller and proxy use Ktor CIO on both JVM and Android. + +`/flush` calls the SDK once and waits up to ten seconds for acknowledgments. It +returns `success: false` if any observed event is still unacknowledged, including +terminal drops: there is no public queue-completion API. `/state.pending_events` +therefore means **observed, unacknowledged UUIDs**, not native queue depth. Retries +are observed repeated UUIDs; a retry budget is never used to infer completion. +Reset closes the instance, retires its ingress and clears this test app's storage. +Parallel tests are not supported. + +The configured flush threshold defaults to 100. Millisecond intervals default to +500 and round up to whole seconds (minimum one second), the SDK's supported unit. +Java server has no public `maxRetries` setting; that input cannot be forwarded. +Gzip is the only profile: the SDK has no public compression-disable switch here. +V1 and dedicated AI capture are not advertised. + +### Flags + +Core and Android disable flag preload, and Android additionally disables lifecycle, +screen, deep-link and push autocapture. Remote configuration still uses the SDK's +startup path. These are isolated **reload-per-action** profiles, not certification +of default startup/preload behavior or ordinary cached-getter network behavior. + +Person/group properties use public setters. Identity and groups use public +`identify`/`group`, retaining their SDK-owned events and reloads. The adapter awaits +those reload callbacks; otherwise it explicitly reloads and reads the public cached +getter. No controller parses flag responses or manufactures called-events. +Multi-group actions can cause multiple SDK requests. Singleton flag-key scope, +per-call GeoIP overrides and compound person-property assertions are deferred +stateful/server-contract differences, but remain selected and visible in reports. + +Java uses public `evaluateFlags` with singleton keys, groups, person/group properties +and the supported GeoIP argument, then reads `snapshot.getFlag`. Local evaluation +is disabled and `featureFlagCacheSize=0`, so this is an explicit remote/no-cache +profile regardless of `force_remote`. + +The following Java result assertions remain deferred because their legacy-only +`featureFlags` fixtures do not match the SDK's rich v2 parser. They are **not skipped**: + +- `feature_flags.request_lifecycle.mock_response_value_is_returned_to_caller` +- `feature_flags.retry_behavior.retries_flags_on_502` +- `feature_flags.retry_behavior.retries_flags_on_504` +- `feature_flags.side_effect_events.get_feature_flag_captures_feature_flag_called_event` + +All capture retries/status cases exercise real transports, including Java's +existing 408/429 retention, retry-budget and Retry-After differences. Its slower +retry cadence can also miss fixed harness wait windows. A passing assertion does +not imply these underlying SDK policies are compliant. + +## Local JVM build and run + +Use JDK 17 and the repository Gradle wrapper. Adapter projects are opt-in: + +```sh +./gradlew -Pcompliance :sdk_compliance_adapter:test :sdk_compliance_adapter:installDist +PORT=18290 SDK_PROFILE=core sdk_compliance_adapter/build/install/sdk_compliance_adapter/bin/sdk_compliance_adapter +# Use SDK_PROFILE=server for Java server. ``` -### Docker build +Against that process, run the pinned harness (Linux with Docker): -```bash -docker build -f sdk_compliance_adapter/Dockerfile -t posthog-android-adapter . +```sh +bash sdk_compliance_adapter/run-harness.sh 18290 19290 +python3 sdk_compliance_adapter/check-report.py report/compliance.json ``` -The Dockerfile uses Gradle toolchain auto-download to fetch the required Java versions. - -## Running tests +Alternatively build `sdk_compliance_adapter/Dockerfile`, or run Compose from this +directory with `SDK_PROFILE=core` or `SDK_PROFILE=server`. Compose's private network +ports are not published to the host. Docker Compose does not emulate Android. -Tests run automatically in GitHub Actions on pushes to `main`/`master`, pull requests, and manual workflow dispatches. +## Android emulator -### Local Docker Compose run +With an Android SDK and a running emulator: -```bash -cd sdk_compliance_adapter -docker-compose up --build --abort-on-container-exit +```sh +CI=false ./gradlew -Pcompliance -PcomplianceAndroid :sdk_compliance_adapter:android:assembleDebug +# Linux + Docker; set ANDROID_SERIAL if other devices are attached. +bash sdk_compliance_adapter/run-android.sh ``` -This runs: +The script installs the test APK, starts its HTTP listener, and forwards adapter +port 18292 and reverses mock port 19292. For a native harness installation on macOS, +use the same `adb install`, `forward`, `reverse` and `am start` commands in the script, +then run harness 1.0.0 with those URLs and `--sdk-type server --concurrency 1`. +Only use local/mock project keys; no live PostHog project is needed. -- `test-harness` - compliance test runner -- `adapter` - this SDK adapter -- `mock-server` - mock PostHog server +Adapter regression tests cover both public JVM entries, Date/UUID wire fidelity, +real flag parsing/called-events and passive proxy handling of the trailing-slash +flags endpoint. Test fixtures include a genuine rich v2 response independently of +the harness's legacy-only fixtures. diff --git a/sdk_compliance_adapter/Dockerfile b/sdk_compliance_adapter/Dockerfile index e243d0c4d..aced9def1 100644 --- a/sdk_compliance_adapter/Dockerfile +++ b/sdk_compliance_adapter/Dockerfile @@ -1,24 +1,12 @@ -# Build with the repo's own Gradle wrapper: an image-pinned Gradle skews from -# the wrapper-generated lockfiles on every wrapper bump (the embedded Kotlin -# build-tools version is locked strictly per Gradle release). -FROM --platform=linux/amd64 eclipse-temurin:17-jdk AS builder - +FROM eclipse-temurin:17-jdk AS builder WORKDIR /app - COPY . . +RUN ./gradlew -Pcompliance :sdk_compliance_adapter:installDist --no-daemon -RUN echo 'include(":sdk_compliance_adapter")' >> settings.gradle.kts && \ - sed -i '/^dependencyResolutionManagement/i plugins {\n id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"\n}' settings.gradle.kts && \ - mkdir -p /root/.gradle && \ - echo 'org.gradle.java.installations.auto-download=true' > /root/.gradle/gradle.properties && \ - ./gradlew :sdk_compliance_adapter:installDist --no-daemon - -FROM eclipse-temurin:11-jre - +FROM eclipse-temurin:17-jre WORKDIR /app - COPY --from=builder /app/sdk_compliance_adapter/build/install/sdk_compliance_adapter /app - +ARG SDK_PROFILE=core +ENV SDK_PROFILE=${SDK_PROFILE} EXPOSE 8080 - CMD ["/app/bin/sdk_compliance_adapter"] diff --git a/sdk_compliance_adapter/android/build.gradle.kts b/sdk_compliance_adapter/android/build.gradle.kts new file mode 100644 index 000000000..a7c514955 --- /dev/null +++ b/sdk_compliance_adapter/android/build.gradle.kts @@ -0,0 +1,38 @@ +plugins { + id("com.android.application") + kotlin("android") +} + +android { + namespace = "com.posthog.compliance.android" + compileSdk = PosthogBuildConfig.Android.COMPILE_SDK + defaultConfig { + applicationId = "com.posthog.compliance.android" + minSdk = 26 + targetSdk = PosthogBuildConfig.Android.TARGET_SDK + versionCode = 1 + versionName = "1.0.0" + } + compileOptions { + sourceCompatibility = PosthogBuildConfig.Build.JAVA_VERSION + targetCompatibility = PosthogBuildConfig.Build.JAVA_VERSION + } + packaging.resources.excludes += setOf("META-INF/INDEX.LIST", "META-INF/io.netty.versions.properties") +} + +kotlin { + jvmToolchain(PosthogBuildConfig.Build.JDK_VERSION) +} + +tasks.withType().configureEach { + compilerOptions.postHogConfig(false) +} + +dependencies { + implementation(project(":sdk_compliance_adapter:common")) + implementation(project(":posthog-android")) +} + +tasks.matching { it.name == "apiCheck" || it.name == "apiDump" }.configureEach { + enabled = false +} diff --git a/sdk_compliance_adapter/android/gradle.lockfile b/sdk_compliance_adapter/android/gradle.lockfile new file mode 100644 index 000000000..6114ad180 --- /dev/null +++ b/sdk_compliance_adapter/android/gradle.lockfile @@ -0,0 +1,206 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +# To regenerate this file, run: ./gradlew :sdk_compliance_adapter:android:dependencies --write-locks +androidx.annotation:annotation:1.2.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-common:2.2.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-runtime:2.2.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.collection:collection:1.0.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.concurrent:concurrent-futures:1.1.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core:1.5.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common-java8:2.6.2=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.6.2=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-process:2.6.2=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-runtime:2.6.2=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.profileinstaller:profileinstaller:1.3.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.startup:startup-runtime:1.1.1=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.tracing:tracing:1.0.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.versionedparcelable:versionedparcelable:1.1.1=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.android.tools.ddms:ddmlib:31.9.1=_internal-unified-test-platform-android-device-provider-ddmlib +com.android.tools.emulator:proto:31.9.1=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools.utp:android-device-provider-ddmlib-proto:31.9.1=_internal-unified-test-platform-android-device-provider-ddmlib +com.android.tools.utp:android-device-provider-ddmlib:31.9.1=_internal-unified-test-platform-android-device-provider-ddmlib +com.android.tools.utp:android-device-provider-gradle-proto:31.9.1=_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-device-provider-gradle:31.9.1=_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-device-provider-profile-proto:31.9.1=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-device-provider-profile:31.9.1=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:31.9.1=_internal-unified-test-platform-android-test-plugin-host-additional-test-output +com.android.tools.utp:android-test-plugin-host-additional-test-output:31.9.1=_internal-unified-test-platform-android-test-plugin-host-additional-test-output +com.android.tools.utp:android-test-plugin-host-apk-installer-proto:31.9.1=_internal-unified-test-platform-android-test-plugin-host-apk-installer +com.android.tools.utp:android-test-plugin-host-apk-installer:31.9.1=_internal-unified-test-platform-android-test-plugin-host-apk-installer +com.android.tools.utp:android-test-plugin-host-coverage-proto:31.9.1=_internal-unified-test-platform-android-test-plugin-host-coverage +com.android.tools.utp:android-test-plugin-host-coverage:31.9.1=_internal-unified-test-platform-android-test-plugin-host-coverage +com.android.tools.utp:android-test-plugin-host-device-info-proto:31.9.1=_internal-unified-test-platform-android-test-plugin-host-device-info +com.android.tools.utp:android-test-plugin-host-device-info:31.9.1=_internal-unified-test-platform-android-test-plugin-host-device-info +com.android.tools.utp:android-test-plugin-host-emulator-control-proto:31.9.1=_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.android.tools.utp:android-test-plugin-host-emulator-control:31.9.1=_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.android.tools.utp:android-test-plugin-host-logcat-proto:31.9.1=_internal-unified-test-platform-android-test-plugin-host-logcat +com.android.tools.utp:android-test-plugin-host-logcat:31.9.1=_internal-unified-test-platform-android-test-plugin-host-logcat +com.android.tools.utp:android-test-plugin-host-retention-proto:31.9.1=_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools.utp:android-test-plugin-host-retention:31.9.1=_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:31.9.1=_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools.utp:android-test-plugin-result-listener-gradle:31.9.1=_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools.utp:utp-common:31.9.1=_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools:annotations:31.9.1=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools:common:31.9.1=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.fasterxml.jackson.core:jackson-annotations:2.12.7=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +com.fasterxml.jackson.core:jackson-core:2.12.7=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +com.fasterxml.jackson.core:jackson-databind:2.12.7.1=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.12.7=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.12.7=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +com.fasterxml.jackson.module:jackson-module-kotlin:2.12.7=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +com.fasterxml.jackson:jackson-bom:2.12.7=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +com.fasterxml.woodstox:woodstox-core:6.2.4=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +com.google.android:annotations:4.1.1.4=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +com.google.api.grpc:proto-google-common-protos:2.17.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +com.google.auto.service:auto-service-annotations:1.1.1=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.auto.service:auto-service:1.1.1=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.auto:auto-common:1.2.1=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.code.findbugs:jsr305:3.0.2=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher +com.google.code.gson:gson:2.10.1=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.code.gson:gson:2.8.9=_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-launcher +com.google.crypto.tink:tink:1.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.google.dagger:dagger:2.48=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +com.google.errorprone:error_prone_annotations:2.23.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher +com.google.guava:failureaccess:1.0.1=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher +com.google.guava:guava:32.0.1-jre=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher +com.google.guava:listenablefuture:1.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher +com.google.j2objc:j2objc-annotations:2.8=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher +com.google.protobuf:protobuf-java-util:3.22.3=_internal-unified-test-platform-core +com.google.protobuf:protobuf-java-util:3.24.4=_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-launcher +com.google.protobuf:protobuf-java:3.24.4=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher +com.google.protobuf:protobuf-kotlin:3.24.4=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher +com.google.testing.platform:android-device-provider-local:0.0.9-alpha03=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.testing.platform:android-driver-instrumentation:0.0.9-alpha03=_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.google.testing.platform:android-test-plugin:0.0.9-alpha03=_internal-unified-test-platform-android-test-plugin +com.google.testing.platform:core-proto:0.0.9-alpha03=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.testing.platform:core:0.0.9-alpha03=_internal-unified-test-platform-core +com.google.testing.platform:launcher:0.0.9-alpha03=_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-launcher +com.squareup.curtains:curtains:1.2.5=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.squareup.okhttp3:okhttp-bom:4.12.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.squareup.okhttp3:okhttp:4.12.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.squareup.okio:okio-jvm:3.6.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.squareup.okio:okio:3.6.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.typesafe:config:1.4.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +commons-io:commons-io:2.16.1=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention +io.grpc:grpc-api:1.57.2=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.grpc:grpc-context:1.57.2=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.grpc:grpc-core:1.57.2=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.grpc:grpc-netty:1.57.2=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.grpc:grpc-protobuf-lite:1.57.2=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.grpc:grpc-protobuf:1.57.2=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.grpc:grpc-services:1.57.2=_internal-unified-test-platform-core +io.grpc:grpc-stub:1.57.2=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.ktor:ktor-events-jvm:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-events:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-http-cio-jvm:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-http-cio:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-http-jvm:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-http:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-io-jvm:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-io:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-network-jvm:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-network:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-serialization-jvm:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-serialization:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-server-cio-jvm:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-server-cio:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-server-core-jvm:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-server-core:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-server-host-common-jvm:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-server-host-common:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-utils-jvm:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-utils:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-websockets-jvm:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.ktor:ktor-websockets:2.3.7=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +io.netty:netty-buffer:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.netty:netty-codec-http2:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.netty:netty-codec-http:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.netty:netty-codec-socks:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.netty:netty-codec:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.netty:netty-common:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.netty:netty-handler-proxy:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.netty:netty-handler:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.netty:netty-resolver:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.netty:netty-transport-native-unix-common:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.netty:netty-transport:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +io.opencensus:opencensus-api:0.31.0=_internal-unified-test-platform-core +io.opencensus:opencensus-proto:0.2.0=_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher +io.perfmark:perfmark-api:0.26.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +it.unimi.dsi:fastutil-core:8.5.12=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +jakarta.activation:jakarta.activation-api:1.2.1=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +javax.annotation:javax.annotation-api:1.3.2=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +javax.inject:javax.inject:1=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +net.java.dev.jna:jna-platform:5.6.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +net.java.dev.jna:jna:5.6.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +net.sf.kxml:kxml2:2.3.0=_internal-unified-test-platform-android-device-provider-ddmlib +org.checkerframework:checker-qual:3.33.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher +org.codehaus.mojo:animal-sniffer-annotations:1.23=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core +org.codehaus.woodstox:stax2-api:4.2.1=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +org.freemarker:freemarker:2.3.32=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.fusesource.jansi:jansi:2.4.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.dokka:analysis-kotlin-descriptors:1.9.20=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains.dokka:analysis-markdown:1.9.20=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains.dokka:dokka-base:1.9.20=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains.dokka:dokka-core:1.9.20=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-build-tools-api:2.1.21=kotlinBuildToolsApiClasspath +org.jetbrains.kotlin:kotlin-build-tools-impl:2.1.21=kotlinBuildToolsApiClasspath +org.jetbrains.kotlin:kotlin-compiler-embeddable:2.1.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-compiler-runner:2.1.21=kotlinBuildToolsApiClasspath +org.jetbrains.kotlin:kotlin-daemon-client:2.1.21=kotlinBuildToolsApiClasspath +org.jetbrains.kotlin:kotlin-daemon-embeddable:2.1.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:2.1.21=kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-metadata-jvm:2.1.21=bcv-rt-jvm-cp-resolver +org.jetbrains.kotlin:kotlin-reflect:1.6.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-reflect:1.8.21=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher +org.jetbrains.kotlin:kotlin-reflect:1.8.22=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-reflect:1.9.22=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlin:kotlin-script-runtime:2.1.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-scripting-common:2.1.21=kotlinBuildToolsApiClasspath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:2.1.21=kotlinBuildToolsApiClasspath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:2.1.21=kotlinBuildToolsApiClasspath +org.jetbrains.kotlin:kotlin-scripting-jvm:2.1.21=kotlinBuildToolsApiClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21=_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-core +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.0=_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-launcher +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.22=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlin:kotlin-stdlib-common:2.1.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-stdlib-common:2.1.21=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20=_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher,dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.22=debugAndroidTestCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.1.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.1.21=debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20=_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher,dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22=debugAndroidTestCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.21=debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.8.21=_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-core +org.jetbrains.kotlin:kotlin-stdlib:1.9.0=_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-launcher +org.jetbrains.kotlin:kotlin-stdlib:1.9.22=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlin:kotlin-stdlib:2.1.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-stdlib:2.1.21=apiDependenciesMetadata,bcv-rt-jvm-cp-resolver,debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:atomicfu-jvm:0.22.0=_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-launcher +org.jetbrains.kotlinx:atomicfu:0.22.0=_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-launcher +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.7.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-html-jvm:0.9.1=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains:annotations:13.0=bcv-rt-jvm-cp-resolver,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains:annotations:23.0.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,_internal-unified-test-platform-core,_internal-unified-test-platform-launcher,debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains:markdown-jvm:0.5.2=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains:markdown:0.5.2=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jsoup:jsoup:1.16.1=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.ow2.asm:asm-tree:9.6=bcv-rt-jvm-cp-resolver +org.ow2.asm:asm:9.6=bcv-rt-jvm-cp-resolver +org.slf4j:slf4j-api:1.7.36=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +empty=androidApis,androidJdkImage,androidTestApiDependenciesMetadata,androidTestCompileOnlyDependenciesMetadata,androidTestDebugApiDependenciesMetadata,androidTestDebugCompileOnlyDependenciesMetadata,androidTestDebugImplementationDependenciesMetadata,androidTestDebugIntransitiveDependenciesMetadata,androidTestImplementationDependenciesMetadata,androidTestIntransitiveDependenciesMetadata,androidTestReleaseApiDependenciesMetadata,androidTestReleaseCompileOnlyDependenciesMetadata,androidTestReleaseImplementationDependenciesMetadata,androidTestReleaseIntransitiveDependenciesMetadata,androidTestUtil,compileOnlyDependenciesMetadata,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestApiDependenciesMetadata,debugAndroidTestCompileOnlyDependenciesMetadata,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestIntransitiveDependenciesMetadata,debugAndroidTestRuntimeClasspath,debugAnnotationProcessorClasspath,debugApiDependenciesMetadata,debugCompileOnlyDependenciesMetadata,debugImplementationDependenciesMetadata,debugIntransitiveDependenciesMetadata,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugUnitTestApiDependenciesMetadata,debugUnitTestCompileOnlyDependenciesMetadata,debugUnitTestImplementationDependenciesMetadata,debugUnitTestIntransitiveDependenciesMetadata,debugWearBundling,dokkaPlugin,dokkaRuntime,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseUnitTest,kotlinNativeCompilerPluginClasspath,lintChecks,lintPublish,releaseAnnotationProcessorClasspath,releaseApiDependenciesMetadata,releaseCompileOnlyDependenciesMetadata,releaseImplementationDependenciesMetadata,releaseIntransitiveDependenciesMetadata,releaseReverseMetadataValues,releaseUnitTestAnnotationProcessorClasspath,releaseUnitTestApiDependenciesMetadata,releaseUnitTestCompileOnlyDependenciesMetadata,releaseUnitTestImplementationDependenciesMetadata,releaseUnitTestIntransitiveDependenciesMetadata,releaseWearBundling,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testDebugApiDependenciesMetadata,testDebugCompileOnlyDependenciesMetadata,testDebugImplementationDependenciesMetadata,testDebugIntransitiveDependenciesMetadata,testFixturesApiDependenciesMetadata,testFixturesCompileOnlyDependenciesMetadata,testFixturesDebugApiDependenciesMetadata,testFixturesDebugCompileOnlyDependenciesMetadata,testFixturesDebugImplementationDependenciesMetadata,testFixturesDebugIntransitiveDependenciesMetadata,testFixturesImplementationDependenciesMetadata,testFixturesIntransitiveDependenciesMetadata,testFixturesReleaseApiDependenciesMetadata,testFixturesReleaseCompileOnlyDependenciesMetadata,testFixturesReleaseImplementationDependenciesMetadata,testFixturesReleaseIntransitiveDependenciesMetadata,testImplementationDependenciesMetadata,testIntransitiveDependenciesMetadata,testReleaseApiDependenciesMetadata,testReleaseCompileOnlyDependenciesMetadata,testReleaseImplementationDependenciesMetadata,testReleaseIntransitiveDependenciesMetadata diff --git a/sdk_compliance_adapter/android/src/main/AndroidManifest.xml b/sdk_compliance_adapter/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..a4fffd3c4 --- /dev/null +++ b/sdk_compliance_adapter/android/src/main/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sdk_compliance_adapter/android/src/main/kotlin/com/posthog/compliance/android/AdapterActivity.kt b/sdk_compliance_adapter/android/src/main/kotlin/com/posthog/compliance/android/AdapterActivity.kt new file mode 100644 index 000000000..5c7d24001 --- /dev/null +++ b/sdk_compliance_adapter/android/src/main/kotlin/com/posthog/compliance/android/AdapterActivity.kt @@ -0,0 +1,66 @@ +package com.posthog.compliance.android + +import android.app.Activity +import android.os.Bundle +import com.posthog.android.PostHogAndroid +import com.posthog.android.PostHogAndroidConfig +import com.posthog.compliance.InitRequest +import com.posthog.compliance.Observation +import com.posthog.compliance.SdkClient +import com.posthog.compliance.SdkProfile +import com.posthog.compliance.StatefulClient +import com.posthog.compliance.complianceRoutes +import com.posthog.compliance.configureStateful +import com.posthog.compliance.sdkVersion +import io.ktor.server.cio.CIO +import io.ktor.server.engine.ApplicationEngine +import io.ktor.server.engine.embeddedServer +import java.io.File + +class AdapterActivity : Activity() { + private var server: ApplicationEngine? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val port = intent.getIntExtra("port", 8080) + val context = applicationContext + val profile = + object : SdkProfile { + override val name = "posthog-android-entry" + override val version = sdkVersion("android") + + override fun create( + request: InitRequest, + storage: File, + observer: Observation, + ): SdkClient { + // This test app owns its storage; start each case without persisted SDK identity/queues. + context.cacheDir.listFiles()?.filter { it.name.startsWith("posthog-") }?.forEach { it.deleteRecursively() } + File(context.applicationInfo.dataDir, "shared_prefs").listFiles()?.forEach { + context.getSharedPreferences(it.nameWithoutExtension, MODE_PRIVATE).edit().clear().commit() + } + val config = + PostHogAndroidConfig( + request.api_key, + request.host, + captureApplicationLifecycleEvents = false, + captureDeepLinks = false, + captureScreenViews = false, + capturePushNotificationSubscriptions = false, + capturePushNotificationOpened = false, + ) + val completion = configureStateful(config, request, observer) + return StatefulClient(PostHogAndroid.with(context, config), observer, completion) + } + } + server = + embeddedServer(CIO, port = port) { + complianceRoutes(profile, port, File(context.cacheDir, "compliance")) + }.start(wait = false) + } + + override fun onDestroy() { + server?.stop(100, 1000) + super.onDestroy() + } +} diff --git a/sdk_compliance_adapter/build.gradle.kts b/sdk_compliance_adapter/build.gradle.kts index ed13a44c2..c0f25c69e 100644 --- a/sdk_compliance_adapter/build.gradle.kts +++ b/sdk_compliance_adapter/build.gradle.kts @@ -7,32 +7,23 @@ group = "com.posthog.compliance" version = "1.0.0" dependencies { - // PostHog Core SDK - implementation(project(":posthog")) - - // Ktor server - val ktorVersion = "2.3.7" - implementation("io.ktor:ktor-server-core:$ktorVersion") - implementation("io.ktor:ktor-server-netty:$ktorVersion") - implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") - implementation("io.ktor:ktor-serialization-gson:$ktorVersion") - - // Logging - implementation("ch.qos.logback:logback-classic:1.4.14") - - // OkHttp (for interceptor) - implementation(platform("com.squareup.okhttp3:okhttp-bom:${PosthogBuildConfig.Dependencies.OKHTTP}")) - implementation("com.squareup.okhttp3:okhttp") + implementation(project(":sdk_compliance_adapter:common")) + implementation(project(":posthog-server")) + testImplementation("org.jetbrains.kotlin:kotlin-test-junit:${PosthogBuildConfig.Kotlin.KOTLIN}") + testImplementation("com.squareup.okhttp3:mockwebserver:${PosthogBuildConfig.Dependencies.OKHTTP}") + testImplementation("com.google.code.gson:gson:${PosthogBuildConfig.Dependencies.GSON}") } application { - mainClass.set("com.posthog.compliance.ComplianceAdapterKt") + mainClass.set("com.posthog.compliance.MainKt") +} + +kotlin { + jvmToolchain(PosthogBuildConfig.Build.JDK_VERSION) } -tasks.withType { - kotlinOptions { - jvmTarget = PosthogBuildConfig.Build.JAVA_VERSION.toString() - } +tasks.withType().configureEach { + compilerOptions.postHogConfig(false) } java { diff --git a/sdk_compliance_adapter/check-report.py b/sdk_compliance_adapter/check-report.py new file mode 100644 index 000000000..72ead256d --- /dev/null +++ b/sdk_compliance_adapter/check-report.py @@ -0,0 +1,19 @@ +"""Keep advisory assertion failures distinct from absent/incomplete harness coverage.""" + +import json +import sys + +with open(sys.argv[1]) as report: + data = json.load(report) +assert data["summary"]["total"] == 47, data["summary"] +assert {suite["name"]: suite["total"] for suite in data["suites"]} == { + "capture": 30, + "feature_flags": 17, +}, data["suites"] +for suite in data["suites"]: + assert len(suite["tests"]) == suite["total"] + assert len({test["name"] for test in suite["tests"]}) == suite["total"] + for test in suite["tests"]: + if not test["passed"]: + print(f"{suite['name']}.{test['name']}: {test['message']}") +print(data["sdk_name"], data["summary"]) diff --git a/sdk_compliance_adapter/common/build.gradle.kts b/sdk_compliance_adapter/common/build.gradle.kts new file mode 100644 index 000000000..db1095fda --- /dev/null +++ b/sdk_compliance_adapter/common/build.gradle.kts @@ -0,0 +1,29 @@ +plugins { + kotlin("jvm") + `java-library` +} + +dependencies { + api(project(":posthog")) + val ktorVersion = "2.3.7" + api("io.ktor:ktor-server-core:$ktorVersion") + api("io.ktor:ktor-server-cio:$ktorVersion") + implementation("com.google.code.gson:gson:${PosthogBuildConfig.Dependencies.GSON}") + implementation(platform("com.squareup.okhttp3:okhttp-bom:${PosthogBuildConfig.Dependencies.OKHTTP}")) + implementation("com.squareup.okhttp3:okhttp") +} + +kotlin { jvmToolchain(PosthogBuildConfig.Build.JDK_VERSION) } +java { + sourceCompatibility = PosthogBuildConfig.Build.JAVA_VERSION + targetCompatibility = PosthogBuildConfig.Build.JAVA_VERSION +} +tasks.withType().configureEach { + compilerOptions.postHogConfig(false) +} +tasks.processResources { + filesMatching("sdk-versions.properties") { + expand(mapOf("serverVersion" to project.property("serverVersion"), "androidVersion" to project.property("androidVersion"))) + } +} +tasks.matching { it.name == "apiCheck" || it.name == "apiDump" }.configureEach { enabled = false } diff --git a/sdk_compliance_adapter/common/gradle.lockfile b/sdk_compliance_adapter/common/gradle.lockfile new file mode 100644 index 000000000..1abdd5fdb --- /dev/null +++ b/sdk_compliance_adapter/common/gradle.lockfile @@ -0,0 +1,100 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +# To regenerate this file, run: ./gradlew :sdk_compliance_adapter:common:dependencies --write-locks +com.fasterxml.jackson.core:jackson-annotations:2.12.7=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +com.fasterxml.jackson.core:jackson-core:2.12.7=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +com.fasterxml.jackson.core:jackson-databind:2.12.7.1=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.12.7=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.12.7=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +com.fasterxml.jackson.module:jackson-module-kotlin:2.12.7=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +com.fasterxml.jackson:jackson-bom:2.12.7=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +com.fasterxml.woodstox:woodstox-core:6.2.4=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +com.google.code.gson:gson:2.10.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.squareup.okhttp3:okhttp-bom:4.12.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.squareup.okhttp3:okhttp:4.12.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.squareup.okio:okio-jvm:3.6.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.squareup.okio:okio:3.6.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.typesafe:config:1.4.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.ktor:ktor-events-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.ktor:ktor-events:2.3.7=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +io.ktor:ktor-http-cio-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.ktor:ktor-http-cio:2.3.7=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +io.ktor:ktor-http-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.ktor:ktor-http:2.3.7=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +io.ktor:ktor-io-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.ktor:ktor-io:2.3.7=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +io.ktor:ktor-network-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.ktor:ktor-network:2.3.7=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +io.ktor:ktor-serialization-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.ktor:ktor-serialization:2.3.7=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +io.ktor:ktor-server-cio-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.ktor:ktor-server-cio:2.3.7=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +io.ktor:ktor-server-core-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.ktor:ktor-server-core:2.3.7=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +io.ktor:ktor-server-host-common-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.ktor:ktor-server-host-common:2.3.7=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +io.ktor:ktor-utils-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.ktor:ktor-utils:2.3.7=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +io.ktor:ktor-websockets-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.ktor:ktor-websockets:2.3.7=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +it.unimi.dsi:fastutil-core:8.5.12=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +jakarta.activation:jakarta.activation-api:1.2.1=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +org.codehaus.woodstox:stax2-api:4.2.1=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +org.freemarker:freemarker:2.3.32=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.fusesource.jansi:jansi:2.4.0=runtimeClasspath,testRuntimeClasspath +org.jetbrains.dokka:analysis-kotlin-descriptors:1.9.20=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains.dokka:analysis-markdown:1.9.20=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains.dokka:dokka-base:1.9.20=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains.dokka:dokka-core:1.9.20=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-build-tools-api:2.1.21=kotlinBuildToolsApiClasspath +org.jetbrains.kotlin:kotlin-build-tools-impl:2.1.21=kotlinBuildToolsApiClasspath +org.jetbrains.kotlin:kotlin-compiler-embeddable:2.1.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-compiler-runner:2.1.21=kotlinBuildToolsApiClasspath +org.jetbrains.kotlin:kotlin-daemon-client:2.1.21=kotlinBuildToolsApiClasspath +org.jetbrains.kotlin:kotlin-daemon-embeddable:2.1.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:2.1.21=kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-metadata-jvm:2.1.21=bcv-rt-jvm-cp-resolver +org.jetbrains.kotlin:kotlin-reflect:1.6.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-reflect:1.8.22=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-reflect:1.9.22=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlin:kotlin-script-runtime:2.1.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-scripting-common:2.1.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:2.1.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:2.1.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest +org.jetbrains.kotlin:kotlin-scripting-jvm:2.1.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.22=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlin:kotlin-stdlib-common:2.1.21=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20=apiDependenciesMetadata,dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.21=implementationDependenciesMetadata,testImplementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10=compileClasspath,testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.1.21=runtimeClasspath,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20=apiDependenciesMetadata,dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.21=implementationDependenciesMetadata,testImplementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10=compileClasspath,testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.21=runtimeClasspath,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.9.22=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlin:kotlin-stdlib:2.1.21=apiDependenciesMetadata,bcv-rt-jvm-cp-resolver,compileClasspath,implementationDependenciesMetadata,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlinx:atomicfu:0.20.2=apiDependenciesMetadata,implementationDependenciesMetadata,testImplementationDependenciesMetadata +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.7.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-html-jvm:0.9.1=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains:annotations:13.0=bcv-rt-jvm-cp-resolver,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath +org.jetbrains:annotations:23.0.0=compileClasspath,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains:markdown-jvm:0.5.2=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains:markdown:0.5.2=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jsoup:jsoup:1.16.1=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.ow2.asm:asm-tree:9.6=bcv-rt-jvm-cp-resolver +org.ow2.asm:asm:9.6=bcv-rt-jvm-cp-resolver +org.slf4j:slf4j-api:1.7.36=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +empty=annotationProcessor,compileOnlyDependenciesMetadata,dokkaPlugin,dokkaRuntime,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,testAnnotationProcessor,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions diff --git a/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt b/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt new file mode 100644 index 000000000..c235ff63e --- /dev/null +++ b/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt @@ -0,0 +1,332 @@ +package com.posthog.compliance + +import com.google.gson.Gson +import com.google.gson.JsonParser +import com.posthog.PostHogBeforeSend +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.request.httpMethod +import io.ktor.server.request.receive +import io.ktor.server.request.receiveChannel +import io.ktor.server.request.uri +import io.ktor.server.response.respondBytes +import io.ktor.server.response.respondText +import io.ktor.server.routing.IgnoreTrailingSlash +import io.ktor.server.routing.get +import io.ktor.server.routing.post +import io.ktor.server.routing.route +import io.ktor.server.routing.routing +import io.ktor.utils.io.jvm.javaio.toInputStream +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.File +import java.time.OffsetDateTime +import java.util.Date +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.TimeUnit +import java.util.concurrent.locks.ReentrantLock +import java.util.zip.GZIPInputStream +import kotlin.concurrent.withLock + +// Each initialization gets its own ingress URL and observation state, including during close(). +data class InitRequest( + val api_key: String, + val host: String, + val flush_at: Int? = null, + val flush_interval_ms: Int? = null, + val max_retries: Int? = null, + val enable_compression: Boolean? = null, +) + +data class CaptureRequest( + val distinct_id: String, + val event: String, + val properties: Map? = null, + val timestamp: String? = null, +) { + fun date(): Date? = timestamp?.let { Date.from(OffsetDateTime.parse(it).toInstant()) } +} + +data class FlagRequest( + val key: String, + val distinct_id: String? = null, + val person_properties: Map? = null, + val groups: Map? = null, + val group_properties: Map>? = null, + val disable_geoip: Boolean? = null, + val force_remote: Boolean? = null, +) + +interface SdkClient { + fun capture(request: CaptureRequest) + + fun flag(request: FlagRequest): Any? + + fun flush() + + fun close() +} + +interface SdkProfile { + val name: String + val version: String + + fun create( + request: InitRequest, + storage: File, + observer: Observation, + ): SdkClient +} + +data class RequestRecord( + val timestamp_ms: Long, + val status_code: Int, + val retry_attempt: Int, + val event_count: Int, + val uuid_list: List, +) + +class Observation { + private val captured = linkedSetOf() + private val sent = mutableSetOf() + private val attempts = mutableMapOf() + private val requests = mutableListOf() + private var lastError: String? = null + + private val candidates = ThreadLocal>() + val beforeSend = + PostHogBeforeSend { event -> + event.uuid?.let { candidates.get()?.add(it.toString()) } + event + } + + fun track(action: () -> T): T { + val ids = mutableListOf() + candidates.set(ids) + try { + return action() + } finally { + // Stateful capture builds an intermediate event, then the stateless base builds + // the queued event. Only the final beforeSend UUID belongs to the queue. + synchronized(this) { ids.lastOrNull()?.let { captured.add(it) } } + candidates.remove() + } + } + + @Synchronized fun capturedIds(): Set = captured.toSet() + + @Synchronized fun sentCount(): Int = sent.size + + @Synchronized fun pendingCount(): Int = (captured - sent).size + + @Synchronized fun record( + bytes: ByteArray, + encoding: String?, + status: Int, + started: Long, + ) { + val decoded = if (encoding == "gzip") GZIPInputStream(bytes.inputStream()).use { it.readBytes() } else bytes + val ids = + JsonParser.parseString(decoded.toString(Charsets.UTF_8)).asJsonObject["batch"].asJsonArray + .map { it.asJsonObject["uuid"].asString } + captured.addAll(ids) + val attempt = ids.maxOfOrNull { attempts[it] ?: 0 } ?: 0 + ids.forEach { attempts[it] = (attempts[it] ?: 0) + 1 } + requests.add(RequestRecord(started, status, attempt, ids.size, ids)) + if (status in 200..299) sent.addAll(ids) else lastError = "HTTP $status" + } + + @Synchronized fun state(): Map = + mapOf( + // Unacknowledged events are not a queue-depth API: terminal drops remain unresolved. + "pending_events" to pendingCount(), + "total_events_captured" to captured.size, + "total_events_sent" to sent.size, + "total_retries" to requests.count { it.retry_attempt > 0 }, + "last_error" to lastError, + "requests_made" to requests.toList(), + ) +} + +private class Session(val target: String, val storage: File) { + val observation = Observation() + lateinit var client: SdkClient + + private val lock = ReentrantLock() + private val settled = lock.newCondition() + private var closed = false + private var inFlight = 0 + + fun begin(): Boolean = + lock.withLock { + if (closed) { + false + } else { + inFlight++ + true + } + } + + fun end() = + lock.withLock { + inFlight-- + settled.signalAll() + } + + fun close() { + lock.withLock { + closed = true + var remaining = TimeUnit.SECONDS.toNanos(15) + while (inFlight > 0 && remaining > 0) remaining = settled.awaitNanos(remaining) + check(inFlight == 0) { "Previous session still has HTTP requests in flight" } + } + client.close() + storage.deleteRecursively() + } +} + +fun Application.complianceRoutes( + profile: SdkProfile, + port: Int, + storageRoot: File, +) { + install(IgnoreTrailingSlash) + val gson = Gson().newBuilder().serializeNulls().create() + val actions = Mutex() + val sessions = ConcurrentHashMap() + var active: Session? = null + val proxy = OkHttpClient.Builder().retryOnConnectionFailure(false).followRedirects(false).followSslRedirects(false).build() + val hopHeaders = setOf("host", "connection", "content-length", "transfer-encoding", "keep-alive", "upgrade", "proxy-connection") + + routing { + get("/health") { + call.respondText( + gson.toJson( + mapOf( + "sdk_name" to profile.name, + "sdk_version" to profile.version, + "adapter_version" to "1.0.0", + "supports_parallel" to false, + "capabilities" to listOf("capture_v0", "encoding_gzip"), + ), + ), + ContentType.Application.Json, + ) + } + route("/ingress/{id}/{path...}") { + handle { + val session = sessions[call.parameters["id"]] + if (session == null || !session.begin()) { + call.respondText("Session closed", status = HttpStatusCode.Gone) + return@handle + } + try { + val prefix = "/ingress/${call.parameters["id"]}" + val path = call.request.uri.removePrefix(prefix) + val bytes = withContext(Dispatchers.IO) { call.receiveChannel().toInputStream().readBytes() } + val started = System.currentTimeMillis() + val request = Request.Builder().url(session.target.trimEnd('/') + path) + call.request.headers.forEach { key, values -> + if (key.lowercase() !in hopHeaders) values.forEach { request.addHeader(key, it) } + } + request.method( + call.request.httpMethod.value, + if (call.request.httpMethod.value in listOf("GET", "HEAD")) null else bytes.toRequestBody(), + ) + withContext(Dispatchers.IO) { + proxy.newCall(request.build()).execute().use { response -> + if (path.substringBefore('?') == "/batch") { + session.observation.record(bytes, call.request.headers["Content-Encoding"], response.code, started) + } + response.headers.forEach { (key, value) -> + if (key.lowercase() !in hopHeaders) call.response.headers.append(key, value, safeOnly = false) + } + call.respondBytes(response.body?.bytes() ?: byteArrayOf(), status = HttpStatusCode.fromValue(response.code)) + } + } + } finally { + session.end() + } + } + } + post("/{action}") { + actions.withLock { + withContext(Dispatchers.IO) { + try { + val result: Any = + when (call.parameters["action"]) { + "init" -> { + active?.close() + sessions.clear() + val req = gson.fromJson(call.receive(), InitRequest::class.java) + val id = UUID.randomUUID().toString() + val session = Session(req.host, File(storageRoot, id).apply { mkdirs() }) + sessions[id] = session + session.client = + profile.create( + req.copy(host = "http://127.0.0.1:$port/ingress/$id"), + session.storage, + session.observation, + ) + active = session + mapOf("success" to true) + } + "capture" -> { + val session = checkNotNull(active) { "SDK not initialized" } + val before = session.observation.capturedIds() + session.client.capture(gson.fromJson(call.receive(), CaptureRequest::class.java)) + val uuid = (session.observation.capturedIds() - before).singleOrNull() + checkNotNull(uuid) { "SDK did not synchronously expose a captured UUID" } + mapOf("success" to true, "uuid" to uuid) + } + "get_feature_flag" -> { + val session = checkNotNull(active) { "SDK not initialized" } + val value = session.client.flag(gson.fromJson(call.receive(), FlagRequest::class.java)) + mapOf("success" to true, "value" to value) + } + "flush" -> { + val session = checkNotNull(active) { "SDK not initialized" } + val before = session.observation.sentCount() + session.client.flush() + val deadline = System.nanoTime() + java.util.concurrent.TimeUnit.SECONDS.toNanos(10) + while (session.observation.pendingCount() > 0 && System.nanoTime() < deadline) Thread.sleep(25) + mapOf( + "success" to (session.observation.pendingCount() == 0), + "events_flushed" to (session.observation.sentCount() - before), + ) + } + "reset" -> { + active?.close() + sessions.clear() + active = null + mapOf("success" to true) + } + else -> error("Unknown action") + } + call.respondText(gson.toJson(result), ContentType.Application.Json) + } catch (e: Exception) { + call.respondText( + gson.toJson(mapOf("success" to false, "error" to e.toString())), + ContentType.Application.Json, + HttpStatusCode.BadRequest, + ) + } + } + } + } + get("/state") { + actions.withLock { + call.respondText(gson.toJson(active?.observation?.state() ?: Observation().state()), ContentType.Application.Json) + } + } + } +} diff --git a/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/SdkProfiles.kt b/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/SdkProfiles.kt new file mode 100644 index 000000000..bd77f2391 --- /dev/null +++ b/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/SdkProfiles.kt @@ -0,0 +1,121 @@ +package com.posthog.compliance + +import com.posthog.PostHog +import com.posthog.PostHogConfig +import com.posthog.PostHogInterface +import com.posthog.internal.PostHogContext +import java.io.File +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +fun sdkVersion(profile: String): String = + java.util.Properties().apply { + SdkProfile::class.java.getResourceAsStream("/sdk-versions.properties").use { load(it) } + }.getProperty(profile) + +fun InitRequest.flushSeconds(): Int = maxOf(1, ((flush_interval_ms ?: 500) + 999) / 1000) + +class ReloadCompletion(private val config: PostHogConfig) { + fun await(action: (com.posthog.PostHogOnFeatureFlags) -> Unit) { + val latch = CountDownLatch(1) + val callback = com.posthog.PostHogOnFeatureFlags { latch.countDown() } + config.onFeatureFlags = callback + try { + action(callback) + check(latch.await(15, TimeUnit.SECONDS)) { "Feature flag reload did not complete" } + } finally { + config.onFeatureFlags = null + } + } +} + +fun configureStateful( + config: PostHogConfig, + request: InitRequest, + observer: Observation, +): ReloadCompletion { + config.flushAt = request.flush_at ?: 100 + config.flushIntervalSeconds = request.flushSeconds() + config.preloadFeatureFlags = false + request.max_retries?.let { config.maxRetries = it } + config.addBeforeSend(observer.beforeSend) + return ReloadCompletion(config) +} + +class StatefulClient( + private val sdk: PostHogInterface, + private val observer: Observation, + private val completion: ReloadCompletion, +) : SdkClient { + private val groups = mutableMapOf() + + override fun capture(request: CaptureRequest) { + observer.track { + sdk.capture( + event = request.event, + distinctId = request.distinct_id, + properties = request.properties, + timestamp = request.date(), + ) + } + } + + override fun flag(request: FlagRequest): Any? { + request.person_properties?.let { sdk.setPersonPropertiesForFlags(it, reloadFeatureFlags = false) } + request.group_properties?.forEach { (type, properties) -> + sdk.setGroupPropertiesForFlags(type, properties, reloadFeatureFlags = false) + } + var reloaded = false + request.groups?.forEach { (type, key) -> + if (groups[type] != key) { + // The first group assignment does not trigger an SDK reload; subsequent + // assignments do. Defer the first load until identity is set below. + if (groups.isEmpty()) { + observer.track { sdk.group(type, key) } + } else { + completion.await { observer.track { sdk.group(type, key) } } + reloaded = true + } + groups[type] = key + } + } + request.distinct_id?.let { distinctId -> + if (sdk.distinctId() != distinctId) { + completion.await { observer.track { sdk.identify(distinctId) } } + reloaded = true + } + } + // identify/group already reload through the public SDK. Await that load rather than + // consuming a second response with a redundant reload. Ordinary reads reload explicitly. + if (!reloaded) completion.await { sdk.reloadFeatureFlags(it) } + return observer.track { sdk.getFeatureFlag(request.key) } + } + + override fun flush() = sdk.flush() + + override fun close() = sdk.close() +} + +object CoreProfile : SdkProfile { + override val name = "posthog-core-integration" + override val version = PostHogConfig("").sdkVersion + + override fun create( + request: InitRequest, + storage: File, + observer: Observation, + ): SdkClient { + val config = PostHogConfig(request.api_key, request.host) + val completion = configureStateful(config, request, observer) + config.storagePrefix = storage.absolutePath + config.context = + object : PostHogContext { + override fun getStaticContext(): Map = emptyMap() + + override fun getDynamicContext(): Map = emptyMap() + + override fun getSdkInfo(): Map = mapOf("\$lib" to config.sdkName, "\$lib_version" to config.sdkVersion) + } + return StatefulClient(PostHog.with(config), observer, completion) + } +} diff --git a/sdk_compliance_adapter/common/src/main/resources/sdk-versions.properties b/sdk_compliance_adapter/common/src/main/resources/sdk-versions.properties new file mode 100644 index 000000000..20aab4199 --- /dev/null +++ b/sdk_compliance_adapter/common/src/main/resources/sdk-versions.properties @@ -0,0 +1,2 @@ +server=${serverVersion} +android=${androidVersion} diff --git a/sdk_compliance_adapter/docker-compose.yml b/sdk_compliance_adapter/docker-compose.yml index 2a9881cbd..910451e92 100644 --- a/sdk_compliance_adapter/docker-compose.yml +++ b/sdk_compliance_adapter/docker-compose.yml @@ -1,4 +1,4 @@ -name: posthog-android-sdk-compliance +name: posthog-jvm-sdk-compliance services: sdk-adapter: @@ -9,10 +9,11 @@ services: - test-network environment: - JAVA_TOOL_OPTIONS=-Xmx512m + - SDK_PROFILE=${SDK_PROFILE:-core} test-harness: - image: ghcr.io/posthog/sdk-test-harness:0.10.0 - command: ["run", "--adapter-url", "http://sdk-adapter:8080", "--mock-url", "http://test-harness:8081", "--sdk-type", "server", "--debug"] + image: ghcr.io/posthog/sdk-test-harness:1.0.0 + command: ["run", "--adapter-url", "http://sdk-adapter:8080", "--mock-url", "http://test-harness:8081", "--sdk-type", "server", "--concurrency", "1", "--debug"] networks: - test-network depends_on: diff --git a/sdk_compliance_adapter/gradle.lockfile b/sdk_compliance_adapter/gradle.lockfile index 2e7ee7987..7633ea4ac 100644 --- a/sdk_compliance_adapter/gradle.lockfile +++ b/sdk_compliance_adapter/gradle.lockfile @@ -2,72 +2,61 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. # To regenerate this file, run: ./gradlew :sdk_compliance_adapter:dependencies --write-locks -ch.qos.logback:logback-classic:1.4.14=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -ch.qos.logback:logback-core:1.4.14=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -com.fasterxml.jackson.core:jackson-annotations:2.12.7=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime -com.fasterxml.jackson.core:jackson-core:2.12.7=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime -com.fasterxml.jackson.core:jackson-databind:2.12.7.1=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime -com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.12.7=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime -com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.12.7=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime -com.fasterxml.jackson.module:jackson-module-kotlin:2.12.7=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime -com.fasterxml.jackson:jackson-bom:2.12.7=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime -com.fasterxml.woodstox:woodstox-core:6.2.4=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime -com.google.code.gson:gson:2.10.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.squareup.okhttp3:okhttp-bom:4.12.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -com.squareup.okhttp3:okhttp:4.12.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -com.squareup.okio:okio-jvm:3.6.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.squareup.okio:okio:3.6.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-annotations:2.12.7=dokkaGfmMultiModulePlugin,dokkaGfmMultiModuleRuntime,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlMultiModulePlugin,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllMultiModulePlugin,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +com.fasterxml.jackson.core:jackson-core:2.12.7=dokkaGfmMultiModulePlugin,dokkaGfmMultiModuleRuntime,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlMultiModulePlugin,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllMultiModulePlugin,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +com.fasterxml.jackson.core:jackson-databind:2.12.7.1=dokkaGfmMultiModulePlugin,dokkaGfmMultiModuleRuntime,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlMultiModulePlugin,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllMultiModulePlugin,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.12.7=dokkaGfmMultiModuleRuntime,dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.12.7=dokkaGfmMultiModuleRuntime,dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +com.fasterxml.jackson.module:jackson-module-kotlin:2.12.7=dokkaGfmMultiModulePlugin,dokkaGfmMultiModuleRuntime,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlMultiModulePlugin,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllMultiModulePlugin,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +com.fasterxml.jackson:jackson-bom:2.12.7=dokkaGfmMultiModulePlugin,dokkaGfmMultiModuleRuntime,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlMultiModulePlugin,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllMultiModulePlugin,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +com.fasterxml.woodstox:woodstox-core:6.2.4=dokkaGfmMultiModuleRuntime,dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +com.google.code.gson:gson:2.10.1=runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.squareup.okhttp3:mockwebserver:4.12.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.squareup.okhttp3:okhttp-bom:4.12.0=runtimeClasspath,testRuntimeClasspath +com.squareup.okhttp3:okhttp:4.12.0=runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.squareup.okio:okio-jvm:3.6.0=runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.squareup.okio:okio:3.6.0=runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.typesafe:config:1.4.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.ktor:ktor-events-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.ktor:ktor-events:2.3.7=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath io.ktor:ktor-http-cio-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.ktor:ktor-http-cio:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.ktor:ktor-http-cio:2.3.7=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath io.ktor:ktor-http-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.ktor:ktor-http:2.3.7=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath io.ktor:ktor-io-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.ktor:ktor-io:2.3.7=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath io.ktor:ktor-network-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.ktor:ktor-network:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.ktor:ktor-serialization-gson-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.ktor:ktor-serialization-gson:2.3.7=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +io.ktor:ktor-network:2.3.7=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath io.ktor:ktor-serialization-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.ktor:ktor-serialization:2.3.7=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -io.ktor:ktor-server-content-negotiation-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.ktor:ktor-server-content-negotiation:2.3.7=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +io.ktor:ktor-server-cio-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.ktor:ktor-server-cio:2.3.7=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath io.ktor:ktor-server-core-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.ktor:ktor-server-core:2.3.7=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath io.ktor:ktor-server-host-common-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.ktor:ktor-server-host-common:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.ktor:ktor-server-netty-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.ktor:ktor-server-netty:2.3.7=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +io.ktor:ktor-server-host-common:2.3.7=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath io.ktor:ktor-utils-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.ktor:ktor-utils:2.3.7=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath io.ktor:ktor-websockets-jvm:2.3.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.ktor:ktor-websockets:2.3.7=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -io.netty:netty-buffer:4.1.101.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-codec-http2:4.1.101.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-codec-http:4.1.101.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-codec:4.1.101.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-common:4.1.101.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-handler:4.1.101.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-resolver:4.1.101.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport-classes-epoll:4.1.101.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport-classes-kqueue:4.1.101.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport-native-epoll:4.1.101.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport-native-kqueue:4.1.101.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport-native-unix-common:4.1.101.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport:4.1.101.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -it.unimi.dsi:fastutil-core:8.5.12=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin -jakarta.activation:jakarta.activation-api:1.2.1=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime -jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime -org.codehaus.woodstox:stax2-api:4.2.1=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime -org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.freemarker:freemarker:2.3.32=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +it.unimi.dsi:fastutil-core:8.5.12=dokkaGfmMultiModulePlugin,dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlMultiModulePlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllMultiModulePlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +jakarta.activation:jakarta.activation-api:1.2.1=dokkaGfmMultiModuleRuntime,dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=dokkaGfmMultiModuleRuntime,dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +junit:junit:4.13.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.codehaus.woodstox:stax2-api:4.2.1=dokkaGfmMultiModuleRuntime,dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +org.freemarker:freemarker:2.3.32=dokkaGfmMultiModulePlugin,dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlMultiModulePlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllMultiModulePlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin org.fusesource.jansi:jansi:2.4.0=runtimeClasspath,testRuntimeClasspath -org.jetbrains.dokka:analysis-kotlin-descriptors:1.9.20=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin -org.jetbrains.dokka:analysis-markdown:1.9.20=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin -org.jetbrains.dokka:dokka-base:1.9.20=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin -org.jetbrains.dokka:dokka-core:1.9.20=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +org.hamcrest:hamcrest-core:1.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.dokka:all-modules-page-plugin:1.9.20=dokkaGfmMultiModulePlugin,dokkaHtmlMultiModulePlugin,dokkaJekyllMultiModulePlugin +org.jetbrains.dokka:analysis-kotlin-descriptors:1.9.20=dokkaGfmMultiModulePlugin,dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlMultiModulePlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllMultiModulePlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains.dokka:analysis-markdown:1.9.20=dokkaGfmMultiModulePlugin,dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlMultiModulePlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllMultiModulePlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains.dokka:dokka-base:1.9.20=dokkaGfmMultiModulePlugin,dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlMultiModulePlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllMultiModulePlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains.dokka:dokka-core:1.9.20=dokkaGfmMultiModuleRuntime,dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +org.jetbrains.dokka:gfm-plugin:1.9.20=dokkaGfmMultiModulePlugin,dokkaJekyllMultiModulePlugin +org.jetbrains.dokka:gfm-template-processing-plugin:1.9.20=dokkaGfmMultiModulePlugin,dokkaJekyllMultiModulePlugin +org.jetbrains.dokka:jekyll-plugin:1.9.20=dokkaJekyllMultiModulePlugin +org.jetbrains.dokka:jekyll-template-processing-plugin:1.9.20=dokkaJekyllMultiModulePlugin +org.jetbrains.dokka:templating-plugin:1.9.20=dokkaGfmMultiModulePlugin,dokkaHtmlMultiModulePlugin,dokkaJekyllMultiModulePlugin org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-build-tools-api:2.1.21=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-build-tools-impl:2.1.21=kotlinBuildToolsApiClasspath @@ -79,42 +68,40 @@ org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:2.1.21=kotlinKlibCommoniz org.jetbrains.kotlin:kotlin-metadata-jvm:2.1.21=bcv-rt-jvm-cp-resolver org.jetbrains.kotlin:kotlin-reflect:1.6.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-reflect:1.8.22=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-reflect:1.9.22=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlin:kotlin-reflect:1.9.22=dokkaGfmMultiModulePlugin,dokkaGfmMultiModuleRuntime,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlMultiModulePlugin,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllMultiModulePlugin,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime org.jetbrains.kotlin:kotlin-script-runtime:2.1.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-scripting-common:2.1.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:2.1.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:2.1.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-jvm:2.1.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest -org.jetbrains.kotlin:kotlin-stdlib-common:1.9.22=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.22=dokkaGfmMultiModulePlugin,dokkaGfmMultiModuleRuntime,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlMultiModulePlugin,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllMultiModulePlugin,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime org.jetbrains.kotlin:kotlin-stdlib-common:2.1.21=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.21=implementationDependenciesMetadata,testImplementationDependenciesMetadata -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10=compileClasspath,testCompileClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.1.21=runtimeClasspath,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20=dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.21=implementationDependenciesMetadata,testImplementationDependenciesMetadata -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10=compileClasspath,testCompileClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.21=runtimeClasspath,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib:1.9.22=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20=dokkaGfmMultiModuleRuntime,dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0=dokkaGfmMultiModulePlugin,dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlMultiModulePlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllMultiModulePlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.1.21=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20=dokkaGfmMultiModuleRuntime,dokkaGfmPartialRuntime,dokkaGfmRuntime,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialRuntime,dokkaHtmlRuntime,dokkaJavadocPartialRuntime,dokkaJavadocRuntime,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialRuntime,dokkaJekyllRuntime +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0=dokkaGfmMultiModulePlugin,dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlMultiModulePlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllMultiModulePlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.21=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.9.22=dokkaGfmMultiModulePlugin,dokkaGfmMultiModuleRuntime,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlMultiModulePlugin,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllMultiModulePlugin,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime org.jetbrains.kotlin:kotlin-stdlib:2.1.21=apiDependenciesMetadata,bcv-rt-jvm-cp-resolver,compileClasspath,implementationDependenciesMetadata,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-test-junit:2.1.21=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-test:2.1.21=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlinx:atomicfu:0.20.2=implementationDependenciesMetadata,testImplementationDependenciesMetadata org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3=dokkaGfmMultiModulePlugin,dokkaGfmMultiModuleRuntime,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlMultiModulePlugin,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllMultiModulePlugin,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3=dokkaGfmMultiModulePlugin,dokkaGfmMultiModuleRuntime,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlMultiModulePlugin,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllMultiModulePlugin,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3=dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3=dokkaGfmMultiModulePlugin,dokkaGfmMultiModuleRuntime,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlMultiModulePlugin,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllMultiModulePlugin,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.7.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.jetbrains.kotlinx:kotlinx-html-jvm:0.9.1=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains.kotlinx:kotlinx-html-jvm:0.9.1=dokkaGfmMultiModulePlugin,dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlMultiModulePlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllMultiModulePlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin org.jetbrains:annotations:13.0=bcv-rt-jvm-cp-resolver,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath -org.jetbrains:annotations:23.0.0=compileClasspath,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.jetbrains:markdown-jvm:0.5.2=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin -org.jetbrains:markdown:0.5.2=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin -org.jsoup:jsoup:1.16.1=dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains:annotations:23.0.0=compileClasspath,dokkaGfmMultiModulePlugin,dokkaGfmMultiModuleRuntime,dokkaGfmPartialPlugin,dokkaGfmPartialRuntime,dokkaGfmPlugin,dokkaGfmRuntime,dokkaHtmlMultiModulePlugin,dokkaHtmlMultiModuleRuntime,dokkaHtmlPartialPlugin,dokkaHtmlPartialRuntime,dokkaHtmlPlugin,dokkaHtmlRuntime,dokkaJavadocPartialPlugin,dokkaJavadocPartialRuntime,dokkaJavadocPlugin,dokkaJavadocRuntime,dokkaJekyllMultiModulePlugin,dokkaJekyllMultiModuleRuntime,dokkaJekyllPartialPlugin,dokkaJekyllPartialRuntime,dokkaJekyllPlugin,dokkaJekyllRuntime,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains:markdown-jvm:0.5.2=dokkaGfmMultiModulePlugin,dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlMultiModulePlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllMultiModulePlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jetbrains:markdown:0.5.2=dokkaGfmMultiModulePlugin,dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlMultiModulePlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllMultiModulePlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin +org.jsoup:jsoup:1.16.1=dokkaGfmMultiModulePlugin,dokkaGfmPartialPlugin,dokkaGfmPlugin,dokkaHtmlMultiModulePlugin,dokkaHtmlPartialPlugin,dokkaHtmlPlugin,dokkaJavadocPartialPlugin,dokkaJavadocPlugin,dokkaJekyllMultiModulePlugin,dokkaJekyllPartialPlugin,dokkaJekyllPlugin org.ow2.asm:asm-tree:9.6=bcv-rt-jvm-cp-resolver org.ow2.asm:asm:9.6=bcv-rt-jvm-cp-resolver -org.slf4j:slf4j-api:2.0.7=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.slf4j:slf4j-api:1.7.36=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath empty=annotationProcessor,compileOnlyDependenciesMetadata,dokkaPlugin,dokkaRuntime,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,testAnnotationProcessor,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions diff --git a/sdk_compliance_adapter/run-android.sh b/sdk_compliance_adapter/run-android.sh new file mode 100644 index 000000000..9ccaa8781 --- /dev/null +++ b/sdk_compliance_adapter/run-android.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail +mkdir -p report +# Set ANDROID_SERIAL when more than one device/emulator is attached. +cleanup() { + adb logcat -d > report/android-logcat.log + adb shell am force-stop com.posthog.compliance.android + adb forward --remove tcp:18292 + adb reverse --remove tcp:19292 +} +trap cleanup EXIT +adb install -r sdk_compliance_adapter/android/build/outputs/apk/debug/android-debug.apk +adb forward tcp:18292 tcp:18292 +adb reverse tcp:19292 tcp:19292 +adb shell am start -n com.posthog.compliance.android/.AdapterActivity --ei port 18292 +bash sdk_compliance_adapter/run-harness.sh 18292 19292 diff --git a/sdk_compliance_adapter/run-harness.sh b/sdk_compliance_adapter/run-harness.sh new file mode 100644 index 000000000..fee0d1532 --- /dev/null +++ b/sdk_compliance_adapter/run-harness.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail +adapter_port=${1:-18290} +mock_port=${2:-19290} +mkdir -p report +# Linux host networking also reaches an adb-forwarded Android listener. +docker run --rm --network host \ + -v "$PWD/report:/report" \ + ghcr.io/posthog/sdk-test-harness:1.0.0 \ + run --adapter-url "http://127.0.0.1:$adapter_port" \ + --mock-port "$mock_port" --mock-url "http://127.0.0.1:$mock_port" \ + --sdk-type server --concurrency 1 --report /report/compliance.json \ + 2>&1 | tee report/harness.log diff --git a/sdk_compliance_adapter/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt b/sdk_compliance_adapter/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt deleted file mode 100644 index a2cfe55d9..000000000 --- a/sdk_compliance_adapter/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt +++ /dev/null @@ -1,478 +0,0 @@ -package com.posthog.compliance - -import com.posthog.PostHog -import com.posthog.PostHogConfig -import com.posthog.internal.GzipRequestInterceptor -import com.posthog.internal.PostHogApi -import com.posthog.internal.PostHogContext -import io.ktor.http.HttpStatusCode -import io.ktor.serialization.gson.gson -import io.ktor.server.application.call -import io.ktor.server.application.install -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.ktor.server.plugins.contentnegotiation.ContentNegotiation -import io.ktor.server.request.receive -import io.ktor.server.response.respond -import io.ktor.server.routing.get -import io.ktor.server.routing.post -import io.ktor.server.routing.routing -import okhttp3.Interceptor -import okhttp3.Response -import java.io.File -import java.util.UUID -import java.util.concurrent.TimeUnit -import java.util.concurrent.locks.Condition -import java.util.concurrent.locks.ReentrantLock -import kotlin.concurrent.withLock - -// File-backed event queue; wiped between tests (see /init and /reset). -private const val QUEUE_STORAGE_PREFIX = "/tmp/posthog-queue" - -// Upper bound on the wait for a flush()'d batch to reach the mock (generous for Docker). -private const val FLUSH_SETTLE_MS = 2000L - -/** - * PostHog Android SDK Compliance Adapter - * - * HTTP wrapper around the PostHog Android SDK for compliance testing. - */ - -data class RequestRecord( - val timestamp_ms: Long, - val status_code: Int, - val retry_attempt: Int, - val event_count: Int, - val uuid_list: List, -) - -data class AdapterState( - var pendingEvents: Int = 0, - var totalEventsCaptured: Int = 0, - var totalEventsSent: Int = 0, - var totalRetries: Int = 0, - var lastError: String? = null, - val requestsMade: MutableList = mutableListOf(), -) - -// Request/Response models -data class HealthResponse( - val sdk_name: String, - val sdk_version: String, - val adapter_version: String, - val capabilities: List, -) - -data class InitRequest( - val api_key: String, - val host: String, - val flush_at: Int? = null, - val flush_interval_ms: Int? = null, - val max_retries: Int? = null, - val enable_compression: Boolean? = null, -) - -data class CaptureRequest( - val distinct_id: String, - val event: String, - val properties: Map? = null, - val timestamp: String? = null, -) - -data class CaptureResponse( - val success: Boolean, - val uuid: String, -) - -data class FlagRequest( - val key: String, - val distinct_id: String? = null, - val person_properties: Map? = null, - val groups: Map? = null, - val group_properties: Map>? = null, - // Decoded for harness parity but not applied: no public per-request geoip toggle and - // the SDK always loads remotely (known SDK gaps). - val disable_geoip: Boolean? = null, - val force_remote: Boolean? = null, -) - -data class FlagResponse( - val success: Boolean, - val value: Any?, -) - -data class FlushResponse( - val success: Boolean, - val events_flushed: Int, -) - -data class StateResponse( - val pending_events: Int, - val total_events_captured: Int, - val total_events_sent: Int, - val total_retries: Int, - val last_error: String?, - val requests_made: List, -) - -data class SuccessResponse( - val success: Boolean, -) - -// Minimal context for testing (provides $lib and $lib_version) -class TestPostHogContext(private val sdkName: String, private val sdkVersion: String) : PostHogContext { - override fun getStaticContext(): Map = emptyMap() - - override fun getDynamicContext(): Map = emptyMap() - - override fun getSdkInfo(): Map = - mapOf( - "\$lib" to sdkName, - "\$lib_version" to sdkVersion, - ) -} - -// Global state -object AdapterContext { - val state = AdapterState() - val lock = ReentrantLock() - - // Signalled by the tracking interceptor when a /batch response lands (see awaitBatch). - val batchObserved: Condition = lock.newCondition() - var postHog: com.posthog.PostHogInterface? = null - var api: PostHogApi? = null - val capturedEvents = mutableListOf() // Store UUIDs of captured events -} - -// OkHttp Interceptor to track requests -class TrackingInterceptor : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - val request = chain.request() - val url = request.url.toString() - - // Copy the request body so we can read it - val requestBody = request.body - var eventCount = 0 - val uuidList = mutableListOf() - - // Parse request body before sending - if (url.contains("/batch") && !url.contains("/flags") && requestBody != null) { - try { - val buffer = okio.Buffer() - requestBody.writeTo(buffer) - val bodyString = buffer.readUtf8() - - // Parse JSON to extract UUIDs - // The body format is: {"api_key": "...", "batch": [{event}, {event}], "sent_at": "..."} - val uuidRegex = """"uuid"\s*:\s*"([^"]+)"""".toRegex() - uuidRegex.findAll(bodyString).forEach { match -> - uuidList.add(match.groupValues[1]) - } - eventCount = uuidList.size - } catch (_: Exception) { - // Ignore parsing errors - } - } - - val response = chain.proceed(request) - - // Track the response - if (url.contains("/batch") && !url.contains("/flags")) { - try { - // Extract retry count from URL if present - val retryCount = request.url.queryParameter("retry_count")?.toIntOrNull() ?: 0 - - AdapterContext.lock.withLock { - val record = - RequestRecord( - timestamp_ms = System.currentTimeMillis(), - status_code = response.code, - retry_attempt = retryCount, - event_count = eventCount, - uuid_list = uuidList, - ) - - AdapterContext.state.requestsMade.add(record) - - if (response.isSuccessful) { - AdapterContext.state.totalEventsSent += eventCount - AdapterContext.state.pendingEvents = maxOf(0, AdapterContext.state.pendingEvents - eventCount) - } - - if (retryCount > 0) { - AdapterContext.state.totalRetries++ - } - AdapterContext.batchObserved.signalAll() - } - } catch (_: Exception) { - // Ignore tracking errors - } - } - - return response - } -} - -// Block until [predicate] holds (signalled as each /batch response lands) or [timeoutMs] elapses. -private fun awaitBatch( - timeoutMs: Long, - predicate: () -> Boolean, -): Boolean = - AdapterContext.lock.withLock { - var remainingNanos = TimeUnit.MILLISECONDS.toNanos(timeoutMs) - while (!predicate() && remainingNanos > 0) { - remainingNanos = AdapterContext.batchObserved.awaitNanos(remainingNanos) - } - predicate() - } - -fun main() { - embeddedServer(Netty, port = 8080) { - install(ContentNegotiation) { - gson { - setPrettyPrinting() - } - } - - routing { - get("/health") { - call.respond( - HealthResponse( - sdk_name = "posthog-android", - sdk_version = PostHogConfig(apiKey = "").sdkVersion, - adapter_version = "1.0.0", - // Opt into the capture suites (android posts /batch with gzip). - capabilities = listOf("capture_v0", "encoding_gzip"), - ), - ) - } - - post("/init") { - val req = call.receive() - - AdapterContext.lock.withLock { - // Reset state - AdapterContext.state.pendingEvents = 0 - AdapterContext.state.totalEventsCaptured = 0 - AdapterContext.state.totalEventsSent = 0 - AdapterContext.state.totalRetries = 0 - AdapterContext.state.lastError = null - AdapterContext.state.requestsMade.clear() - AdapterContext.capturedEvents.clear() - - // Close existing instance if any - AdapterContext.postHog?.close() - AdapterContext.api = null - - // close() leaves queue files on disk; wipe so they don't replay into this test. - File(QUEUE_STORAGE_PREFIX).deleteRecursively() - - // Create new config - val flushIntervalMs = req.flush_interval_ms ?: 100 - val flushIntervalSeconds = maxOf(1, flushIntervalMs / 1000) // Min 1 second - - val config = - PostHogConfig( - apiKey = req.api_key, - host = req.host, - flushAt = req.flush_at ?: 1, - flushIntervalSeconds = flushIntervalSeconds, - debug = true, - preloadFeatureFlags = false, - ) - - // Create OkHttpClient with tracking and gzip interceptors - val httpClient = - okhttp3.OkHttpClient.Builder() - .addInterceptor(TrackingInterceptor()) - .addInterceptor(GzipRequestInterceptor(config)) - .build() - - config.httpClient = httpClient - - req.max_retries?.let { config.maxRetries = it } - - // Set storage prefix for file-backed queue - config.storagePrefix = QUEUE_STORAGE_PREFIX - - // Set minimal context to provide $lib and $lib_version - config.context = TestPostHogContext("posthog-android", config.sdkVersion) - - // Add beforeSend hook to track captured events - config.addBeforeSend { event -> - AdapterContext.lock.withLock { - event.uuid?.let { uuid -> - AdapterContext.capturedEvents.add(uuid.toString()) - } - } - event - } - - // Create PostHog instance and direct API helper for per-request flag evaluation. - AdapterContext.postHog = PostHog.with(config) - AdapterContext.api = PostHogApi(config) - } - - call.respond(SuccessResponse(success = true)) - } - - post("/capture") { - val req = call.receive() - - val ph = - AdapterContext.lock.withLock { - AdapterContext.postHog - } - - if (ph == null) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "SDK not initialized")) - return@post - } - - // Remember the count before capturing - val beforeCount = AdapterContext.capturedEvents.size - - // Capture event with distinct_id parameter (don't call identify separately) - val properties = req.properties?.toMutableMap() ?: mutableMapOf() - ph.capture( - event = req.event, - distinctId = req.distinct_id, - properties = properties, - ) - - // Get the UUID that was just captured (via beforeSend hook) - val uuid = - AdapterContext.lock.withLock { - AdapterContext.state.totalEventsCaptured++ - AdapterContext.state.pendingEvents++ - - // The last UUID added is the one we just captured - if (AdapterContext.capturedEvents.size > beforeCount) { - AdapterContext.capturedEvents.last() - } else { - // Fallback if beforeSend didn't fire yet - UUID.randomUUID().toString() - } - } - - call.respond(CaptureResponse(success = true, uuid = uuid)) - } - - post("/get_feature_flag") { - val req = call.receive() - - val ph = - AdapterContext.lock.withLock { - AdapterContext.postHog - } - - if (ph == null) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "SDK not initialized")) - return@post - } - - val api = AdapterContext.lock.withLock { AdapterContext.api } - if (api == null) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "SDK not initialized")) - return@post - } - - val distinctId = req.distinct_id ?: ph.distinctId() - val response = - api.flags( - distinctId = distinctId, - anonymousId = ph.distinctId(), - deviceId = ph.distinctId(), - groups = req.groups ?: emptyMap(), - personProperties = req.person_properties, - groupProperties = req.group_properties, - flagKeys = listOf(req.key), - disableGeoip = req.disable_geoip ?: false, - ) - val flagDetails = response?.flags?.get(req.key) - val value = - response?.featureFlags?.get(req.key) - ?: flagDetails?.let { it.variant ?: it.enabled } - - val requestsBefore = AdapterContext.lock.withLock { AdapterContext.state.requestsMade.size } - val flagCalledProperties = - mutableMapOf( - "\$feature_flag" to req.key, - "\$feature_flag_response" to (value ?: false), - "\$feature/${req.key}" to (value ?: false), - ) - flagDetails?.metadata?.hasExperiment?.let { flagCalledProperties["\$feature_flag_has_experiment"] = it } - ph.capture( - event = "\$feature_flag_called", - distinctId = distinctId, - properties = flagCalledProperties, - ) - AdapterContext.lock.withLock { - AdapterContext.state.totalEventsCaptured++ - AdapterContext.state.pendingEvents++ - } - ph.flush() - awaitBatch(FLUSH_SETTLE_MS) { AdapterContext.state.requestsMade.size > requestsBefore } - - call.respond(FlagResponse(success = true, value = value ?: false)) - } - - post("/flush") { - val pendingBefore = AdapterContext.lock.withLock { AdapterContext.state.pendingEvents } - AdapterContext.postHog?.flush() - - // Wait until every pending event's batch is acked; bounded so a no-op flush can't hang. - if (pendingBefore > 0) { - awaitBatch(FLUSH_SETTLE_MS) { AdapterContext.state.pendingEvents == 0 } - } - - val eventsFlushed = - AdapterContext.lock.withLock { - val flushed = AdapterContext.state.totalEventsSent - AdapterContext.state.pendingEvents = 0 - flushed - } - - call.respond(FlushResponse(success = true, events_flushed = eventsFlushed)) - } - - get("/state") { - val stateSnapshot = - AdapterContext.lock.withLock { - StateResponse( - pending_events = AdapterContext.state.pendingEvents, - total_events_captured = AdapterContext.state.totalEventsCaptured, - total_events_sent = AdapterContext.state.totalEventsSent, - total_retries = AdapterContext.state.totalRetries, - last_error = AdapterContext.state.lastError, - requests_made = AdapterContext.state.requestsMade.toList(), - ) - } - - call.respond(stateSnapshot) - } - - post("/reset") { - AdapterContext.lock.withLock { - // close (not reset) — reset() reloads flags, lands as +1 /flags in the next test. - AdapterContext.postHog?.close() - AdapterContext.postHog = null - AdapterContext.api = null - - // close() leaves queue files on disk; wipe so they don't replay next test (matches iOS). - File(QUEUE_STORAGE_PREFIX).deleteRecursively() - - AdapterContext.state.pendingEvents = 0 - AdapterContext.state.totalEventsCaptured = 0 - AdapterContext.state.totalEventsSent = 0 - AdapterContext.state.totalRetries = 0 - AdapterContext.state.lastError = null - AdapterContext.state.requestsMade.clear() - AdapterContext.capturedEvents.clear() - } - - call.respond(SuccessResponse(success = true)) - } - } - }.start(wait = true) -} diff --git a/sdk_compliance_adapter/src/main/kotlin/com/posthog/compliance/Main.kt b/sdk_compliance_adapter/src/main/kotlin/com/posthog/compliance/Main.kt new file mode 100644 index 000000000..55bee04ff --- /dev/null +++ b/sdk_compliance_adapter/src/main/kotlin/com/posthog/compliance/Main.kt @@ -0,0 +1,17 @@ +package com.posthog.compliance + +import io.ktor.server.cio.CIO +import io.ktor.server.engine.embeddedServer +import java.io.File + +fun main() { + val port = System.getenv("PORT")?.toInt() ?: 8080 + val profile = + when (System.getenv("SDK_PROFILE") ?: "core") { + "core" -> CoreProfile + "server" -> ServerProfile + else -> error("SDK_PROFILE must be core or server") + } + val storage = File(System.getProperty("java.io.tmpdir"), "posthog-compliance-$port") + embeddedServer(CIO, port = port) { complianceRoutes(profile, port, storage) }.start(wait = true) +} diff --git a/sdk_compliance_adapter/src/main/kotlin/com/posthog/compliance/ServerProfile.kt b/sdk_compliance_adapter/src/main/kotlin/com/posthog/compliance/ServerProfile.kt new file mode 100644 index 000000000..753615e8a --- /dev/null +++ b/sdk_compliance_adapter/src/main/kotlin/com/posthog/compliance/ServerProfile.kt @@ -0,0 +1,54 @@ +package com.posthog.compliance + +import java.io.File + +object ServerProfile : SdkProfile { + override val name = "posthog-java-server" + override val version = sdkVersion("server") + + override fun create( + request: InitRequest, + storage: File, + observer: Observation, + ): SdkClient { + val config = + com.posthog.server.PostHogConfig( + request.api_key, + request.host, + flushAt = request.flush_at ?: 100, + flushIntervalSeconds = request.flushSeconds(), + featureFlagCacheSize = 0, + localEvaluation = false, + ) + config.addBeforeSend(observer.beforeSend) + val sdk = com.posthog.server.PostHog.with(config) + return object : SdkClient { + override fun capture(request: CaptureRequest) { + observer.track { + sdk.capture( + distinctId = request.distinct_id, + event = request.event, + properties = request.properties, + timestamp = request.date(), + ) + } + } + + override fun flag(request: FlagRequest): Any? = + observer.track { + sdk.evaluateFlags( + distinctId = request.distinct_id, + groups = request.groups, + personProperties = request.person_properties, + groupProperties = request.group_properties, + flagKeys = listOf(request.key), + disableGeoip = request.disable_geoip ?: false, + ).getFlag(request.key) + } + + override fun flush() = sdk.flush() + + override fun close() = sdk.close() + } + } +} diff --git a/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ComplianceAdapterTest.kt b/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ComplianceAdapterTest.kt new file mode 100644 index 000000000..b8bb7ae22 --- /dev/null +++ b/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ComplianceAdapterTest.kt @@ -0,0 +1,113 @@ +package com.posthog.compliance + +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import io.ktor.server.cio.CIO +import io.ktor.server.engine.embeddedServer +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import okhttp3.mockwebserver.RecordedRequest +import org.junit.Test +import java.nio.file.Files +import java.util.concurrent.TimeUnit +import java.util.zip.GZIPInputStream +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ComplianceAdapterTest { + private val http = OkHttpClient() + + private fun fixture(name: String): String = javaClass.getResource("/json/$name")!!.readText() + + private fun action( + name: String, + body: String = "{}", + ): JsonObject = + http.newCall( + Request.Builder().url("http://127.0.0.1:18293/$name") + .post(body.toRequestBody("application/json".toMediaType())).build(), + ).execute().use { + check(it.isSuccessful) { it.body?.string().orEmpty() } + JsonParser.parseString(it.body!!.string()).asJsonObject + } + + private fun withAdapter( + profile: SdkProfile, + test: (MockWebServer) -> Unit, + ) { + val mock = MockWebServer() + mock.dispatcher = + object : Dispatcher() { + override fun dispatch(request: RecordedRequest): MockResponse = + MockResponse().setHeader("Content-Type", "application/json").setBody( + if (request.path!!.startsWith("/flags/")) { + fixture("flags-v2.json") + } else { + "{}" + }, + ) + } + mock.start(19293) + val storage = Files.createTempDirectory("compliance-test").toFile() + val server = embeddedServer(CIO, port = 18293) { complianceRoutes(profile, 18293, storage) }.start() + try { + action("init", """{"api_key":"phc_test","host":"http://127.0.0.1:19293","flush_at":100}""") + test(mock) + } finally { + action("reset") + server.stop(0, 1000) + mock.shutdown() + storage.deleteRecursively() + http.connectionPool.evictAll() + } + } + + private fun captureTimestamp(profile: SdkProfile) = + withAdapter(profile) { mock -> + val captured = action("capture", fixture("capture-offset.json")) + val flushed = action("flush") + assertTrue(flushed["success"].asBoolean) + assertEquals(1, flushed["events_flushed"].asInt) + val request = mock.takeRequest(5, TimeUnit.SECONDS)!! + assertEquals("/batch", request.path) + assertEquals("gzip", request.getHeader("Content-Encoding")) + val body = GZIPInputStream(request.body.inputStream()).reader().readText() + val event = JsonParser.parseString(body).asJsonObject["batch"].asJsonArray.single().asJsonObject + assertEquals(captured["uuid"].asString, event["uuid"].asString) + assertEquals("2025-01-02T03:04:05.000Z", event["timestamp"].asString) + assertEquals("2025-01-02T08:34:05+05:30", event["properties"].asJsonObject["timestamp_like"].asString) + assertEquals(0, action("flush")["events_flushed"].asInt) + } + + @Test fun coreTimestampAndReturnedUuidComeFromQueuedEvent() = captureTimestamp(CoreProfile) + + @Test fun serverTimestampAndReturnedUuidComeFromQueuedEvent() = captureTimestamp(ServerProfile) + + private fun flags(profile: SdkProfile) = + withAdapter(profile) { mock -> + val result = action("get_feature_flag", """{"key":"test-flag","distinct_id":"user","force_remote":true}""") + assertEquals("variant-a", result["value"].asString) + val flags = mock.takeRequest(5, TimeUnit.SECONDS)!! + assertEquals("/flags/?v=2", flags.path) + assertEquals(null, flags.getHeader("Authorization")) + action("flush") + val batches = generateSequence { mock.takeRequest(300, TimeUnit.MILLISECONDS) }.filter { it.path == "/batch" }.toList() + val events = + batches.flatMap { + val body = GZIPInputStream(it.body.inputStream()).reader().readText() + JsonParser.parseString(body).asJsonObject["batch"].asJsonArray.toList() + } + assertEquals(1, events.count { it.asJsonObject["event"].asString == "\$feature_flag_called" }) + assertFalse(events.isEmpty()) + } + + @Test fun coreReloadUsesSdkResultAndCalledEvent() = flags(CoreProfile) + + @Test fun serverSnapshotUsesSdkResultAndCalledEvent() = flags(ServerProfile) +} diff --git a/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ObservationTest.kt b/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ObservationTest.kt new file mode 100644 index 000000000..e7cee1e88 --- /dev/null +++ b/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ObservationTest.kt @@ -0,0 +1,28 @@ +package com.posthog.compliance + +import org.junit.Test +import kotlin.test.assertEquals + +class ObservationTest { + private val batch = """{"batch":[{"uuid":"0198bd44-1234-7000-8000-000000000001"}]}""".toByteArray() + + @Test + fun failedResponseDoesNotClaimQueueCompletion() { + val observation = Observation() + observation.record(batch, null, 400, 1) + assertEquals(1, observation.pendingCount()) + assertEquals(0, observation.sentCount()) + assertEquals("HTTP 400", observation.state()["last_error"]) + } + + @Test + fun retriesCountRepeatedWireUuidsWithoutDoubleCountingDelivery() { + val observation = Observation() + observation.record(batch, null, 503, 1) + observation.record(batch, null, 200, 2) + assertEquals(0, observation.pendingCount()) + assertEquals(1, observation.sentCount()) + assertEquals(1, observation.state()["total_events_captured"]) + assertEquals(1, observation.state()["total_retries"]) + } +} diff --git a/sdk_compliance_adapter/src/test/resources/json/capture-offset.json b/sdk_compliance_adapter/src/test/resources/json/capture-offset.json new file mode 100644 index 000000000..c2fef8d71 --- /dev/null +++ b/sdk_compliance_adapter/src/test/resources/json/capture-offset.json @@ -0,0 +1,6 @@ +{ + "event": "test", + "distinct_id": "user", + "timestamp": "2025-01-02T08:34:05+05:30", + "properties": {"timestamp_like": "2025-01-02T08:34:05+05:30"} +} diff --git a/sdk_compliance_adapter/src/test/resources/json/flags-v2.json b/sdk_compliance_adapter/src/test/resources/json/flags-v2.json new file mode 100644 index 000000000..3d5b79ef0 --- /dev/null +++ b/sdk_compliance_adapter/src/test/resources/json/flags-v2.json @@ -0,0 +1,12 @@ +{ + "featureFlags": {"test-flag": "variant-a"}, + "flags": { + "test-flag": { + "key": "test-flag", + "enabled": true, + "variant": "variant-a", + "metadata": {"id": 1, "version": 1, "payload": null}, + "reason": {"kind": "condition_match", "condition_index": 0} + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 9ccdc7f6d..aecae1b2b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,6 +20,14 @@ include(":posthog-android") include(":posthog-android-surveys-compose") include(":posthog-server") +// Test-only HTTP adapter; opt in so ordinary library builds are unchanged. +if (providers.gradleProperty("compliance").isPresent) { + include(":sdk_compliance_adapter", ":sdk_compliance_adapter:common") + if (providers.gradleProperty("complianceAndroid").isPresent) { + include(":sdk_compliance_adapter:android") + } +} + // samples include(":posthog-samples:posthog-android-sample") include(":posthog-samples:posthog-java-sample") From 40685585d175c05533cf516db74ad3b07b61d204 Mon Sep 17 00:00:00 2001 From: Dustin Byrne Date: Mon, 7 Sep 2026 17:52:00 -0400 Subject: [PATCH 2/5] test: reject unsupported stateful flag identity changes --- sdk_compliance_adapter/CONTRIBUTING.md | 2 ++ .../kotlin/com/posthog/compliance/SdkProfiles.kt | 9 ++++++++- .../posthog/compliance/ComplianceAdapterTest.kt | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/sdk_compliance_adapter/CONTRIBUTING.md b/sdk_compliance_adapter/CONTRIBUTING.md index d43dbc405..907add3e6 100644 --- a/sdk_compliance_adapter/CONTRIBUTING.md +++ b/sdk_compliance_adapter/CONTRIBUTING.md @@ -52,6 +52,8 @@ Person/group properties use public setters. Identity and groups use public `identify`/`group`, retaining their SDK-owned events and reloads. The adapter awaits those reload callbacks; otherwise it explicitly reloads and reads the public cached getter. No controller parses flag responses or manufactures called-events. +Once identified, changing to another user requires adapter reset/init; unsupported +identity transitions are rejected before changing SDK state or waiting for a reload. Multi-group actions can cause multiple SDK requests. Singleton flag-key scope, per-call GeoIP overrides and compound person-property assertions are deferred stateful/server-contract differences, but remain selected and visible in reports. diff --git a/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/SdkProfiles.kt b/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/SdkProfiles.kt index bd77f2391..3321c3197 100644 --- a/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/SdkProfiles.kt +++ b/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/SdkProfiles.kt @@ -48,6 +48,7 @@ class StatefulClient( private val completion: ReloadCompletion, ) : SdkClient { private val groups = mutableMapOf() + private var identified = false override fun capture(request: CaptureRequest) { observer.track { @@ -61,6 +62,9 @@ class StatefulClient( } override fun flag(request: FlagRequest): Any? { + require(!identified || request.distinct_id == null || request.distinct_id == sdk.distinctId()) { + "Changing an identified user requires reset/init before evaluating flags for another user" + } request.person_properties?.let { sdk.setPersonPropertiesForFlags(it, reloadFeatureFlags = false) } request.group_properties?.forEach { (type, properties) -> sdk.setGroupPropertiesForFlags(type, properties, reloadFeatureFlags = false) @@ -81,7 +85,10 @@ class StatefulClient( } request.distinct_id?.let { distinctId -> if (sdk.distinctId() != distinctId) { - completion.await { observer.track { sdk.identify(distinctId) } } + completion.await { + observer.track { sdk.identify(distinctId) } + identified = sdk.distinctId() == distinctId + } reloaded = true } } diff --git a/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ComplianceAdapterTest.kt b/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ComplianceAdapterTest.kt index b8bb7ae22..781d23b1d 100644 --- a/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ComplianceAdapterTest.kt +++ b/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ComplianceAdapterTest.kt @@ -17,6 +17,7 @@ import java.nio.file.Files import java.util.concurrent.TimeUnit import java.util.zip.GZIPInputStream import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -110,4 +111,19 @@ class ComplianceAdapterTest { @Test fun coreReloadUsesSdkResultAndCalledEvent() = flags(CoreProfile) @Test fun serverSnapshotUsesSdkResultAndCalledEvent() = flags(ServerProfile) + + @Test(timeout = 10_000) + fun coreRejectsChangingAnIdentifiedFlagUserWithoutReloading() = + withAdapter(CoreProfile) { mock -> + action("get_feature_flag", """{"key":"test-flag","distinct_id":"user-a"}""") + val requestsBefore = mock.requestCount + val error = + assertFailsWith { + action("get_feature_flag", """{"key":"test-flag","distinct_id":"user-b"}""") + } + assertTrue(error.message.orEmpty().contains("requires reset/init")) + assertEquals(requestsBefore, mock.requestCount) + val result = action("get_feature_flag", """{"key":"test-flag","distinct_id":"user-a"}""") + assertEquals("variant-a", result["value"].asString) + } } From 71445e12fb9b69adc64d9a2d1b9bd456728a89da Mon Sep 17 00:00:00 2001 From: Dustin Byrne Date: Tue, 8 Sep 2026 10:36:26 -0400 Subject: [PATCH 3/5] test: prevent passive proxy HTTP follow-up retries --- .../posthog/compliance/ComplianceAdapter.kt | 3 +- .../com/posthog/compliance/PassiveProxy.kt | 26 ++++++++++ .../compliance/ComplianceAdapterTest.kt | 51 +++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/PassiveProxy.kt diff --git a/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt b/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt index c235ff63e..b5a3c7517 100644 --- a/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt +++ b/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt @@ -24,7 +24,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext -import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import java.io.File @@ -204,7 +203,7 @@ fun Application.complianceRoutes( val actions = Mutex() val sessions = ConcurrentHashMap() var active: Session? = null - val proxy = OkHttpClient.Builder().retryOnConnectionFailure(false).followRedirects(false).followSslRedirects(false).build() + val proxy = passiveProxyClient() val hopHeaders = setOf("host", "connection", "content-length", "transfer-encoding", "keep-alive", "upgrade", "proxy-connection") routing { diff --git a/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/PassiveProxy.kt b/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/PassiveProxy.kt new file mode 100644 index 000000000..375c3d104 --- /dev/null +++ b/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/PassiveProxy.kt @@ -0,0 +1,26 @@ +package com.posthog.compliance + +import okhttp3.OkHttpClient + +private class UpstreamStatus(var code: Int? = null) + +internal fun passiveProxyClient(): OkHttpClient = + OkHttpClient.Builder() + .retryOnConnectionFailure(false) + .followRedirects(false) + .followSslRedirects(false) + .addInterceptor { chain -> + val status = UpstreamStatus() + val request = chain.request().newBuilder().tag(UpstreamStatus::class.java, status).build() + val response = chain.proceed(request) + response.newBuilder().code(checkNotNull(status.code)).build() + } + .addNetworkInterceptor { chain -> + val response = chain.proceed(chain.request()) + checkNotNull(chain.request().tag(UpstreamStatus::class.java)).code = response.code + // OkHttp's 503/Retry-After: 0 follow-up ignores retryOnConnectionFailure(false), + // including for bodyless requests. Hide the status only from its follow-up layer; + // the application interceptor restores it before forwarding or observation. + response.newBuilder().code(200).build() + } + .build() diff --git a/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ComplianceAdapterTest.kt b/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ComplianceAdapterTest.kt index 781d23b1d..85dd552de 100644 --- a/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ComplianceAdapterTest.kt +++ b/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ComplianceAdapterTest.kt @@ -13,6 +13,9 @@ import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.RecordedRequest import org.junit.Test +import java.io.File +import java.net.HttpURLConnection +import java.net.URL import java.nio.file.Files import java.util.concurrent.TimeUnit import java.util.zip.GZIPInputStream @@ -112,6 +115,54 @@ class ComplianceAdapterTest { @Test fun serverSnapshotUsesSdkResultAndCalledEvent() = flags(ServerProfile) + private fun ingressPreserves503WithoutFollowUp(method: String) { + lateinit var ingress: String + val profile = + object : SdkProfile by CoreProfile { + override fun create( + request: InitRequest, + storage: File, + observer: Observation, + ): SdkClient { + ingress = request.host + return CoreProfile.create(request, storage, observer) + } + } + withAdapter(profile) { mock -> + mock.dispatcher = + object : Dispatcher() { + override fun dispatch(request: RecordedRequest): MockResponse = + MockResponse().setResponseCode(503).setHeader("Retry-After", "0") + .setHeader("X-Upstream", "unchanged").setBody("upstream unavailable") + } + val before = mock.requestCount + val connection = URL("$ingress/probe").openConnection() as HttpURLConnection + try { + connection.requestMethod = method + connection.instanceFollowRedirects = false + connection.readTimeout = 5000 + if (method == "POST") { + connection.doOutput = true + connection.outputStream.use { it.write("original request".toByteArray()) } + } + assertEquals(503, connection.responseCode) + assertEquals("0", connection.getHeaderField("Retry-After")) + assertEquals("unchanged", connection.getHeaderField("X-Upstream")) + assertEquals("upstream unavailable", connection.errorStream.reader().readText()) + assertEquals(before + 1, mock.requestCount) + val request = mock.takeRequest(5, TimeUnit.SECONDS)!! + assertEquals(method, request.method) + assertEquals(if (method == "POST") "original request" else "", request.body.readUtf8()) + } finally { + connection.disconnect() + } + } + } + + @Test fun ingressDoesNotRetry503Post() = ingressPreserves503WithoutFollowUp("POST") + + @Test fun ingressDoesNotRetry503BodylessGet() = ingressPreserves503WithoutFollowUp("GET") + @Test(timeout = 10_000) fun coreRejectsChangingAnIdentifiedFlagUserWithoutReloading() = withAdapter(CoreProfile) { mock -> From 9f4847e36c8c1be4f81e030a8cafb5f2570d121b Mon Sep 17 00:00:00 2001 From: Dustin Byrne Date: Tue, 8 Sep 2026 10:44:14 -0400 Subject: [PATCH 4/5] test: preserve adapter integer and bodyless response fidelity --- .../posthog/compliance/ComplianceAdapter.kt | 3 +- .../com/posthog/compliance/PassiveProxy.kt | 2 +- .../compliance/ComplianceAdapterTest.kt | 49 ++++++++++++++++--- .../json/capture-large-integers.json | 8 +++ 4 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 sdk_compliance_adapter/src/test/resources/json/capture-large-integers.json diff --git a/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt b/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt index b5a3c7517..ffca857e6 100644 --- a/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt +++ b/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt @@ -2,6 +2,7 @@ package com.posthog.compliance import com.google.gson.Gson import com.google.gson.JsonParser +import com.google.gson.ToNumberPolicy import com.posthog.PostHogBeforeSend import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode @@ -199,7 +200,7 @@ fun Application.complianceRoutes( storageRoot: File, ) { install(IgnoreTrailingSlash) - val gson = Gson().newBuilder().serializeNulls().create() + val gson = Gson().newBuilder().setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE).serializeNulls().create() val actions = Mutex() val sessions = ConcurrentHashMap() var active: Session? = null diff --git a/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/PassiveProxy.kt b/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/PassiveProxy.kt index 375c3d104..b71559cf2 100644 --- a/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/PassiveProxy.kt +++ b/sdk_compliance_adapter/common/src/main/kotlin/com/posthog/compliance/PassiveProxy.kt @@ -21,6 +21,6 @@ internal fun passiveProxyClient(): OkHttpClient = // OkHttp's 503/Retry-After: 0 follow-up ignores retryOnConnectionFailure(false), // including for bodyless requests. Hide the status only from its follow-up layer; // the application interceptor restores it before forwarding or observation. - response.newBuilder().code(200).build() + if (response.code == 503) response.newBuilder().code(200).build() else response } .build() diff --git a/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ComplianceAdapterTest.kt b/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ComplianceAdapterTest.kt index 85dd552de..612a46bc0 100644 --- a/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ComplianceAdapterTest.kt +++ b/sdk_compliance_adapter/src/test/kotlin/com/posthog/compliance/ComplianceAdapterTest.kt @@ -115,7 +115,25 @@ class ComplianceAdapterTest { @Test fun serverSnapshotUsesSdkResultAndCalledEvent() = flags(ServerProfile) - private fun ingressPreserves503WithoutFollowUp(method: String) { + @Test + fun capturePreservesLargeIntegerPropertiesOnTheWire() = + withAdapter(CoreProfile) { mock -> + action("capture", fixture("capture-large-integers.json")) + action("flush") + val request = mock.takeRequest(5, TimeUnit.SECONDS)!! + assertEquals("/batch", request.path) + val body = GZIPInputStream(request.body.inputStream()).reader().readText() + val event = JsonParser.parseString(body).asJsonObject["batch"].asJsonArray.single().asJsonObject + assertEquals("large-integer-properties", event["event"].asString) + val properties = event["properties"].asJsonObject + assertEquals(9007199254740993L, properties["exact"].asLong) + assertEquals(9007199254740993L, properties["nested"].asJsonObject["exact"].asLong) + } + + private fun ingressPreservesResponse( + method: String, + status: Int = 503, + ) { lateinit var ingress: String val profile = object : SdkProfile by CoreProfile { @@ -132,8 +150,14 @@ class ComplianceAdapterTest { mock.dispatcher = object : Dispatcher() { override fun dispatch(request: RecordedRequest): MockResponse = - MockResponse().setResponseCode(503).setHeader("Retry-After", "0") - .setHeader("X-Upstream", "unchanged").setBody("upstream unavailable") + MockResponse().setResponseCode(status).setHeader("X-Upstream", "unchanged").apply { + if (status == 503) { + setHeader("Retry-After", "0").setBody("upstream unavailable") + } else { + setHeader("Content-Encoding", "gzip").removeHeader("Content-Length") + setHeader("Connection", "close") + } + } } val before = mock.requestCount val connection = URL("$ingress/probe").openConnection() as HttpURLConnection @@ -145,10 +169,15 @@ class ComplianceAdapterTest { connection.doOutput = true connection.outputStream.use { it.write("original request".toByteArray()) } } - assertEquals(503, connection.responseCode) - assertEquals("0", connection.getHeaderField("Retry-After")) + assertEquals(status, connection.responseCode) assertEquals("unchanged", connection.getHeaderField("X-Upstream")) - assertEquals("upstream unavailable", connection.errorStream.reader().readText()) + if (status == 503) { + assertEquals("0", connection.getHeaderField("Retry-After")) + assertEquals("upstream unavailable", connection.errorStream.reader().readText()) + } else { + assertEquals("gzip", connection.getHeaderField("Content-Encoding")) + assertEquals("", connection.inputStream.reader().readText()) + } assertEquals(before + 1, mock.requestCount) val request = mock.takeRequest(5, TimeUnit.SECONDS)!! assertEquals(method, request.method) @@ -159,9 +188,13 @@ class ComplianceAdapterTest { } } - @Test fun ingressDoesNotRetry503Post() = ingressPreserves503WithoutFollowUp("POST") + @Test fun ingressDoesNotRetry503Post() = ingressPreservesResponse("POST") + + @Test fun ingressDoesNotRetry503BodylessGet() = ingressPreservesResponse("GET") + + @Test fun ingressPreservesBodyless304WithGzipMetadata() = ingressPreservesResponse("GET", 304) - @Test fun ingressDoesNotRetry503BodylessGet() = ingressPreserves503WithoutFollowUp("GET") + @Test fun ingressPreservesBodyless204WithGzipMetadata() = ingressPreservesResponse("GET", 204) @Test(timeout = 10_000) fun coreRejectsChangingAnIdentifiedFlagUserWithoutReloading() = diff --git a/sdk_compliance_adapter/src/test/resources/json/capture-large-integers.json b/sdk_compliance_adapter/src/test/resources/json/capture-large-integers.json new file mode 100644 index 000000000..fecc0cf7e --- /dev/null +++ b/sdk_compliance_adapter/src/test/resources/json/capture-large-integers.json @@ -0,0 +1,8 @@ +{ + "event": "large-integer-properties", + "distinct_id": "user", + "properties": { + "exact": 9007199254740993, + "nested": {"exact": 9007199254740993} + } +} From 93011693496f950697d74a1bd03ea051ed9067d1 Mon Sep 17 00:00:00 2001 From: Dustin Byrne Date: Tue, 8 Sep 2026 10:55:51 -0400 Subject: [PATCH 5/5] test: disable backups for Android compliance app --- sdk_compliance_adapter/android/src/main/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk_compliance_adapter/android/src/main/AndroidManifest.xml b/sdk_compliance_adapter/android/src/main/AndroidManifest.xml index a4fffd3c4..7c31fb64c 100644 --- a/sdk_compliance_adapter/android/src/main/AndroidManifest.xml +++ b/sdk_compliance_adapter/android/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ - +