This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new ae8c36c03 refactor(ci): Migrate CI runner to Python scripts (#2406)
ae8c36c03 is described below

commit ae8c36c030e63419bfdc88aa09a37dd8ce1521ec
Author: Emre Şafak <[email protected]>
AuthorDate: Wed Aug 6 02:45:12 2025 -0400

    refactor(ci): Migrate CI runner to Python scripts (#2406)
    
    At the suggestion of @LiangliangSui and @PragmaTwice, we propose a
    staged migration from shell scripts to Python per language. Please
    review the PR carefully; it was computer assisted.
    
    ## What does this PR do?
    
    * Refactor CI runner from shell scripts to a Python-based system.
    * Introduces `ci/run_ci.py` as the main entry point for CI tasks.
    * Organizes CI logic into modular Python files within `ci/tasks/`.
    * Replaces shell script calls with Python function calls for each
    language.
    * Updates GitHub Actions workflows (`.github/workflows/ci.yml`) to use
    the new Python runner.
    * Enhances argument parsing for better control over CI tasks (e.g.,
    specifying Java versions).
    * Adds support for various Java versions, including specific scenarios
    like Windows Java 21 and GraalVM.
    * Includes new CI tasks for Go and code formatting.
    * Consolidates and cleans up existing CI logic from shell scripts.
    * Introduces environment variables (e.g., `USE_PYTHON_CPP`) to allow
    gradual migration and fallback to shell scripts.
    
    ## Related issues
    
    Completes #1299
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
    
    N/A
    
    ---------
    
    Co-authored-by: Emre Şafak <[email protected]>
---
 .github/workflows/ci.yml          |  61 +++---
 ci/run_ci.py                      | 390 ++++++++++++++++++++++----------------
 ci/run_ci.sh                      |  15 ++
 ci/tasks/__init__.py              |  18 ++
 ci/tasks/common.py                | 179 +++++++++++++++++
 ci/tasks/cpp.py                   |  44 +++++
 ci/tasks/format.py                |  34 ++++
 ci/tasks/go.py                    |  27 +++
 ci/tasks/java.py                  | 201 ++++++++++++++++++++
 ci/tasks/javascript.py            |  29 +++
 ci/tasks/kotlin.py                |  32 ++++
 ci/tasks/python.py                |  63 ++++++
 ci/tasks/rust.py                  |  47 +++++
 javascript/packages/fory/index.ts |   2 +
 javascript/test/hps.test.ts       |   2 +-
 15 files changed, 952 insertions(+), 192 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 27c61bc60..37fb3c73c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -59,13 +59,11 @@ jobs:
         with:
           python-version: 3.8
       - name: Install bazel
-        run: ./ci/run_ci.sh install_bazel
-      - name: Install python
-        run: ./ci/run_ci.sh install_python
-      - name: Install pyfory
-        run: ./ci/run_ci.sh install_pyfory
+        run: python ./ci/run_ci.py cpp --install-deps-only
+      - name: Install python dependencies
+        run: pip install pyarrow==15.0.0 Cython wheel pytest setuptools -U
       - name: Run CI with Maven
-        run: ./ci/run_ci.sh java${{ matrix.java-version }}
+        run: python ./ci/run_ci.py java --version ${{ matrix.java-version }}
       - name: Upload Test Report
         uses: actions/upload-artifact@v4
         if: ${{ !cancelled() }}
@@ -94,13 +92,11 @@ jobs:
         with:
           python-version: 3.8
       - name: Install bazel
-        run: ./ci/run_ci.sh install_bazel
-      - name: Install python
-        run: ./ci/run_ci.sh install_python
-      - name: Install pyfory
-        run: ./ci/run_ci.sh install_pyfory
+        run: python ./ci/run_ci.py cpp --install-deps-only
+      - name: Install python dependencies
+        run: pip install pyarrow==15.0.0 Cython wheel pytest setuptools -U
       - name: Run CI with Maven
-        run: ./ci/run_ci.sh java${{ matrix.java-version }}
+        run: python ./ci/run_ci.py java --version ${{ matrix.java-version }}
 
   java21_windows:
     name: Windows Java 21 CI
@@ -117,9 +113,13 @@ jobs:
         with:
           java-version: ${{ matrix.java-version }}
           distribution: "temurin"
+      - name: Set up Python 3.8
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.8
       - name: Run CI with Maven
         shell: bash
-        run: ./ci/run_ci.sh windows_java21
+        run: python ./ci/run_ci.py java --version windows_java21
 
   graalvm:
     name: GraalVM CI
@@ -141,7 +141,7 @@ jobs:
           python-version: 3.8
       - name: Build native image and run
         shell: bash
-        run: ./ci/run_ci.sh graalvm_test
+        run: python ./ci/run_ci.py java --version graalvm
 
   kotlin:
     name: Kotlin CI
@@ -158,10 +158,14 @@ jobs:
         with:
           java-version: ${{ matrix.java-version }}
           distribution: "temurin"
+      - name: Set up Python 3.8
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.8
       - name: Install fory java
         run: cd java && mvn -T10 --no-transfer-progress clean install 
-DskipTests && cd -
       - name: Run Kotlin CI
-        run: ./ci/run_ci.sh kotlin
+        run: python ./ci/run_ci.py kotlin
 
   scala:
     name: Scala CI
@@ -191,8 +195,12 @@ jobs:
         with:
           java-version: 8
           distribution: "temurin"
+      - name: Set up Python 3.8
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.8
       - name: Run CI
-        run: ./ci/run_ci.sh integration_tests
+        run: python ./ci/run_ci.py java --version integration_tests
 
   javascript:
     name: JavaScript CI
@@ -266,15 +274,10 @@ jobs:
           python-version: ${{ matrix.python-version }}
       - name: Install bazel
         shell: bash
-        run: |
-             if [ "$RUNNER_OS" == "Windows" ]; then
-                 ./ci/run_ci.sh install_bazel_windows
-             else
-                 ./ci/run_ci.sh install_bazel
-             fi
+        run: python ./ci/run_ci.py cpp --install-deps-only
       - name: Run Python CI
         shell: bash
-        run: ./ci/run_ci.sh python
+        run: python ./ci/run_ci.py python
 
   go:
     name: Golang CI
@@ -295,13 +298,11 @@ jobs:
         with:
           python-version: 3.8
       - name: Install bazel
-        run: ./ci/run_ci.sh install_bazel
-      - name: Install python
-        run: ./ci/run_ci.sh install_python
-      - name: Install pyfory
-        run: ./ci/run_ci.sh install_pyfory
+        run: python ./ci/run_ci.py cpp --install-deps-only
+      - name: Install python dependencies
+        run: pip install pyarrow==15.0.0 Cython wheel pytest setuptools -U
       - name: Run Golang CI
-        run: ./ci/run_ci.sh go
+        run: python ./ci/run_ci.py go
 
   lint:
     name: Code Style Check
@@ -324,4 +325,4 @@ jobs:
         with:
           node-version: 20.x
       - name: Check code style
-        run: ./ci/run_ci.sh format
+        run: python ./ci/run_ci.py format
diff --git a/ci/run_ci.py b/ci/run_ci.py
index 846fc639a..cda65daf1 100644
--- a/ci/run_ci.py
+++ b/ci/run_ci.py
@@ -15,202 +15,270 @@
 # specific language governing permissions and limitations
 # under the License.
 
-
 import argparse
+import logging
+import os
 import shutil
 import subprocess
-import platform
-import urllib.request as ulib
-import os
-import logging
-import importlib
-
-
-def _get_bazel_version():
-    with open(os.path.join(PROJECT_ROOT_DIR, ".bazelversion")) as f:
-        return f.read().strip()
-
+import sys
 
-PYARROW_VERSION = "15.0.0"
-
-PROJECT_ROOT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 
"../")
+from tasks import cpp, java, javascript, kotlin, rust, python, go, format
+from tasks.common import is_windows
 
+# Configure logging
 logging.basicConfig(
     level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
 )
 
-
-def _bazel(cmd: str):
-    bazel_cmd = "bazel" if _is_windows() else "~/bin/bazel"
-    return _exec_cmd(f"{bazel_cmd} {cmd}")
-
-
-def _exec_cmd(cmd: str):
-    logging.info(f"running command: {cmd}")
-    try:
-        result = subprocess.check_output(cmd, shell=True, 
universal_newlines=True)
-    except subprocess.CalledProcessError as error:
-        logging.error(error.stdout)
-        raise
-
-    logging.info(f"command result: {result}")
-    return result
-
-
-def _get_os_name_lower():
-    return platform.system().lower()
-
-
-def _is_windows():
-    return _get_os_name_lower() == "windows"
-
-
-def _get_os_machine():
-    machine = platform.machine().lower()
-    # Unified to x86_64(Windows return AMD64, others return x86_64).
-    return machine.replace("amd64", "x86_64")
-
-
-def _get_bazel_download_url():
-    bazel_version = _get_bazel_version()
-    download_url_base = (
-        
f"https://github.com/bazelbuild/bazel/releases/download/{bazel_version}";
-    )
-    suffix = "exe" if _is_windows() else "sh"
-    return (
-        f"{download_url_base}/bazel-{bazel_version}{'' if _is_windows() else 
'-installer'}-"
-        f"{_get_os_name_lower()}-{_get_os_machine()}.{suffix}"
-    )
-
-
-def _cd_project_subdir(subdir):
-    os.chdir(os.path.join(PROJECT_ROOT_DIR, subdir))
-
-
-def _run_cpp():
-    _install_cpp_deps()
-    # collect all C++ targets
-    query_result = _bazel("query //...")
-    targets = query_result.replace("\n", " ").replace("\r", " ")
-    test_command = "test"
-    if _get_os_machine() == "x86_64":
-        test_command += " --config=x86_64"
-    _bazel(f"{test_command} {targets}")
-
-
-def _run_rust():
-    logging.info("Executing fory rust tests")
-    _cd_project_subdir("rust")
-
-    cmds = (
-        "cargo doc --no-deps --document-private-items --all-features --open",
-        "cargo fmt --all -- --check",
-        "cargo fmt --all",
-        "cargo clippy --workspace --all-features --all-targets -- -D warnings",
-        "cargo doc",
-        "cargo build --all-features --all-targets",
-        "cargo test",
-        "cargo clean",
-    )
-    for cmd in cmds:
-        _exec_cmd(cmd)
-    logging.info("Executing fory rust tests succeeds")
-
-
-def _run_js():
-    logging.info("Executing fory javascript tests.")
-    _cd_project_subdir("javascript")
-    _exec_cmd("npm install")
-    _exec_cmd("npm run test")
-    logging.info("Executing fory javascript tests succeeds.")
-
-
-def _install_cpp_deps():
-    _exec_cmd(f"pip install pyarrow=={PYARROW_VERSION}")
-    # Automatically install numpy
-    _exec_cmd("pip install psutil")
-    _install_bazel()
-
-
-def _install_bazel():
-    local_name = "bazel.exe" if _is_windows() else "bazel-installer.sh"
-    bazel_download_url = _get_bazel_download_url()
-    logging.info(bazel_download_url)
-    ulib.urlretrieve(bazel_download_url, local_name)
-    os.chmod(local_name, 0o777)
-
-    if _is_windows():
-        bazel_path = os.path.join(os.getcwd(), local_name)
-        _exec_cmd(f'setx path "%PATH%;{bazel_path}"')
-    else:
-        if shutil.which("bazel"):
-            os.remove(shutil.which("bazel"))
-        _exec_cmd(f"./{local_name} --user")
-        _update_shell_profile()
-        os.remove(local_name)
-
-    # bazel install status check
-    _bazel("--version")
-
-    # default is byte
-    psutil = importlib.import_module("psutil")
-    total_mem = psutil.virtual_memory().total
-    limit_jobs = int(total_mem / 1024 / 1024 / 1024 / 3)
-    with open(".bazelrc", "a") as file:
-        file.write(f"\nbuild --jobs={limit_jobs}")
-
-
-def _update_shell_profile():
-    home = os.path.expanduser("~")
-    profiles = [".bashrc", ".bash_profile", ".zshrc"]
-    path_export = 'export PATH="$PATH:$HOME/bin" # Add Bazel to PATH\n'
-    for profile in profiles:
-        profile_path = os.path.join(home, profile)
-        if os.path.exists(profile_path):
-            with open(profile_path, "a") as f:
-                f.write(path_export)
-            logging.info(f"Updated {profile} to include Bazel PATH.")
-            break
+# Migration Strategy:
+# This script supports a gradual migration from the shell script (run_ci.sh) 
to Python.
+# You can control which languages use the Python implementation by setting 
environment variables.
+#
+# To use the shell script for a specific language, set the corresponding 
environment variable to "0".
+# To use the Python implementation, set it to "1" or leave it unset (default 
is Python).
+#
+# Example:
+#   # Use shell script for Java, Python implementation for everything else
+#   export USE_PYTHON_JAVA=0
+#   python run_ci.py java --version 17
+#
+#   # Use shell script for multiple languages
+#   export USE_PYTHON_JAVA=0 USE_PYTHON_CPP=0 USE_PYTHON_RUST=0
+#   python run_ci.py cpp
+#
+# Available environment variables:
+#   USE_PYTHON_CPP        - C++ implementation
+#   USE_PYTHON_RUST       - Rust implementation
+#   USE_PYTHON_JAVASCRIPT - JavaScript implementation
+#   USE_PYTHON_JAVA       - Java implementation
+#   USE_PYTHON_KOTLIN     - Kotlin implementation
+#   USE_PYTHON_PYTHON     - Python implementation
+#   USE_PYTHON_GO         - Go implementation
+#   USE_PYTHON_FORMAT     - Format implementation
+#
+# By default, JavaScript, Rust, and C++ use the Python implementation,
+# while Java, Kotlin, Python, Go, and Format use the shell script 
implementation.
+
+# Environment variables to control which languages use the Python 
implementation
+# Default to the status quo before migration:
+# - JavaScript, Rust, and C++ use Python implementation
+# - Java, Kotlin, Python, Go, and Format use shell script implementation
+USE_PYTHON_CPP = os.environ.get("USE_PYTHON_CPP", "1") == "1"
+USE_PYTHON_RUST = os.environ.get("USE_PYTHON_RUST", "1") == "1"
+USE_PYTHON_JAVASCRIPT = os.environ.get("USE_PYTHON_JAVASCRIPT", "1") == "1"
+USE_PYTHON_JAVA = os.environ.get("USE_PYTHON_JAVA", "0") == "1"
+USE_PYTHON_KOTLIN = os.environ.get("USE_PYTHON_KOTLIN", "0") == "1"
+USE_PYTHON_PYTHON = os.environ.get("USE_PYTHON_PYTHON", "0") == "1"
+USE_PYTHON_GO = os.environ.get("USE_PYTHON_GO", "0") == "1"
+USE_PYTHON_FORMAT = os.environ.get("USE_PYTHON_FORMAT", "0") == "1"
+
+
+def run_shell_script(command, *args):
+    """Run the shell script with the given command and arguments."""
+    script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 
"run_ci.sh")
+
+    if is_windows():
+        # On Windows, try to use bash if available
+        bash_path = shutil.which("bash")
+        if bash_path:
+            cmd = [bash_path, script_path, command]
+            cmd.extend(args)
+            logging.info(f"Falling back to shell script with bash: {' 
'.join(cmd)}")
+            return subprocess.call(cmd)
+        else:
+            logging.error(
+                "Bash is not available on this Windows system. Cannot run 
shell script."
+            )
+            logging.error(
+                "Please install Git Bash, WSL, or Cygwin to run shell scripts 
on Windows."
+            )
+            logging.error(
+                "Alternatively, set USE_PYTHON_JAVA=1 to use the Python 
implementation."
+            )
+            sys.exit(1)
     else:
-        logging.info("No shell profile found. Please add Bazel to PATH 
manually.")
+        # On Unix-like systems, run the script directly
+        cmd = [script_path, command]
+        cmd.extend(args)
+        logging.info(f"Falling back to shell script: {' '.join(cmd)}")
+        return subprocess.call(cmd)
 
 
-def _parse_args():
+def parse_args():
+    """Parse command-line arguments and dispatch to the appropriate task 
module."""
     parser = argparse.ArgumentParser(
-        formatter_class=argparse.ArgumentDefaultsHelpFormatter
+        description="Fory CI Runner",
+        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
     )
-    parser.set_defaults(func=parser.print_help)
-    subparsers = parser.add_subparsers()
+    parser.set_defaults(func=lambda: parser.print_help())
+    subparsers = parser.add_subparsers(dest="command")
 
+    # C++ subparser
     cpp_parser = subparsers.add_parser(
         "cpp",
         description="Run C++ CI",
         help="Run C++ CI",
         formatter_class=argparse.ArgumentDefaultsHelpFormatter,
     )
-    cpp_parser.set_defaults(func=_run_cpp)
+    cpp_parser.add_argument(
+        "--install-deps-only",
+        action="store_true",
+        help="Only install dependencies without running tests",
+    )
+    cpp_parser.set_defaults(func=lambda install_deps_only: 
cpp.run(install_deps_only))
 
+    # Rust subparser
     rust_parser = subparsers.add_parser(
         "rust",
         description="Run Rust CI",
         help="Run Rust CI",
         formatter_class=argparse.ArgumentDefaultsHelpFormatter,
     )
-    rust_parser.set_defaults(func=_run_rust)
+    rust_parser.set_defaults(func=rust.run)
 
+    # JavaScript subparser
     js_parser = subparsers.add_parser(
         "javascript",
-        description="Run Javascript CI",
-        help="Run Javascript CI",
+        description="Run JavaScript CI",
+        help="Run JavaScript CI",
+        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+    )
+    js_parser.set_defaults(func=javascript.run)
+
+    # Java subparser
+    java_parser = subparsers.add_parser(
+        "java",
+        description="Run Java CI",
+        help="Run Java CI",
+        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+    )
+    java_parser.add_argument(
+        "--version",
+        choices=[
+            "8",
+            "11",
+            "17",
+            "21",
+            "24",
+            "windows_java21",
+            "integration_tests",
+            "graalvm",
+        ],
+        default="17",
+        help="Java version to use for testing",
+    )
+    java_parser.set_defaults(func=lambda version: java.run(version))
+
+    # Kotlin subparser
+    kotlin_parser = subparsers.add_parser(
+        "kotlin",
+        description="Run Kotlin CI",
+        help="Run Kotlin CI",
+        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+    )
+    kotlin_parser.set_defaults(func=kotlin.run)
+
+    # Python subparser
+    python_parser = subparsers.add_parser(
+        "python",
+        description="Run Python CI",
+        help="Run Python CI",
         formatter_class=argparse.ArgumentDefaultsHelpFormatter,
     )
-    js_parser.set_defaults(func=_run_js)
+    python_parser.set_defaults(func=python.run)
+
+    # Go subparser
+    go_parser = subparsers.add_parser(
+        "go",
+        description="Run Go CI",
+        help="Run Go CI",
+        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+    )
+    go_parser.set_defaults(func=go.run)
+
+    # Format subparser
+    format_parser = subparsers.add_parser(
+        "format",
+        description="Run format checks",
+        help="Run format checks",
+        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+    )
+    format_parser.set_defaults(func=format.run)
 
     args = parser.parse_args()
-    arg_dict = dict(vars(args))
-    del arg_dict["func"]
-    args.func(**arg_dict)
+
+    # If no arguments were provided, print help and exit
+    if len(sys.argv) == 1:
+        parser.print_help()
+        sys.exit(1)
+
+    # Extract arguments to pass to the function
+    arg_dict = vars(args)
+    func = arg_dict.pop("func")
+    command = arg_dict.pop("command", None)
+
+    # Call the appropriate function with the remaining arguments
+    if command == "java":
+        if USE_PYTHON_JAVA:
+            func(arg_dict.get("version"))
+        else:
+            # Map Python version argument to shell script command
+            version = arg_dict.get("version", "17")
+            # For windows_java21 on Windows, use the Python implementation 
directly
+            if version == "windows_java21" and is_windows():
+                logging.info(
+                    "Using Python implementation for windows_java21 on Windows"
+                )
+                func(version)
+            elif version == "integration_tests":
+                run_shell_script("integration_tests")
+            elif version == "windows_java21":
+                run_shell_script("windows_java21")
+            elif version == "graalvm":
+                run_shell_script("graalvm_test")
+            else:
+                run_shell_script(f"java{version}")
+    elif command == "cpp":
+        if USE_PYTHON_CPP:
+            func(arg_dict.get("install_deps_only", False))
+        else:
+            if arg_dict.get("install_deps_only", False):
+                run_shell_script("install_bazel")
+            else:
+                run_shell_script("cpp")
+    elif command == "rust":
+        if USE_PYTHON_RUST:
+            func()
+        else:
+            run_shell_script("rust")
+    elif command == "javascript":
+        if USE_PYTHON_JAVASCRIPT:
+            func()
+        else:
+            run_shell_script("javascript")
+    elif command == "kotlin":
+        if USE_PYTHON_KOTLIN:
+            func()
+        else:
+            run_shell_script("kotlin")
+    elif command == "python":
+        if USE_PYTHON_PYTHON:
+            func()
+        else:
+            run_shell_script("python")
+    elif command == "go":
+        if USE_PYTHON_GO:
+            func()
+        else:
+            run_shell_script("go")
+    elif command == "format":
+        if USE_PYTHON_FORMAT:
+            func()
+        else:
+            run_shell_script("format")
+    else:
+        func()
 
 
 if __name__ == "__main__":
-    _parse_args()
+    parse_args()
diff --git a/ci/run_ci.sh b/ci/run_ci.sh
index bfe9481f9..0c4fb52f6 100755
--- a/ci/run_ci.sh
+++ b/ci/run_ci.sh
@@ -17,6 +17,21 @@
 # specific language governing permissions and limitations
 # under the License.
 
+# NOTE: This script is being gradually migrated to Python (run_ci.py).
+# It can be called directly or from run_ci.py as a fallback.
+# To control which languages use the Python implementation, set environment 
variables:
+#   USE_PYTHON_CPP=0        # Use shell script for C++
+#   USE_PYTHON_RUST=0       # Use shell script for Rust
+#   USE_PYTHON_JAVASCRIPT=0 # Use shell script for JavaScript
+#   USE_PYTHON_JAVA=0       # Use shell script for Java
+#   USE_PYTHON_KOTLIN=0     # Use shell script for Kotlin
+#   USE_PYTHON_PYTHON=0     # Use shell script for Python
+#   USE_PYTHON_GO=0         # Use shell script for Go
+#   USE_PYTHON_FORMAT=0     # Use shell script for Format
+#
+# By default, JavaScript, Rust, and C++ use the Python implementation,
+# while Java, Kotlin, Python, Go, and Format use the shell script 
implementation.
+
 set -e
 set -x
 
diff --git a/ci/tasks/__init__.py b/ci/tasks/__init__.py
new file mode 100644
index 000000000..f44add826
--- /dev/null
+++ b/ci/tasks/__init__.py
@@ -0,0 +1,18 @@
+# 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
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# This file makes the tasks directory a Python package
diff --git a/ci/tasks/common.py b/ci/tasks/common.py
new file mode 100644
index 000000000..76f321735
--- /dev/null
+++ b/ci/tasks/common.py
@@ -0,0 +1,179 @@
+# 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
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import shutil
+import subprocess
+import platform
+import urllib.request as ulib
+import os
+import logging
+import importlib
+
+# Constants
+PYARROW_VERSION = "15.0.0"
+PROJECT_ROOT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 
"../../")
+
+# Configure logging
+logging.basicConfig(
+    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
+)
+
+
+def get_bazel_version():
+    """Get the bazel version from the .bazelversion file."""
+    with open(os.path.join(PROJECT_ROOT_DIR, ".bazelversion")) as f:
+        return f.read().strip()
+
+
+def exec_cmd(cmd: str):
+    """Execute a shell command and return its output."""
+    logging.info(f"running command: {cmd}")
+    try:
+        result = subprocess.check_output(cmd, shell=True, 
universal_newlines=True)
+    except subprocess.CalledProcessError as error:
+        logging.error(error.stdout)
+        raise
+
+    logging.info(f"command result: {result}")
+    return result
+
+
+def get_os_name_lower():
+    """Get the lowercase name of the operating system."""
+    return platform.system().lower()
+
+
+def is_windows():
+    """Check if the operating system is Windows."""
+    return get_os_name_lower() == "windows"
+
+
+def get_os_machine():
+    """Get the normalized machine architecture."""
+    machine = platform.machine().lower()
+    # Normalize architecture names
+    if machine in ["x86_64", "amd64"]:
+        return "x86_64"
+    elif machine in ["aarch64", "arm64"]:
+        return "arm64"
+    return machine
+
+
+def get_bazel_download_url():
+    """Construct the URL to download bazel."""
+    bazel_version = get_bazel_version()
+    download_url_base = (
+        
f"https://github.com/bazelbuild/bazel/releases/download/{bazel_version}";
+    )
+
+    # For Windows, use the .exe installer
+    if is_windows():
+        return f"{download_url_base}/bazel-{bazel_version}-windows-x86_64.exe"
+
+    # For Unix-like systems, use the binary directly (not the installer)
+    return 
f"{download_url_base}/bazel-{bazel_version}-{get_os_name_lower()}-{get_os_machine()}"
+
+
+def cd_project_subdir(subdir):
+    """Change to a subdirectory of the project."""
+    os.chdir(os.path.join(PROJECT_ROOT_DIR, subdir))
+
+
+def bazel(cmd: str):
+    """Execute a bazel command."""
+    bazel_cmd = "bazel" if is_windows() else "~/bin/bazel"
+    return exec_cmd(f"{bazel_cmd} {cmd}")
+
+
+def update_shell_profile():
+    """Update shell profile to include bazel in PATH."""
+    home = os.path.expanduser("~")
+    profiles = [".bashrc", ".bash_profile", ".zshrc"]
+    path_export = 'export PATH="$PATH:$HOME/bin" # Add Bazel to PATH\n'
+    for profile in profiles:
+        profile_path = os.path.join(home, profile)
+        if os.path.exists(profile_path):
+            with open(profile_path, "a") as f:
+                f.write(path_export)
+            logging.info(f"Updated {profile} to include Bazel PATH.")
+            break
+    else:
+        logging.info("No shell profile found. Please add Bazel to PATH 
manually.")
+
+
+def install_bazel():
+    """Download and install bazel."""
+    bazel_download_url = get_bazel_download_url()
+    logging.info(f"Downloading bazel from: {bazel_download_url}")
+
+    if is_windows():
+        # For Windows, download the installer and add it to PATH
+        local_name = "bazel.exe"
+        try:
+            ulib.urlretrieve(bazel_download_url, local_name)
+        except Exception as e:
+            logging.error(f"Failed to download bazel: {e}")
+            logging.error(f"URL: {bazel_download_url}")
+            logging.error(
+                f"OS: {get_os_name_lower()}, Machine: {get_os_machine()}, 
Original Machine: {platform.machine().lower()}"
+            )
+            raise
+        os.chmod(local_name, 0o777)
+        bazel_path = os.path.join(os.getcwd(), local_name)
+        exec_cmd(f'setx path "%PATH%;{bazel_path}"')
+    else:
+        # For Unix-like systems, download the binary directly to ~/bin/bazel
+        home_bin = os.path.expanduser("~/bin")
+        os.makedirs(home_bin, exist_ok=True)
+        bazel_path = os.path.join(home_bin, "bazel")
+
+        try:
+            ulib.urlretrieve(bazel_download_url, bazel_path)
+        except Exception as e:
+            logging.error(f"Failed to download bazel: {e}")
+            logging.error(f"URL: {bazel_download_url}")
+            logging.error(
+                f"OS: {get_os_name_lower()}, Machine: {get_os_machine()}, 
Original Machine: {platform.machine().lower()}"
+            )
+            raise
+
+        os.chmod(bazel_path, 0o755)
+        update_shell_profile()
+
+    # bazel install status check
+    bazel("--version")
+
+    # default is byte
+    psutil = importlib.import_module("psutil")
+    total_mem = psutil.virtual_memory().total
+    limit_jobs = int(total_mem / 1024 / 1024 / 1024 / 3)
+    with open(".bazelrc", "a") as file:
+        file.write(f"\nbuild --jobs={limit_jobs}")
+
+
+def install_cpp_deps():
+    """Install dependencies for C++ development."""
+    # Check the Python version and install the appropriate pyarrow version
+    python_version = platform.python_version()
+    if python_version.startswith("3.13"):
+        exec_cmd("pip install pyarrow==18.0.0")
+        exec_cmd("pip install numpy")
+    else:
+        exec_cmd(f"pip install pyarrow=={PYARROW_VERSION}")
+        # Automatically install numpy
+    exec_cmd("pip install psutil")
+    install_bazel()
diff --git a/ci/tasks/cpp.py b/ci/tasks/cpp.py
new file mode 100644
index 000000000..e24541c30
--- /dev/null
+++ b/ci/tasks/cpp.py
@@ -0,0 +1,44 @@
+# 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
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import logging
+from . import common
+
+
+def run(install_deps_only=False):
+    """Run C++ CI tasks.
+
+    Args:
+        install_deps_only: If True, only install dependencies without running 
tests.
+    """
+    logging.info("Running C++ CI tasks")
+    common.install_cpp_deps()
+
+    if install_deps_only:
+        logging.info("Skipping tests as --install-deps-only was specified")
+        return
+
+    # collect all C++ targets
+    query_result = common.bazel("query //...")
+    targets = query_result.replace("\n", " ").replace("\r", " ")
+
+    test_command = "test"
+    if common.get_os_machine() == "x86_64":
+        test_command += " --config=x86_64"
+
+    common.bazel(f"{test_command} {targets}")
+    logging.info("C++ CI tasks completed successfully")
diff --git a/ci/tasks/format.py b/ci/tasks/format.py
new file mode 100644
index 000000000..d1495f487
--- /dev/null
+++ b/ci/tasks/format.py
@@ -0,0 +1,34 @@
+# 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
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import logging
+from . import common
+
+
+def run():
+    """Run format checking tasks."""
+    logging.info("Install format tools")
+    common.exec_cmd("pip install ruff")
+
+    logging.info("Executing format check")
+    common.exec_cmd("bash ci/format.sh")
+
+    common.cd_project_subdir("java")
+    common.exec_cmd("mvn -T10 -B --no-transfer-progress spotless:check")
+    common.exec_cmd("mvn -T10 -B --no-transfer-progress checkstyle:check")
+
+    logging.info("Executing format check succeeds")
diff --git a/ci/tasks/go.py b/ci/tasks/go.py
new file mode 100644
index 000000000..3df17f81f
--- /dev/null
+++ b/ci/tasks/go.py
@@ -0,0 +1,27 @@
+# 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
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import logging
+from . import common
+
+
+def run():
+    """Run Go CI tasks."""
+    logging.info("Executing fory go tests")
+    common.cd_project_subdir("go/fory")
+    common.exec_cmd("go test -v")
+    logging.info("Executing fory go tests succeeds")
diff --git a/ci/tasks/java.py b/ci/tasks/java.py
new file mode 100644
index 000000000..275d59fff
--- /dev/null
+++ b/ci/tasks/java.py
@@ -0,0 +1,201 @@
+# 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
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import logging
+import os
+from . import common
+
+# JDK versions
+JDKS = [
+    "zulu21.28.85-ca-jdk21.0.0-linux_x64",
+    "zulu17.44.17-ca-crac-jdk17.0.8-linux_x64",
+    "zulu15.46.17-ca-jdk15.0.10-linux_x64",
+    "zulu13.54.17-ca-jdk13.0.14-linux_x64",
+    "zulu11.66.15-ca-jdk11.0.20-linux_x64",
+    "zulu8.72.0.17-ca-jdk8.0.382-linux_x64",
+]
+
+
+def install_jdks():
+    """Download and install JDKs."""
+    common.cd_project_subdir("")  # Go to the project root
+    for jdk in JDKS:
+        common.exec_cmd(
+            f"wget -q https://cdn.azul.com/zulu/bin/{jdk}.tar.gz -O 
{jdk}.tar.gz"
+        )
+        common.exec_cmd(f"tar zxf {jdk}.tar.gz")
+
+
+def run_java8():
+    """Run Java 8 tests."""
+    logging.info("Executing fory java tests with Java 8")
+    common.cd_project_subdir("java")
+    common.exec_cmd("mvn -T16 --batch-mode --no-transfer-progress test")
+    logging.info("Executing fory java tests succeeds")
+
+
+def run_java11():
+    """Run Java 11 tests."""
+    logging.info("Executing fory java tests with Java 11")
+    common.cd_project_subdir("java")
+    common.exec_cmd("mvn -T16 --batch-mode --no-transfer-progress test")
+    logging.info("Executing fory java tests succeeds")
+
+
+def run_jdk17_plus(java_version="17"):
+    """Run Java 17+ tests."""
+    logging.info(f"Executing fory java tests with Java {java_version}")
+    common.exec_cmd("java -version")
+    os.environ["JDK_JAVA_OPTIONS"] = (
+        
"--add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED"
+    )
+
+    common.cd_project_subdir("java")
+    common.exec_cmd("mvn -T10 --batch-mode --no-transfer-progress install")
+
+    logging.info("Executing fory java tests succeeds")
+    logging.info("Executing latest_jdk_tests")
+
+    common.cd_project_subdir("integration_tests/latest_jdk_tests")
+    common.exec_cmd("mvn -T10 -B --no-transfer-progress clean test")
+
+    logging.info("Executing latest_jdk_tests succeeds")
+
+
+def run_windows_java21():
+    """Run Java 21 tests on Windows."""
+    logging.info("Executing fory java tests on Windows with Java 21")
+    common.exec_cmd("java -version")
+
+    common.cd_project_subdir("java")
+    # Use double quotes for Windows compatibility
+    if common.is_windows():
+        common.exec_cmd(
+            'mvn -T10 --batch-mode --no-transfer-progress test 
-Dtest=!org.apache.fory.CrossLanguageTest install -pl 
"!fory-format,!fory-testsuite"'
+        )
+    else:
+        common.exec_cmd(
+            "mvn -T10 --batch-mode --no-transfer-progress test 
-Dtest=!org.apache.fory.CrossLanguageTest install -pl 
'!fory-format,!fory-testsuite'"
+        )
+
+    logging.info("Executing fory java tests succeeds")
+
+
+def run_integration_tests():
+    """Run Java integration tests."""
+    logging.info("Install JDKs")
+    install_jdks()
+
+    logging.info("Executing fory integration tests")
+
+    common.cd_project_subdir("java")
+    common.exec_cmd("mvn -T10 -B --no-transfer-progress clean install 
-DskipTests")
+
+    logging.info("benchmark tests")
+    common.cd_project_subdir("java/benchmark")
+    common.exec_cmd("mvn -T10 -B --no-transfer-progress clean test install 
-Pjmh")
+
+    logging.info("Start latest jdk tests")
+    common.cd_project_subdir("integration_tests/latest_jdk_tests")
+    logging.info("latest_jdk_tests: JDK 21")
+
+    # Set Java 21 as the current JDK
+    java_home = os.path.join(common.PROJECT_ROOT_DIR, JDKS[0])
+    os.environ["JAVA_HOME"] = java_home
+    os.environ["PATH"] = f"{java_home}/bin:{os.environ.get('PATH', '')}"
+
+    common.exec_cmd("mvn -T10 -B --no-transfer-progress clean test")
+
+    logging.info("Start JPMS tests")
+    common.cd_project_subdir("integration_tests/jpms_tests")
+    common.exec_cmd("mvn -T10 -B --no-transfer-progress clean compile")
+
+    logging.info("Start jdk compatibility tests")
+    common.cd_project_subdir("integration_tests/jdk_compatibility_tests")
+    common.exec_cmd("mvn -T10 -B --no-transfer-progress clean test")
+
+    # Run tests with different JDK versions
+    # This is a two-phase process:
+    # 1. First round: Generate serialized data files for each JDK version
+    # 2. Second round: Test if these files can be deserialized correctly by 
each JDK version
+
+    # First round: Generate serialized data files
+    logging.info("First round: Generate serialized data files for each JDK 
version")
+    for jdk in JDKS:
+        java_home = os.path.join(common.PROJECT_ROOT_DIR, jdk)
+        os.environ["JAVA_HOME"] = java_home
+        os.environ["PATH"] = f"{java_home}/bin:{os.environ.get('PATH', '')}"
+
+        logging.info(f"Generating data with JDK: {jdk}")
+        common.exec_cmd(
+            "mvn -T10 --no-transfer-progress clean test 
-Dtest=org.apache.fory.integration_tests.JDKCompatibilityTest"
+        )
+
+    # Second round: Test cross-JDK compatibility
+    logging.info("Second round: Test cross-JDK compatibility")
+    for jdk in JDKS:
+        java_home = os.path.join(common.PROJECT_ROOT_DIR, jdk)
+        os.environ["JAVA_HOME"] = java_home
+        os.environ["PATH"] = f"{java_home}/bin:{os.environ.get('PATH', '')}"
+
+        logging.info(f"Testing compatibility with JDK: {jdk}")
+        common.exec_cmd(
+            "mvn -T10 --no-transfer-progress clean test 
-Dtest=org.apache.fory.integration_tests.JDKCompatibilityTest"
+        )
+
+    logging.info("Executing fory integration tests succeeds")
+
+
+def run_graalvm_test():
+    """Run GraalVM tests."""
+    logging.info("Start GraalVM tests")
+
+    common.cd_project_subdir("java")
+    common.exec_cmd("mvn -T10 -B --no-transfer-progress clean install 
-DskipTests")
+
+    logging.info("Start to build graalvm native image")
+    common.cd_project_subdir("integration_tests/graalvm_tests")
+    common.exec_cmd("mvn -DskipTests=true --no-transfer-progress -Pnative 
package")
+
+    logging.info("Built graalvm native image")
+    logging.info("Start to run graalvm native image")
+    common.exec_cmd("./target/main")
+
+    logging.info("Execute graalvm tests succeed!")
+
+
+def run(java_version=None):
+    """Run Java CI tasks based on the specified Java version."""
+    if java_version == "8":
+        run_java8()
+    elif java_version == "11":
+        run_java11()
+    elif java_version == "17":
+        run_jdk17_plus("17")
+    elif java_version == "21":
+        run_jdk17_plus("21")
+    elif java_version == "24":
+        run_jdk17_plus("24")
+    elif java_version == "windows_java21":
+        run_windows_java21()
+    elif java_version == "integration_tests":
+        run_integration_tests()
+    elif java_version == "graalvm":
+        run_graalvm_test()
+    else:
+        # Default to Java 17 if no version specified
+        run_jdk17_plus("17")
diff --git a/ci/tasks/javascript.py b/ci/tasks/javascript.py
new file mode 100644
index 000000000..5b4031988
--- /dev/null
+++ b/ci/tasks/javascript.py
@@ -0,0 +1,29 @@
+# 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
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import logging
+from . import common
+
+
+def run():
+    """Run JavaScript CI tasks."""
+    logging.info("Executing fory javascript tests.")
+    common.cd_project_subdir("javascript")
+    common.exec_cmd("npm install")
+    common.exec_cmd("npm run test")
+
+    logging.info("Executing fory javascript tests succeeds.")
diff --git a/ci/tasks/kotlin.py b/ci/tasks/kotlin.py
new file mode 100644
index 000000000..cf1197a19
--- /dev/null
+++ b/ci/tasks/kotlin.py
@@ -0,0 +1,32 @@
+# 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
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import logging
+from . import common
+
+
+def run():
+    """Run Kotlin CI tasks."""
+    logging.info("Executing fory kotlin tests")
+    common.cd_project_subdir("kotlin")
+
+    # Using the same command as in run_ci.sh
+    common.exec_cmd(
+        "mvn -T16 --batch-mode --no-transfer-progress test 
-DfailIfNoTests=false"
+    )
+
+    logging.info("Executing fory kotlin tests succeeds")
diff --git a/ci/tasks/python.py b/ci/tasks/python.py
new file mode 100644
index 000000000..252d238d5
--- /dev/null
+++ b/ci/tasks/python.py
@@ -0,0 +1,63 @@
+# 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
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import logging
+import os
+from . import common
+
+
+def install_pyfory():
+    """Install pyfory package."""
+    logging.info("Installing pyfory package")
+    python_version = common.exec_cmd("python -V")
+    python_path = common.exec_cmd("which python")
+    logging.info(f"Python version {python_version}, path {python_path}")
+
+    # Install PyArrow
+    common.exec_cmd(f"{common.PROJECT_ROOT_DIR}/ci/deploy.sh install_pyarrow")
+
+    # Install dependencies
+    common.exec_cmd("pip install Cython wheel pytest")
+
+    # Install pyfory
+    common.cd_project_subdir("python")
+    common.exec_cmd("pip list")
+    logging.info("Install pyfory")
+
+    # Fix strange installed deps not found
+    common.exec_cmd("pip install setuptools -U")
+    common.exec_cmd("pip install -v -e .")
+
+
+def run():
+    """Run Python CI tasks."""
+    install_pyfory()
+    common.exec_cmd("pip install pandas")
+
+    common.cd_project_subdir("python")
+    logging.info("Executing fory python tests")
+
+    # Run tests with default settings
+    common.exec_cmd("pytest -v -s --durations=60 pyfory/tests")
+
+    logging.info("Executing fory python tests succeeds")
+
+    # Run tests with ENABLE_FORY_CYTHON_SERIALIZATION=0
+    os.environ["ENABLE_FORY_CYTHON_SERIALIZATION"] = "0"
+    common.exec_cmd("pytest -v -s --durations=60 pyfory/tests")
+
+    logging.info("Executing fory python tests succeeds")
diff --git a/ci/tasks/rust.py b/ci/tasks/rust.py
new file mode 100644
index 000000000..6ef968801
--- /dev/null
+++ b/ci/tasks/rust.py
@@ -0,0 +1,47 @@
+# 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
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import logging
+from . import common
+
+
+def run():
+    """Run Rust CI tasks."""
+    logging.info("Executing fory rust tests")
+    common.cd_project_subdir("rust")
+
+    # From run_ci.sh, we should also add rustup components
+    try:
+        common.exec_cmd("rustup component add clippy-preview")
+        common.exec_cmd("rustup component add rustfmt")
+    except Exception as e:
+        logging.warning(f"Failed to add rustup components: {e}")
+        logging.warning("Continuing with existing components")
+
+    cmds = (
+        "cargo doc --no-deps --document-private-items --all-features --open",
+        "cargo fmt --all -- --check",
+        "cargo fmt --all",
+        "cargo clippy --workspace --all-features --all-targets -- -D warnings",
+        "cargo doc",
+        "cargo build --all-features --all-targets",
+        "cargo test",
+        "cargo clean",
+    )
+    for cmd in cmds:
+        common.exec_cmd(cmd)
+    logging.info("Executing fory rust tests succeeds")
diff --git a/javascript/packages/fory/index.ts 
b/javascript/packages/fory/index.ts
index 59d571cad..3200c27ca 100644
--- a/javascript/packages/fory/index.ts
+++ b/javascript/packages/fory/index.ts
@@ -25,6 +25,7 @@ import {
 } from "./lib/typeInfo";
 import { Serializer, InternalSerializerType, Mode } from "./lib/type";
 import Fory from "./lib/fory";
+import { BinaryReader } from "./lib/reader";
 
 export {
   Serializer,
@@ -34,6 +35,7 @@ export {
   StructTypeInfo,
   Type,
   Mode,
+  BinaryReader,
 };
 
 export default Fory;
diff --git a/javascript/test/hps.test.ts b/javascript/test/hps.test.ts
index 67e935fb9..6296fda89 100644
--- a/javascript/test/hps.test.ts
+++ b/javascript/test/hps.test.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { BinaryReader } from '@foryjs/fory/dist/lib/reader';
+import { BinaryReader } from '@foryjs/fory';
 import hps from '../packages/hps/index';
 import { describe, expect, test } from '@jest/globals';
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to