From 35ea60ba1f0b9347422fd425e776de8375bfc4b6 Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Wed, 15 Jul 2026 17:57:35 +0800 Subject: [PATCH 1/5] fix(java): stabilize GraalVM field offsets --- .github/workflows/ci.yml | 35 +++- ci/run_ci.py | 3 + ci/run_ci.sh | 21 ++- .../java/org/apache/fory/graalvm/Main.java | 1 - .../org/apache/fory/memory/MemoryBuffer.java | 25 ++- .../fory/serializer/PlatformStringUtils.java | 149 +++++++----------- 6 files changed, 118 insertions(+), 116 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d98bddbc17..e6798d3478 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -575,7 +575,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java-version: ["17", "21", "25"] + java-version: ["17", "25"] steps: - uses: actions/checkout@v5 - uses: graalvm/setup-graalvm@6f3fa030c4b8f77c1f554a860f593a654538fa38 # 1.5.6 @@ -599,6 +599,37 @@ jobs: shell: bash run: python ./ci/run_ci.py java --version graalvm + graalvm_json: + name: GraalVM JSON CI + needs: changes + if: needs.changes.outputs.graalvm == 'true' + runs-on: ubuntu-latest + strategy: + matrix: + java-version: ["17", "25"] + steps: + - uses: actions/checkout@v5 + - uses: graalvm/setup-graalvm@6f3fa030c4b8f77c1f554a860f593a654538fa38 # 1.5.6 + with: + java-version: ${{ matrix.java-version }} + 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: Set up Python3.8 + uses: actions/setup-python@v5 + with: + python-version: 3.8 + - name: Build JSON native image and run + shell: bash + run: python ./ci/run_ci.py java --version graalvm_json_tests + kotlin: name: Kotlin CI needs: changes @@ -916,7 +947,7 @@ jobs: python-version: 3.11 cache: "pip" - name: Set up Dart - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c # v1.7.1 + uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c # v1.7.1 with: sdk: stable - name: Cache Maven local repository diff --git a/ci/run_ci.py b/ci/run_ci.py index 12192dcbd4..f0a84c1b65 100644 --- a/ci/run_ci.py +++ b/ci/run_ci.py @@ -319,6 +319,7 @@ def parse_args(): "windows_java21", "integration_tests", "graalvm", + "graalvm_json_tests", ], default=None, help="Java version to use for testing", @@ -423,6 +424,8 @@ def parse_args(): run_shell_script("windows_java21") elif version == "graalvm": run_shell_script("graalvm_test") + elif version == "graalvm_json_tests": + run_shell_script("graalvm_json_tests") else: run_shell_script(f"java{version}") elif command == "cpp": diff --git a/ci/run_ci.sh b/ci/run_ci.sh index 94360bb65c..c58549ca93 100755 --- a/ci/run_ci.sh +++ b/ci/run_ci.sh @@ -79,7 +79,10 @@ install_jdks() { done } -graalvm_test() { +run_graalvm_tests() { + local main_class="$1" + local java_version + local java_major java_version=$(java -version 2>&1 | awk -F '"' '/version/ {print $2; exit}') if [[ "$java_version" == 1.* ]]; then java_major=$(echo "$java_version" | cut -d. -f2) @@ -93,20 +96,28 @@ graalvm_test() { fi cd "$ROOT"/java mvn -T10 -B --no-transfer-progress clean install -DskipTests -pl '!:fory-testsuite' - echo "Start to build graalvm native image" + echo "Start to build GraalVM native image for $main_class" cd "$ROOT"/integration_tests/graalvm_tests - mvn -DskipTests=true --no-transfer-progress -Pnative clean package + mvn -DmainClass="$main_class" -DskipTests=true --no-transfer-progress -Pnative clean package echo "Built GraalVM classpath native image" echo "Start to run GraalVM classpath native image" ./target/main if [[ "$java_major" -ge 25 ]]; then export JDK_JAVA_OPTIONS="$(jdk25_javac_options)" fi - mvn -DskipTests=true --no-transfer-progress -Pnative-module clean package + mvn -DmainClass="$main_class" -DskipTests=true --no-transfer-progress -Pnative-module clean package echo "Built GraalVM module-path native image" echo "Start to run GraalVM module-path native image" ./target/main-module - echo "Execute graalvm tests succeed!" + echo "Execute GraalVM tests for $main_class succeed!" +} + +graalvm_test() { + run_graalvm_tests org.apache.fory.graalvm.Main +} + +graalvm_json_tests() { + run_graalvm_tests org.apache.fory.graalvm.ForyJsonExample } jdk25_access_options() { diff --git a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java index 14e290f31d..f84eeb50b5 100644 --- a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java +++ b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java @@ -46,6 +46,5 @@ public static void main(String[] args) throws Throwable { ExceptionExample.main(args); AbstractClassExample.main(args); FeatureTestExample.main(args); - ForyJsonExample.main(args); } } diff --git a/java/fory-core/src/main/java/org/apache/fory/memory/MemoryBuffer.java b/java/fory-core/src/main/java/org/apache/fory/memory/MemoryBuffer.java index c0f002ca98..8cd0929bd0 100644 --- a/java/fory-core/src/main/java/org/apache/fory/memory/MemoryBuffer.java +++ b/java/fory-core/src/main/java/org/apache/fory/memory/MemoryBuffer.java @@ -77,6 +77,7 @@ public final class MemoryBuffer { private static final int LONG_ARRAY_OFFSET; private static final int FLOAT_ARRAY_OFFSET; private static final int DOUBLE_ARRAY_OFFSET; + private static final long BUFFER_ADDRESS_FIELD_OFFSET; // GraalVM native-image recognizes arrayBaseOffset only when the call stores directly into the // target static field. Keep these assignments in this shape so native images recompute heap array @@ -91,6 +92,7 @@ public final class MemoryBuffer { LONG_ARRAY_OFFSET = 0; FLOAT_ARRAY_OFFSET = 0; DOUBLE_ARRAY_OFFSET = 0; + BUFFER_ADDRESS_FIELD_OFFSET = -1; } else { BOOLEAN_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(boolean[].class); BYTE_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(byte[].class); @@ -100,29 +102,24 @@ public final class MemoryBuffer { LONG_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(long[].class); FLOAT_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(float[].class); DOUBLE_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(double[].class); + try { + Field addressField = Buffer.class.getDeclaredField("address"); + // GraalVM native-image only recomputes a hosted object field offset when this call stores + // directly into its final static owner. Do not route it through a helper or local offset. + BUFFER_ADDRESS_FIELD_OFFSET = UNSAFE.objectFieldOffset(addressField); + checkArgument(BUFFER_ADDRESS_FIELD_OFFSET != 0); + } catch (NoSuchFieldException e) { + throw new IllegalStateException(e); + } } } /** Limits each raw Unsafe copy to let large copies hit safepoint polls between chunks. */ private static final long UNSAFE_COPY_THRESHOLD = 1024L * 1024L; - private static final long BUFFER_ADDRESS_FIELD_OFFSET = - AndroidSupport.IS_ANDROID ? -1 : bufferAddressFieldOffset(); - // Global allocator instance that can be customized private static volatile MemoryAllocator globalAllocator = new DefaultMemoryAllocator(); - private static long bufferAddressFieldOffset() { - try { - Field addressField = Buffer.class.getDeclaredField("address"); - long offset = UNSAFE.objectFieldOffset(addressField); - checkArgument(offset != 0); - return offset; - } catch (NoSuchFieldException e) { - throw new IllegalStateException(e); - } - } - private static boolean unaligned() { String arch = System.getProperty("os.arch", ""); if ("ppc64le".equals(arch) || "ppc64".equals(arch) || "s390x".equals(arch)) { diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/PlatformStringUtils.java b/java/fory-core/src/main/java/org/apache/fory/serializer/PlatformStringUtils.java index a5f3747b31..978d119550 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/PlatformStringUtils.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/PlatformStringUtils.java @@ -34,6 +34,14 @@ final class PlatformStringUtils { private static final Unsafe UNSAFE = AndroidSupport.IS_ANDROID ? null : _UnsafeUtils.UNSAFE; private static final int BYTE_ARRAY_OFFSET; private static final int CHAR_ARRAY_OFFSET; + static final boolean JDK_STRING_FIELD_ACCESS; + static final boolean STRING_VALUE_FIELD_IS_CHARS; + static final boolean STRING_VALUE_FIELD_IS_BYTES; + static final boolean STRING_HAS_COUNT_OFFSET; + private static final long STRING_VALUE_FIELD_OFFSET; + private static final long STRING_CODER_FIELD_OFFSET; + private static final long STRING_COUNT_FIELD_OFFSET; + private static final long STRING_OFFSET_FIELD_OFFSET; // GraalVM native-image needs arrayBaseOffset calls to store directly into their static fields so // it can recompute the offsets for the image runtime. @@ -45,65 +53,60 @@ final class PlatformStringUtils { BYTE_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(byte[].class); CHAR_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(char[].class); } - } - - private static final StringFields STRING_FIELDS = stringFields(); - - static final boolean JDK_STRING_FIELD_ACCESS = STRING_FIELDS.fieldAccess; - static final boolean STRING_VALUE_FIELD_IS_CHARS = - JDK_STRING_FIELD_ACCESS && STRING_FIELDS.valueFieldIsChars; - static final boolean STRING_VALUE_FIELD_IS_BYTES = - JDK_STRING_FIELD_ACCESS && STRING_FIELDS.valueFieldIsBytes; - static final boolean STRING_HAS_COUNT_OFFSET = JDK_STRING_FIELD_ACCESS && STRING_FIELDS.counted; - - private static final long STRING_VALUE_FIELD_OFFSET = STRING_FIELDS.valueOffset; - private static final long STRING_CODER_FIELD_OFFSET = STRING_FIELDS.coderOffset; - private static final long STRING_COUNT_FIELD_OFFSET = STRING_FIELDS.countOffset; - private static final long STRING_OFFSET_FIELD_OFFSET = STRING_FIELDS.offsetOffset; - - private PlatformStringUtils() {} - - private static StringFields stringFields() { if (AndroidSupport.IS_ANDROID || GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE || !_JDKAccess.JDK_INTERNAL_FIELD_ACCESS) { - return StringFields.noAccess(); - } - try { - Field valueField = String.class.getDeclaredField("value"); - boolean valueFieldIsChars = valueField.getType() == char[].class; - boolean valueFieldIsBytes = valueField.getType() == byte[].class; - long valueOffset = UNSAFE.objectFieldOffset(valueField); - Field countField = getStringFieldNullable("count"); - Field offsetField = getStringFieldNullable("offset"); - boolean counted = false; - long countOffset = -1; - long offsetOffset = -1; - if (countField != null || offsetField != null) { - Preconditions.checkArgument( - countField != null && offsetField != null, "Current jdk not supported"); - Preconditions.checkArgument( - countField.getType() == int.class && offsetField.getType() == int.class, - "Current jdk not supported"); - counted = true; - countOffset = UNSAFE.objectFieldOffset(countField); - offsetOffset = UNSAFE.objectFieldOffset(offsetField); + JDK_STRING_FIELD_ACCESS = false; + STRING_VALUE_FIELD_IS_CHARS = false; + STRING_VALUE_FIELD_IS_BYTES = false; + STRING_HAS_COUNT_OFFSET = false; + STRING_VALUE_FIELD_OFFSET = -1; + STRING_CODER_FIELD_OFFSET = -1; + STRING_COUNT_FIELD_OFFSET = -1; + STRING_OFFSET_FIELD_OFFSET = -1; + } else { + try { + Field valueField = String.class.getDeclaredField("value"); + boolean valueFieldIsChars = valueField.getType() == char[].class; + boolean valueFieldIsBytes = valueField.getType() == byte[].class; + JDK_STRING_FIELD_ACCESS = true; + STRING_VALUE_FIELD_IS_CHARS = valueFieldIsChars; + STRING_VALUE_FIELD_IS_BYTES = valueFieldIsBytes; + // GraalVM native-image only recomputes hosted object field offsets when each call stores + // directly into its final static owner. Do not route these through locals or holder + // objects. + STRING_VALUE_FIELD_OFFSET = UNSAFE.objectFieldOffset(valueField); + + Field countField = getStringFieldNullable("count"); + Field offsetField = getStringFieldNullable("offset"); + if (countField != null || offsetField != null) { + Preconditions.checkArgument( + countField != null && offsetField != null, "Current jdk not supported"); + Preconditions.checkArgument( + countField.getType() == int.class && offsetField.getType() == int.class, + "Current jdk not supported"); + STRING_HAS_COUNT_OFFSET = true; + STRING_COUNT_FIELD_OFFSET = UNSAFE.objectFieldOffset(countField); + STRING_OFFSET_FIELD_OFFSET = UNSAFE.objectFieldOffset(offsetField); + } else { + STRING_HAS_COUNT_OFFSET = false; + STRING_COUNT_FIELD_OFFSET = -1; + STRING_OFFSET_FIELD_OFFSET = -1; + } + if (valueFieldIsBytes) { + STRING_CODER_FIELD_OFFSET = + UNSAFE.objectFieldOffset(String.class.getDeclaredField("coder")); + } else { + STRING_CODER_FIELD_OFFSET = -1; + } + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); } - long coderOffset = valueFieldIsBytes ? stringCoderFieldOffset() : -1; - return new StringFields( - true, - valueFieldIsChars, - valueFieldIsBytes, - counted, - valueOffset, - coderOffset, - countOffset, - offsetOffset); - } catch (NoSuchFieldException e) { - throw new RuntimeException(e); } } + private PlatformStringUtils() {} + private static Field getStringFieldNullable(String fieldName) { try { return String.class.getDeclaredField(fieldName); @@ -112,48 +115,6 @@ private static Field getStringFieldNullable(String fieldName) { } } - private static long stringCoderFieldOffset() { - try { - return UNSAFE.objectFieldOffset(String.class.getDeclaredField("coder")); - } catch (NoSuchFieldException e) { - throw new RuntimeException(e); - } - } - - private static final class StringFields { - private final boolean fieldAccess; - private final boolean valueFieldIsChars; - private final boolean valueFieldIsBytes; - private final boolean counted; - private final long valueOffset; - private final long coderOffset; - private final long countOffset; - private final long offsetOffset; - - private StringFields( - boolean fieldAccess, - boolean valueFieldIsChars, - boolean valueFieldIsBytes, - boolean counted, - long valueOffset, - long coderOffset, - long countOffset, - long offsetOffset) { - this.fieldAccess = fieldAccess; - this.valueFieldIsChars = valueFieldIsChars; - this.valueFieldIsBytes = valueFieldIsBytes; - this.counted = counted; - this.valueOffset = valueOffset; - this.coderOffset = coderOffset; - this.countOffset = countOffset; - this.offsetOffset = offsetOffset; - } - - private static StringFields noAccess() { - return new StringFields(false, false, false, false, -1, -1, -1, -1); - } - } - static Object getStringValue(String value) { return UNSAFE.getObject(value, STRING_VALUE_FIELD_OFFSET); } From 4b1e91490a03d9f120d47fd43d50b2b5277a3930 Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Wed, 15 Jul 2026 18:24:06 +0800 Subject: [PATCH 2/5] ci: reduce GraalVM test latency --- ci/run_ci.sh | 11 ++++++++--- integration_tests/graalvm_tests/pom.xml | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ci/run_ci.sh b/ci/run_ci.sh index c58549ca93..ddd7c0ebf1 100755 --- a/ci/run_ci.sh +++ b/ci/run_ci.sh @@ -95,17 +95,22 @@ run_graalvm_tests() { unset JDK_JAVA_OPTIONS fi cd "$ROOT"/java - mvn -T10 -B --no-transfer-progress clean install -DskipTests -pl '!:fory-testsuite' + mvn -T10 -B --no-transfer-progress clean install \ + -pl fory-json -am \ + -DskipTests \ + -Dmaven.javadoc.skip=true echo "Start to build GraalVM native image for $main_class" cd "$ROOT"/integration_tests/graalvm_tests - mvn -DmainClass="$main_class" -DskipTests=true --no-transfer-progress -Pnative clean package + mvn -DmainClass="$main_class" -DskipTests=true -Dassembly.skipAssembly=true \ + --no-transfer-progress -Pnative clean package echo "Built GraalVM classpath native image" echo "Start to run GraalVM classpath native image" ./target/main if [[ "$java_major" -ge 25 ]]; then export JDK_JAVA_OPTIONS="$(jdk25_javac_options)" fi - mvn -DmainClass="$main_class" -DskipTests=true --no-transfer-progress -Pnative-module clean package + mvn -DmainClass="$main_class" -DskipTests=true -Dassembly.skipAssembly=true \ + --no-transfer-progress -Pnative-module package echo "Built GraalVM module-path native image" echo "Start to run GraalVM module-path native image" ./target/main-module diff --git a/integration_tests/graalvm_tests/pom.xml b/integration_tests/graalvm_tests/pom.xml index 13acb89b9d..be4ba87652 100644 --- a/integration_tests/graalvm_tests/pom.xml +++ b/integration_tests/graalvm_tests/pom.xml @@ -235,6 +235,9 @@ ${mainClass} true false + + false From 8e3713a50fc46987d91976baf2be653b4a489dbf Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Wed, 15 Jul 2026 18:43:34 +0800 Subject: [PATCH 3/5] ci: align GraalVM Python test runner --- .github/workflows/ci.yml | 2 +- ci/tasks/java.py | 46 +++++++++++++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6798d3478..3a4635c8f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -127,7 +127,7 @@ jobs: echo "python=false" >> "$GITHUB_OUTPUT" fi - if grep -Eq '^(java/|integration_tests/graalvm_tests/)' <<< "$changed_files"; then + if grep -Eq '^(\.github/workflows/ci\.yml$|ci/run_ci\.(py|sh)$|ci/tasks/(common|java)\.py$|java/|integration_tests/graalvm_tests/)' <<< "$changed_files"; then echo "graalvm=true" >> "$GITHUB_OUTPUT" else echo "graalvm=false" >> "$GITHUB_OUTPUT" diff --git a/ci/tasks/java.py b/ci/tasks/java.py index 8e6ecc0826..d7d95a3cc9 100644 --- a/ci/tasks/java.py +++ b/ci/tasks/java.py @@ -352,29 +352,53 @@ def run_integration_tests(): logging.info("Executing fory integration tests succeeds") -def run_graalvm_test(): - """Run GraalVM tests.""" - logging.info("Start GraalVM tests") +def run_graalvm_tests(main_class): + """Build and run classpath and module-path GraalVM tests.""" + logging.info(f"Start GraalVM tests for {main_class}") java_major = get_jdk_major_version() if java_major is not None and java_major >= 25: - os.environ["JDK_JAVA_OPTIONS"] = " ".join(jdk25_javac_options()) + os.environ["JDK_JAVA_OPTIONS"] = " ".join( + jdk25_runtime_options("ALL-UNNAMED") + jdk25_javac_options() + ) else: os.environ.pop("JDK_JAVA_OPTIONS", None) common.cd_project_subdir("java") common.exec_cmd( - "mvn -T10 -B --no-transfer-progress clean install -DskipTests -pl '!:fory-testsuite'" + "mvn -T10 -B --no-transfer-progress clean install " + "-pl fory-json -am -DskipTests -Dmaven.javadoc.skip=true" ) - logging.info("Start to build graalvm native image") + logging.info(f"Start to build GraalVM native image for {main_class}") common.cd_project_subdir("integration_tests/graalvm_tests") - common.exec_cmd("mvn -DskipTests=true --no-transfer-progress -Pnative package") + common.exec_cmd( + f"mvn -DmainClass={main_class} -DskipTests=true " + "-Dassembly.skipAssembly=true --no-transfer-progress -Pnative clean package" + ) - logging.info("Built graalvm native image") - logging.info("Start to run graalvm native image") + logging.info("Built GraalVM classpath native image") + logging.info("Start to run GraalVM classpath native image") common.exec_cmd("./target/main") - logging.info("Execute graalvm tests succeed!") + if java_major is not None and java_major >= 25: + os.environ["JDK_JAVA_OPTIONS"] = " ".join(jdk25_javac_options()) + common.exec_cmd( + f"mvn -DmainClass={main_class} -DskipTests=true " + "-Dassembly.skipAssembly=true --no-transfer-progress -Pnative-module package" + ) + + logging.info("Built GraalVM module-path native image") + logging.info("Start to run GraalVM module-path native image") + common.exec_cmd("./target/main-module") + logging.info(f"Execute GraalVM tests for {main_class} succeed!") + + +def run_graalvm_test(): + run_graalvm_tests("org.apache.fory.graalvm.Main") + + +def run_graalvm_json_tests(): + run_graalvm_tests("org.apache.fory.graalvm.ForyJsonExample") def run_release(): @@ -435,3 +459,5 @@ def run(version=None, release=False, install_jdks=False, install_fory=False): run_integration_tests() elif version == "graalvm": run_graalvm_test() + elif version == "graalvm_json_tests": + run_graalvm_json_tests() From f9536adb05899adaa6e28935913c020ab45d1381 Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Wed, 15 Jul 2026 19:33:53 +0800 Subject: [PATCH 4/5] ci: run GraalVM tests on JPMS only --- .github/workflows/ci.yml | 16 +++--------- ci/run_ci.sh | 27 +++++++++----------- ci/tasks/java.py | 33 +++++++++---------------- integration_tests/graalvm_tests/pom.xml | 3 --- 4 files changed, 27 insertions(+), 52 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a4635c8f9..1be7f8676f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -591,13 +591,9 @@ jobs: key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - - name: Set up Python3.8 - uses: actions/setup-python@v5 - with: - python-version: 3.8 - - name: Build native image and run + - name: Build JPMS native image and run shell: bash - run: python ./ci/run_ci.py java --version graalvm + run: ./ci/run_ci.sh graalvm_test graalvm_json: name: GraalVM JSON CI @@ -622,13 +618,9 @@ jobs: key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - - name: Set up Python3.8 - uses: actions/setup-python@v5 - with: - python-version: 3.8 - - name: Build JSON native image and run + - name: Build JSON JPMS native image and run shell: bash - run: python ./ci/run_ci.py java --version graalvm_json_tests + run: ./ci/run_ci.sh graalvm_json_tests kotlin: name: Kotlin CI diff --git a/ci/run_ci.sh b/ci/run_ci.sh index ddd7c0ebf1..52e9449162 100755 --- a/ci/run_ci.sh +++ b/ci/run_ci.sh @@ -90,29 +90,24 @@ run_graalvm_tests() { java_major=$(echo "$java_version" | cut -d. -f1) fi if [[ "$java_major" -ge 25 ]]; then - export JDK_JAVA_OPTIONS="$(jdk25_runtime_options "ALL-UNNAMED") $(jdk25_javac_options)" + export JDK_JAVA_OPTIONS="$(jdk25_javac_options)" else unset JDK_JAVA_OPTIONS fi cd "$ROOT"/java - mvn -T10 -B --no-transfer-progress clean install \ - -pl fory-json -am \ - -DskipTests \ + # GraalVM jobs consume production jars only; Java CI owns test/source jar verification. + # Run the install goal directly after package so verify is not repeated in every native job. + mvn -T10 -B --no-transfer-progress clean package install:install \ + -pl .,fory-core,fory-json \ + -Dmaven.test.skip=true \ + -Dmaven.source.skip=true \ -Dmaven.javadoc.skip=true - echo "Start to build GraalVM native image for $main_class" + echo "Start to build GraalVM JPMS native image for $main_class" cd "$ROOT"/integration_tests/graalvm_tests mvn -DmainClass="$main_class" -DskipTests=true -Dassembly.skipAssembly=true \ - --no-transfer-progress -Pnative clean package - echo "Built GraalVM classpath native image" - echo "Start to run GraalVM classpath native image" - ./target/main - if [[ "$java_major" -ge 25 ]]; then - export JDK_JAVA_OPTIONS="$(jdk25_javac_options)" - fi - mvn -DmainClass="$main_class" -DskipTests=true -Dassembly.skipAssembly=true \ - --no-transfer-progress -Pnative-module package - echo "Built GraalVM module-path native image" - echo "Start to run GraalVM module-path native image" + --no-transfer-progress -Pnative-module clean package + echo "Built GraalVM JPMS native image" + echo "Start to run GraalVM JPMS native image" ./target/main-module echo "Execute GraalVM tests for $main_class succeed!" } diff --git a/ci/tasks/java.py b/ci/tasks/java.py index d7d95a3cc9..23283eaff0 100644 --- a/ci/tasks/java.py +++ b/ci/tasks/java.py @@ -353,42 +353,33 @@ def run_integration_tests(): def run_graalvm_tests(main_class): - """Build and run classpath and module-path GraalVM tests.""" + """Build and run JPMS GraalVM tests.""" logging.info(f"Start GraalVM tests for {main_class}") java_major = get_jdk_major_version() if java_major is not None and java_major >= 25: - os.environ["JDK_JAVA_OPTIONS"] = " ".join( - jdk25_runtime_options("ALL-UNNAMED") + jdk25_javac_options() - ) + os.environ["JDK_JAVA_OPTIONS"] = " ".join(jdk25_javac_options()) else: os.environ.pop("JDK_JAVA_OPTIONS", None) common.cd_project_subdir("java") + # Java CI owns test/source jar verification. GraalVM jobs install only the + # production multi-release jars consumed by native-image. common.exec_cmd( - "mvn -T10 -B --no-transfer-progress clean install " - "-pl fory-json -am -DskipTests -Dmaven.javadoc.skip=true" + "mvn -T10 -B --no-transfer-progress clean package install:install " + "-pl .,fory-core,fory-json -Dmaven.test.skip=true " + "-Dmaven.source.skip=true -Dmaven.javadoc.skip=true" ) - logging.info(f"Start to build GraalVM native image for {main_class}") + logging.info(f"Start to build GraalVM JPMS native image for {main_class}") common.cd_project_subdir("integration_tests/graalvm_tests") common.exec_cmd( f"mvn -DmainClass={main_class} -DskipTests=true " - "-Dassembly.skipAssembly=true --no-transfer-progress -Pnative clean package" - ) - - logging.info("Built GraalVM classpath native image") - logging.info("Start to run GraalVM classpath native image") - common.exec_cmd("./target/main") - - if java_major is not None and java_major >= 25: - os.environ["JDK_JAVA_OPTIONS"] = " ".join(jdk25_javac_options()) - common.exec_cmd( - f"mvn -DmainClass={main_class} -DskipTests=true " - "-Dassembly.skipAssembly=true --no-transfer-progress -Pnative-module package" + "-Dassembly.skipAssembly=true --no-transfer-progress " + "-Pnative-module clean package" ) - logging.info("Built GraalVM module-path native image") - logging.info("Start to run GraalVM module-path native image") + logging.info("Built GraalVM JPMS native image") + logging.info("Start to run GraalVM JPMS native image") common.exec_cmd("./target/main-module") logging.info(f"Execute GraalVM tests for {main_class} succeed!") diff --git a/integration_tests/graalvm_tests/pom.xml b/integration_tests/graalvm_tests/pom.xml index be4ba87652..13acb89b9d 100644 --- a/integration_tests/graalvm_tests/pom.xml +++ b/integration_tests/graalvm_tests/pom.xml @@ -235,9 +235,6 @@ ${mainClass} true false - - false From bad6ed4ef03d00dad52e954f526b664a9f352d3d Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Wed, 15 Jul 2026 19:40:41 +0800 Subject: [PATCH 5/5] ci: speed up JPMS native image compilation --- ci/run_ci.sh | 2 +- ci/tasks/java.py | 2 +- integration_tests/graalvm_tests/pom.xml | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ci/run_ci.sh b/ci/run_ci.sh index 52e9449162..0196ce043f 100755 --- a/ci/run_ci.sh +++ b/ci/run_ci.sh @@ -98,7 +98,7 @@ run_graalvm_tests() { # GraalVM jobs consume production jars only; Java CI owns test/source jar verification. # Run the install goal directly after package so verify is not repeated in every native job. mvn -T10 -B --no-transfer-progress clean package install:install \ - -pl .,fory-core,fory-json \ + -pl .,fory-test-core,fory-core,fory-json \ -Dmaven.test.skip=true \ -Dmaven.source.skip=true \ -Dmaven.javadoc.skip=true diff --git a/ci/tasks/java.py b/ci/tasks/java.py index 23283eaff0..7af581443f 100644 --- a/ci/tasks/java.py +++ b/ci/tasks/java.py @@ -366,7 +366,7 @@ def run_graalvm_tests(main_class): # production multi-release jars consumed by native-image. common.exec_cmd( "mvn -T10 -B --no-transfer-progress clean package install:install " - "-pl .,fory-core,fory-json -Dmaven.test.skip=true " + "-pl .,fory-test-core,fory-core,fory-json -Dmaven.test.skip=true " "-Dmaven.source.skip=true -Dmaven.javadoc.skip=true" ) diff --git a/integration_tests/graalvm_tests/pom.xml b/integration_tests/graalvm_tests/pom.xml index 13acb89b9d..f2b872df06 100644 --- a/integration_tests/graalvm_tests/pom.xml +++ b/integration_tests/graalvm_tests/pom.xml @@ -281,6 +281,8 @@ native-image --no-fallback + + -O1 -H:+UnlockExperimentalVMOptions -o ${project.build.directory}/${moduleImageName}