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]