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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 92 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
Expand Down Expand Up @@ -57,12 +57,15 @@
cpp: ${{ steps.filter.outputs.cpp }}
cpp_code: ${{ steps.filter.outputs.cpp_code }}
java_code: ${{ steps.filter.outputs.java_code }}
android: ${{ steps.filter.outputs.android }}
python: ${{ steps.filter.outputs.python }}
graalvm: ${{ steps.filter.outputs.graalvm }}
graalvm_kotlin: ${{ steps.filter.outputs.graalvm_kotlin }}
rust: ${{ steps.filter.outputs.rust }}
swift: ${{ steps.filter.outputs.swift }}
javascript: ${{ steps.filter.outputs.javascript }}
kotlin: ${{ steps.filter.outputs.kotlin }}
kotlin_benchmark: ${{ steps.filter.outputs.kotlin_benchmark }}
scala: ${{ steps.filter.outputs.scala }}
steps:
- uses: actions/checkout@v5
Expand All @@ -78,12 +81,15 @@
echo "cpp=true" >> "$GITHUB_OUTPUT"
echo "cpp_code=true" >> "$GITHUB_OUTPUT"
echo "java_code=true" >> "$GITHUB_OUTPUT"
echo "android=true" >> "$GITHUB_OUTPUT"
echo "python=true" >> "$GITHUB_OUTPUT"
echo "graalvm=true" >> "$GITHUB_OUTPUT"
echo "graalvm_kotlin=true" >> "$GITHUB_OUTPUT"
echo "rust=true" >> "$GITHUB_OUTPUT"
echo "swift=true" >> "$GITHUB_OUTPUT"
echo "javascript=true" >> "$GITHUB_OUTPUT"
echo "kotlin=true" >> "$GITHUB_OUTPUT"
echo "kotlin_benchmark=true" >> "$GITHUB_OUTPUT"
echo "scala=true" >> "$GITHUB_OUTPUT"
exit 0
fi
Expand Down Expand Up @@ -121,6 +127,12 @@
echo "java_code=false" >> "$GITHUB_OUTPUT"
fi

if grep -Eq '^(\.github/workflows/ci\.yml$|ci/run_ci\.(py|sh)$|ci/tasks/(common|kotlin)\.py$|java/(pom\.xml$|fory-(core|json|annotation-processor)/)|kotlin/|integration_tests/(kotlin_json_corpus|android_tests)/)' <<< "$changed_files"; then
echo "android=true" >> "$GITHUB_OUTPUT"
else
echo "android=false" >> "$GITHUB_OUTPUT"
fi

if grep -Eq '^(python/)' <<< "$changed_files"; then
echo "python=true" >> "$GITHUB_OUTPUT"
else
Expand All @@ -133,6 +145,12 @@
echo "graalvm=false" >> "$GITHUB_OUTPUT"
fi

if grep -Eq '^(\.github/workflows/ci\.yml$|ci/run_ci\.(py|sh)$|ci/tasks/(common|kotlin)\.py$|java/(pom\.xml$|fory-(core|json|annotation-processor)/)|kotlin/|integration_tests/(kotlin_json_corpus|graalvm_kotlin_tests)/)' <<< "$changed_files"; then
echo "graalvm_kotlin=true" >> "$GITHUB_OUTPUT"
else
echo "graalvm_kotlin=false" >> "$GITHUB_OUTPUT"
fi

if grep -Eq '^(rust/)' <<< "$changed_files"; then
echo "rust=true" >> "$GITHUB_OUTPUT"
else
Expand All @@ -151,12 +169,18 @@
echo "javascript=false" >> "$GITHUB_OUTPUT"
fi

if grep -Eq '^(java/|kotlin/)' <<< "$changed_files"; then
if grep -Eq '^(\.github/workflows/(ci\.yml|release-jvm-snapshot\.yaml)$|ci/run_ci\.(py|sh)$|ci/tasks/(common|java|kotlin)\.py$|ci/release\.py$|java/(pom\.xml$|fory-(core|json|annotation-processor)/)|kotlin/|integration_tests/(kotlin_json_corpus|graalvm_kotlin_tests|jpms_tests|grpc_tests/kotlin|idl_tests/kotlin)/)' <<< "$changed_files"; then
echo "kotlin=true" >> "$GITHUB_OUTPUT"
else
echo "kotlin=false" >> "$GITHUB_OUTPUT"
fi

if grep -Eq '^(\.github/workflows/ci\.yml$|ci/run_ci\.(py|sh)$|ci/tasks/(common|kotlin)\.py$|java/(pom\.xml$|fory-(core|json|annotation-processor)/)|kotlin/|benchmarks/kotlin/)' <<< "$changed_files"; then
echo "kotlin_benchmark=true" >> "$GITHUB_OUTPUT"
else
echo "kotlin_benchmark=false" >> "$GITHUB_OUTPUT"
fi

if grep -Eq '^(java/|scala/)' <<< "$changed_files"; then
echo "scala=true" >> "$GITHUB_OUTPUT"
else
Expand Down Expand Up @@ -287,7 +311,7 @@
android:
name: Android Instrumented Tests API ${{ matrix.api-level }}
needs: changes
if: needs.changes.outputs.java_code == 'true'
if: needs.changes.outputs.android == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand All @@ -300,6 +324,10 @@
with:
java-version: 17
distribution: "temurin"
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: 3.11
- name: Cache Maven local repository
uses: actions/cache@v4
with:
Expand All @@ -311,24 +339,23 @@
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e
with:
gradle-version: "8.13"
- name: Install Fory Java artifacts
run: |
cd java
mvn -T16 --no-transfer-progress -pl fory-json,fory-annotation-processor -am install -DskipTests -Dmaven.javadoc.skip=true -Dmaven.source.skip=true
- name: Install Fory Java and Kotlin JSON artifacts
run: python ./ci/run_ci.py kotlin --task install
- 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 Android instrumented tests
uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0
uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0
with:
api-level: ${{ matrix.api-level }}
arch: x86_64
target: default
working-directory: integration_tests/android_tests
script: |
yes | sdkmanager "platforms;android-${{ matrix.api-level }}" "build-tools;35.0.0"
if [ "${{ matrix.api-level }}" = "26" ]; then gradle --no-daemon --stacktrace verifyKotlinJsonRules; fi
if [ "${{ matrix.api-level }}" = "26" ]; then gradle --no-daemon --stacktrace -PforyTestBuildType=debug connectedCheck; fi
gradle --no-daemon --stacktrace -PforyTestBuildType=release connectedCheck
- name: Upload Android Test Report
Expand Down Expand Up @@ -623,6 +650,29 @@
shell: bash
run: ./ci/run_ci.sh graalvm_json_tests

graalvm_kotlin_json:
name: GraalVM Kotlin JSON CI
needs: changes
if: needs.changes.outputs.graalvm_kotlin == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: graalvm/setup-graalvm@6f3fa030c4b8f77c1f554a860f593a654538fa38 # 1.5.6
with:
java-version: "25"
distribution: "graalvm"
github-token: ${{ secrets.GITHUB_TOKEN }}
native-image-job-reports: "true"
- name: Cache Maven local repository
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Build and run Kotlin JSON native image
run: python ./ci/run_ci.py kotlin --task native-json

kotlin:
name: Kotlin CI
needs: changes
Expand All @@ -632,7 +682,7 @@
MY_VAR: "PATH"
strategy:
matrix:
java-version: ["8", "11", "17", "21"]
java-version: ["8", "11", "17", "21", "25", "26"]
steps:
- uses: actions/checkout@v5
- name: Set up JDK ${{ matrix.java-version }}
Expand All @@ -647,15 +697,45 @@
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Set up Python 3.8
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: 3.8
- name: Install fory java
run: python ./ci/run_ci.py java --install-jdks --install-fory
python-version: 3.11
- name: Run Kotlin CI
run: python ./ci/run_ci.py kotlin

kotlin_json_benchmark:
name: Kotlin JSON Benchmark Correctness
needs: changes
if: needs.changes.outputs.kotlin_benchmark == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: "temurin"
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: 3.11
- name: Cache Maven local repository
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Set up Gradle 9.3.0
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e
with:
gradle-version: "9.3.0"
- name: Install Fory Java and Kotlin JSON artifacts
run: python ./ci/run_ci.py kotlin --task install
- name: Verify benchmark correctness and Moshi adapters
run: gradle --no-daemon -p benchmarks/kotlin test verifyGeneratedJsonArtifacts jmhClasses

scala:
name: Scala CI
needs: changes
Expand Down Expand Up @@ -2011,8 +2091,6 @@
uses: actions/setup-python@v5
with:
python-version: 3.8
- name: Test release documentation updater
run: python -m unittest ci.test_release
- name: Set up .NET 8
uses: actions/setup-dotnet@v4
with:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,24 @@
# All `uses:` action pins in this workflow must come from the Apache action allowlist:
# https://github.com/apache/infrastructure-actions/blob/main/actions.yml

name: Publish Fory Java Snapshot
name: Publish Fory JVM Snapshot

on:
push:
branches:
- main
- release-java-snapshot
- release-jvm-snapshot

permissions:
contents: read

jobs:
publish-java:
publish-jvm:
runs-on: ubuntu-latest
if: github.repository == 'apache/fory'
steps:
- uses: actions/checkout@v5
- name: Set up Maven Central Repository
- name: Set up Apache snapshot repository
uses: actions/setup-java@v4
with:
java-version: "25"
Expand All @@ -42,8 +45,11 @@ jobs:
server-id: apache.snapshots.https
server-username: NEXUS_USERNAME
server-password: NEXUS_PASSWORD
- name: Publish Fory Java Snapshot
run: python ./ci/run_ci.py java --version 25 --release
- uses: sbt/setup-sbt@9d56cf12e9b58d219605e1d8bfe69a8395fedde0 # v1.5.1
with:
disk-cache: false
- name: Publish Java, Kotlin, and Scala snapshots
run: python ./ci/release.py publish_jvm --mode snapshot
env:
NEXUS_USERNAME: ${{ secrets.NEXUS_USER }}
NEXUS_PASSWORD: ${{ secrets.NEXUS_PW }}
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th
- Add comments only when behavior is hard to understand or an algorithm is non-obvious.
- Do not remove existing code comments unless they are stale, misleading, redundant, or no longer necessary after the change.
- Only add tests that verify internal behaviors or fix specific bugs; do not create unnecessary tests unless requested.
- Do not add unit tests for repository scripts. Validate scripts through their owning execution or
integration workflow instead of maintaining a parallel script-test suite.
- Do not add cleanup-sentinel tests that only pin deleted APIs or removed fields.
- Tests must exercise the actual code you wrote or changed. Do not write tests that pass by exercising a pre-existing code path that produces similar-looking results. Before writing a test, identify the exact new code path (annotation, codegen output, new API) and verify the test would fail if that code path were removed. When the change involves codegen or annotations, the test must use those annotations on real structs, run through the codegen pipeline, and verify the generated output drives the expected runtime behavior.
- Keep test method names concise. Name the behavior under test without encoding the whole scenario or expected result in the method name.
Expand Down
4 changes: 4 additions & 0 deletions benchmarks/kotlin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.gradle/
build/
reports/
__pycache__/
70 changes: 70 additions & 0 deletions benchmarks/kotlin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Kotlin JSON Benchmarks

This standalone Gradle/JMH project compares Fory JSON Kotlin, kotlinx.serialization, Moshi, and
Jackson Kotlin on one immutable Eishay `MediaContent` model. It is a repository benchmark project,
not a published Fory artifact.

The model uses only `val` properties and has no public zero-argument constructor. All four
libraries consume the same model and fixture; no library-specific data transfer object or measured
model conversion is used. The fixture SHA-256 is
`8faba2f57ab397f319aced5cf1e8411a76785557d4c7d1703ec9d540354310a1`.

## Compared operations

The suite contains exactly 16 methods: four libraries multiplied by String serialization, UTF-8
byte serialization, String deserialization, and UTF-8 byte deserialization.

Each library uses a retained declared-type or generated serializer API:

- Fory retains `jsonTypeRef<MediaContent>()`, disables asynchronous compilation for deterministic
setup, enables null emission, and warms all four generated paths before measurement.
- kotlinx.serialization retains `MediaContent.serializer()` and uses its String and stream APIs.
- Moshi retains its KSP-generated adapter and uses its String and Okio buffer APIs.
- Jackson Kotlin retains one `ObjectReader` and one `ObjectWriter` and uses its direct String and
byte APIs.

The final byte materialization required by a library remains inside its measured byte-serialization
method. Deserialization likewise includes any fresh in-memory stream or buffer required by that
library. No byte method routes through a prebuilt String.

## Correctness gates

Before timing, setup verifies that every library:

- decodes the exact fixture from String and UTF-8 bytes to the independent expected object;
- emits structurally equivalent JSON from String and byte APIs; and
- round-trips its own String and byte output.

The Gradle build also fails unless the Moshi KSP adapter is present for every object model. Fory
uses its normal HotSpot metadata path in this benchmark; Android retention rules do not participate
in the measured runtime.

## Build and run

Install the current `fory-json-kotlin` artifact in Maven local first. Use Gradle 9.3.0 and a JDK 17
or later toolchain.

Build the correctness and JMH artifacts:

```bash
gradle --no-daemon test verifyGeneratedJsonArtifacts jmhJar
```

Run the standard JMH benchmark:

```bash
python run_json_benchmark.py --output-dir reports/json
```

The runner invokes JMH once for all 16 methods. By default, each method uses three 2-second warmup
iterations and five 2-second measurement iterations. Standard JMH JSON is written to
`benchmark_results.json`; the full process log is retained in `benchmark.log`. For a quick harness
check without collecting publishable numbers, pass
`--warmup-iterations 1 --iterations 1 --duration 100ms`.

Use `--prepare-only` for CI correctness and Moshi adapter-generation checks without performance
timing.

See the [published Kotlin JSON benchmark report](../../docs/benchmarks/json/kotlin/README.md). The
published page explicitly remains pending until a complete measured run is available; the tooling
does not synthesize results or publish reports automatically.
Loading
Loading