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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,4 @@ jobs:
uv --version

- name: Run pre-commit on all files
env:
# shellcheck and hadolint findings are fixed in the follow-up PR.
SKIP: shellcheck,hadolint-docker
run: uvx pre-commit run --all-files --show-diff-on-failure
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ repos:
hooks:
- id: no-old-package-name
name: the old package name does not come back
entry: python3 scripts/check_no_embedded_name.py
language: system
entry: python scripts/check_no_embedded_name.py
language: python
pass_filenames: false
always_run: true
4 changes: 4 additions & 0 deletions .shellcheckrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Picked up by the shellcheck pre-commit hook (.pre-commit-config.yaml) and by editors.
# Follow `source` lines; a `# shellcheck source=<file>` directive names the file relative to the script's directory.
external-sources=true
source-path=SCRIPTDIR
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ uv run --no-project --with "$W[test]" python -m pytest -m server

## Lint / format

Pre-commit: black, isort (`--profile black`), shfmt (`-i 4 -ci -sr`), pretty-format-yaml (not on `.github/`), actionlint. CI also runs Bandit, which must be clean:
Pre-commit (CI runs every hook on all files, none is skipped): ruff, black, isort (`--profile black`), prettier, shfmt (`-i 4 -ci -sr`), shellcheck and hadolint (both in Docker), pretty-format-yaml (not on `.github/`), actionlint. CI also runs Bandit, which must be clean:

```bash
uvx bandit==1.9.4 -c pyproject.toml -r src tests --severity-level low --confidence-level low
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ uv run --no-project --python 3.12 python native/wheel/build_wheel.py --lib-dir <

### Pre-commit

This project uses [pre-commit](https://pre-commit.com/): ruff (lint, default rules E4/E7/E9/F at line length 88), black and isort (Python formatting), prettier (Java, XML, Markdown, JSON), shfmt and shellcheck (shell), hadolint (Dockerfile), pretty-format-yaml and the basic hygiene hooks (trailing whitespace, end-of-file, merge-conflict markers, large files, private keys), and actionlint (GitHub Actions). The settings live in `pyproject.toml` (`[tool.ruff]`) and `.prettierrc.json`.
This project uses [pre-commit](https://pre-commit.com/): ruff (lint, default rules E4/E7/E9/F at line length 88), black and isort (Python formatting), prettier (Java, XML, Markdown, JSON), shfmt and shellcheck (shell), hadolint (Dockerfile), pretty-format-yaml and the basic hygiene hooks (trailing whitespace, end-of-file, merge-conflict markers, large files, private keys), and actionlint (GitHub Actions). The settings live in `pyproject.toml` (`[tool.ruff]`), `.prettierrc.json` and `.shellcheckrc`.

Install the hooks once so they run on every commit:

Expand Down
3 changes: 2 additions & 1 deletion native/scripts/build-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ for f in target/reports/used_classes_*.txt reports/used_classes_*.txt; do
done
if [ -z "$UC" ]; then
echo "no used_classes_*.txt analysis report after the build (-H:+PrintAnalysisCallTree); found:" >&2
ls -R target 2> /dev/null | grep -i -E "report|used_" >&2 || true
target_listing=$(ls -R target 2> /dev/null || true)
printf '%s\n' "$target_listing" | grep -i -E "report|used_" >&2 || true
exit 1
fi
cp "$UC" "$OUT/used_classes.txt"
Expand Down
4 changes: 2 additions & 2 deletions native/wheel/build_wheel.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Build the `arcadedb` wheel (the native build) from the shared Python sources and a built native image.

python native/wheel/build_wheel.py --lib-dir native/target/lib --version 26.11.1.dev0 --engine 26.10.2 --out <dir>
python native/wheel/build_wheel.py --lib-dir native/target/lib --version 26.12.1.dev0 --engine 26.11.1 --out <dir>

Steps: copy src/arcadedb to <out>/stage/src/arcadedb (the shared tree is not duplicated in git; only the install hints of the extras are renamed from arcadedb-jvm[...] to arcadedb[...]);
rewrite `import jpype` to the bundled stub; flip the default backend to native; add the library of the build platform (libarcadedb.so on Linux,
Expand Down Expand Up @@ -116,7 +116,7 @@ def main():
ap.add_argument(
"--engine",
required=True,
help="engine release the image was built from, e.g. 26.10.2",
help="engine release the image was built from, e.g. 26.11.1",
)
ap.add_argument("--out", required=True)
ap.add_argument(
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ dependencies = [
# This pyproject is the *package definition* consumed by the wheel build
# (scripts/build.sh). It is not a uv project: tests run against the built wheel,
# with its `test` extra, in a throwaway `uv run --no-project --with` environment
# (see README, Testing). `managed = false` makes uv skip it during project
# (see README, "Build and test from source"). `managed = false` makes uv skip it during project
# discovery, so `uv run` never tries to sync this package.
managed = false

Expand Down
57 changes: 39 additions & 18 deletions scripts/Dockerfile.build
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,14 @@ FROM arcadedata/arcadedb:${ARCADEDB_TAG} AS java-builder
# JDK it runs, so the bundled JRE has the same floor. Both are GPLv2 with the Classpath Exception.
FROM eclipse-temurin:25-jdk AS jre-builder

# bash with pipefail for every RUN of this stage: a failing command in the middle of a pipe stops the build
# (hadolint DL4006). The RUNs below were written for sh, so they avoid bashisms that differ (echo with \n).
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

# Install required tooling (findutils for jar exclusion, binutils for jlink)
# DL3008: the package versions are not pinned on purpose. The base image is a rolling Ubuntu tag, and a pinned
# version disappears from the mirror at the next point release, which would break this build for no reason.
# hadolint ignore=DL3008
RUN apt-get update && apt-get install -y --no-install-recommends findutils binutils \
&& rm -rf /var/lib/apt/lists/*

Expand All @@ -66,7 +73,7 @@ RUN echo "LOCAL_JARS_HASH=${LOCAL_JARS_HASH}"

# Select jar source: local when requested and available; otherwise fall back to upstream image
RUN if [ "$USE_LOCAL_JARS" = "1" ]; then \
if [ -d /build/local-jars ] && [ "$(ls -1 /build/local-jars | wc -l)" -gt 0 ]; then \
if [ -d /build/local-jars ] && [ "$(find /build/local-jars -mindepth 1 -maxdepth 1 -name '[!.]*' | wc -l)" -gt 0 ]; then \
echo "📦 Using local jars from build context" && cp /build/local-jars/* /build/jars/; \
else \
echo "❌ USE_LOCAL_JARS=1 but no jars found in local-jars/lib" && exit 1; \
Expand All @@ -82,7 +89,7 @@ COPY scripts/jre_module_exclusions.txt /build/jre_module_exclusions.txt

# Remove excluded JARs based on jar_exclusions.txt
RUN echo "🗑️ Removing excluded JARs..." && \
echo "📋 JAR count before exclusion: $(ls -1 /build/jars/*.jar | wc -l)" && \
echo "📋 JAR count before exclusion: $(find /build/jars -maxdepth 1 -name '*.jar' | wc -l)" && \
while IFS= read -r pattern; do \
# Skip empty lines and comments
if [ -n "$pattern" ] && [ "${pattern#\#}" = "$pattern" ]; then \
Expand All @@ -91,15 +98,15 @@ RUN echo "🗑️ Removing excluded JARs..." && \
find /build/jars -name "$pattern" -type f -exec echo " - Removing: {}" \; -delete; \
fi \
done < /build/jar_exclusions.txt && \
echo "📋 JAR count after exclusion: $(ls -1 /build/jars/*.jar | wc -l)"
echo "📋 JAR count after exclusion: $(find /build/jars -maxdepth 1 -name '*.jar' | wc -l)"

# Build the Python-bindings bridge jar (batched row transport; see
# src/java/com/arcadedb/python/RowBatcher.java)
COPY src/java /build/bridge-src
RUN echo "🔨 Building arcadedb-python-bridge.jar..." && \
mkdir -p /build/bridge-classes && \
javac -cp "/build/jars/*" -d /build/bridge-classes \
$(find /build/bridge-src -name "*.java") && \
find /build/bridge-src -name "*.java" -print0 \
| xargs -0 javac -cp "/build/jars/*" -d /build/bridge-classes && \
jar cf /build/jars/arcadedb-python-bridge.jar -C /build/bridge-classes . && \
echo "✅ Bridge jar built: $(ls -la /build/jars/arcadedb-python-bridge.jar)"

Expand All @@ -115,16 +122,17 @@ RUN echo "🔨 Building arcadedb-python-bridge.jar..." && \
RUN echo "🔍 Analyzing JARs with jdeps..." && \
JMODS_DIR="${JAVA_HOME}/jmods" && \
if [ ! -d "$JMODS_DIR" ]; then JMODS_DIR="${JAVA_HOME}/lib/jmods"; fi && \
DETECTED_MODULES=$(find /build/jars -name "*.jar" | grep -v "jboss" | grep -v "wildfly" | grep -v "smallrye" | grep -v "spatial-extras" | xargs jdeps --print-module-deps --ignore-missing-deps --multi-release 25 2>&1 | tr ',' '\n' | sed 's/^ *//;s/ *$//' | grep -E '^[a-zA-Z0-9_.]+$' | sort -u | paste -sd "," -) && \
RAW_JDEPS_OUTPUT=$(find /build/jars -name "*.jar" | grep -v "jboss" | grep -v "wildfly" | grep -v "smallrye" | grep -v "spatial-extras" | xargs jdeps --print-module-deps --ignore-missing-deps --multi-release 25 2>&1 || true) && \
DETECTED_MODULES=$(echo "${RAW_JDEPS_OUTPUT}" | tr ',' '\n' | sed 's/^ *//;s/ *$//' | grep -E '^[a-zA-Z0-9_.]+$' | sort -u | paste -sd "," - || true) && \
if [ -d "$JMODS_DIR" ]; then \
AVAILABLE_MODULES=$(find "$JMODS_DIR" -name "*.jmod" -printf "%f\n" | sed 's/\.jmod$//' | sort -u | paste -sd "|" -) ; \
FILTERED_MODULES=$(echo "${DETECTED_MODULES}" | tr ',' '\n' | sed '/^$/d' | grep -E "^(${AVAILABLE_MODULES})$" | sort -u | paste -sd "," -) ; \
FILTERED_MODULES=$(echo "${DETECTED_MODULES}" | tr ',' '\n' | sed '/^$/d' | grep -E "^(${AVAILABLE_MODULES})$" | sort -u | paste -sd "," - || true) ; \
else \
FILTERED_MODULES="${DETECTED_MODULES}" ; \
fi && \
grep -v '^#' /build/jre_module_exclusions.txt | tr -d '\r' | sed '/^[[:space:]]*$/d' > /build/jre_excluded_modules.list && \
echo "📦 Excluded modules: $(paste -sd ',' /build/jre_excluded_modules.list)" && \
FILTERED_MODULES=$(echo "${FILTERED_MODULES}" | tr ',' '\n' | sed '/^$/d' | grep -vxFf /build/jre_excluded_modules.list | paste -sd "," -) && \
FILTERED_MODULES=$(echo "${FILTERED_MODULES}" | tr ',' '\n' | sed '/^$/d' | grep -vxFf /build/jre_excluded_modules.list | paste -sd "," - || true) && \
if [ -z "${FILTERED_MODULES}" ]; then \
echo "❌ jdeps found no modules; the java.se fallback would bring back the excluded modules, so the build stops" && \
exit 1 ; \
Expand All @@ -139,14 +147,14 @@ RUN echo "🔍 Analyzing JARs with jdeps..." && \
echo "" && \
echo "🔨 Running jlink..." && \
if [ -d "$JMODS_DIR" ]; then \
JLINK_SOURCE="--module-path $JMODS_DIR" ; \
set -- --module-path "$JMODS_DIR" ; \
echo " modules from ${JMODS_DIR}" ; \
else \
JLINK_SOURCE="" ; \
set -- ; \
echo " no jmods directory: linking from the run-time image of ${JAVA_HOME} (JEP 493)" ; \
fi && \
if ! jlink \
${JLINK_SOURCE} \
"$@" \
--add-modules "${REQUIRED_MODULES}" \
--ignore-signing-information \
--strip-debug \
Expand Down Expand Up @@ -189,8 +197,14 @@ RUN bash /build/dump_cds_archive.sh /build/jre

FROM python:${PYTHON_VERSION}-slim AS python-builder

# bash with pipefail for every RUN of this stage: a failing command in the middle of a pipe stops the build
# (hadolint DL4006). The RUNs below were written for sh, so they avoid bashisms that differ (echo with \n).
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

# Install minimal build tooling (no external JDK needed; we use bundled JRE)
RUN apt-get update && apt-get install -y \
# DL3008: versions are not pinned on purpose (rolling Debian base image, see the jre-builder stage).
# hadolint ignore=DL3008
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
curl \
&& rm -rf /var/lib/apt/lists/*
Expand Down Expand Up @@ -256,7 +270,8 @@ RUN if [ -n "${BUILD_VERSION}" ]; then \
export ARCADEDB_VERSION="${BUILD_VERSION}"; \
echo "📌 Using provided version: ${ARCADEDB_VERSION}"; \
else \
export ARCADEDB_VERSION=$(python3 scripts/extract_version.py --format=pep440 /build/jars); \
ARCADEDB_VERSION=$(python3 scripts/extract_version.py --format=pep440 /build/jars) && \
export ARCADEDB_VERSION && \
echo "📦 Extracted version from engine jar: ${ARCADEDB_VERSION}"; \
fi && \
echo "📦 Python package version: ${ARCADEDB_VERSION}" && \
Expand All @@ -275,20 +290,20 @@ RUN if [ -n "${BUILD_VERSION}" ]; then \
fi; \
if [ -n "${WHEEL_PLAT}" ]; then \
echo "🏷️ Building wheel with platform tag: ${WHEEL_PLAT}"; \
uvx --from "build[uv]==1.6.1" pyproject-build --installer uv --wheel --config-setting=--build-option=--plat-name=${WHEEL_PLAT}; \
uvx --from "build[uv]==1.6.1" pyproject-build --installer uv --wheel "--config-setting=--build-option=--plat-name=${WHEEL_PLAT}"; \
else \
uvx --from "build[uv]==1.6.1" pyproject-build --installer uv --wheel; \
fi; \
else \
uvx --from "build[uv]==1.6.1" pyproject-build --installer uv --wheel; \
fi && \
if echo "${TARGET_PLATFORM}" | grep -q '^linux-'; then \
if [ -n "${WHEEL_PLAT}" ] && ! ls /build/dist/*${WHEEL_PLAT}*.whl 1> /dev/null 2>&1; then \
if [ -n "${WHEEL_PLAT}" ] && ! ls /build/dist/*"${WHEEL_PLAT}"*.whl 1> /dev/null 2>&1; then \
echo "❌ Expected manylinux wheel (${WHEEL_PLAT}) not found"; \
ls -lh /build/dist; \
exit 1; \
fi; \
WHEEL_FILE=$(ls /build/dist/*.whl | head -n1); \
WHEEL_FILE=$(find /build/dist -maxdepth 1 -name '*.whl' | sort | head -n1); \
if [ -n "${WHEEL_FILE}" ]; then \
python3 scripts/verify_wheel_platform_tag.py "${WHEEL_FILE}" /build/jre || exit 1; \
fi; \
Expand All @@ -304,9 +319,15 @@ FROM python-builder AS export
# Stage 5: Test the built wheel
FROM python:${PYTHON_VERSION}-slim AS tester

# bash with pipefail for every RUN of this stage: a failing command in the middle of a pipe stops the build
# (hadolint DL4006). The RUNs below were written for sh, so they avoid bashisms that differ (echo with \n).
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

# No external JRE needed in tester; the wheel includes a bundled JRE
# Keep build-essential in case pytest or wheels need compilation on some platforms
RUN apt-get update && apt-get install -y \
# DL3008: versions are not pinned on purpose (rolling Debian base image, see the jre-builder stage).
# hadolint ignore=DL3008
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
curl \
&& rm -rf /var/lib/apt/lists/*
Expand All @@ -329,7 +350,7 @@ ENV UV_PYTHON_DOWNLOADS=never
COPY --from=python-builder /build/tests ./tests/

# Create a quick installation test
RUN echo '#!/usr/bin/env python3\n\
RUN printf '#!/usr/bin/env python3\n\
import arcadedb\n\
import tempfile\n\
import shutil\n\
Expand Down
16 changes: 10 additions & 6 deletions scripts/build-native.sh
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ JARS_DIR="$PY_BINDINGS_DIR/src/arcadedb/jars"
# shellcheck source=stage_jars.sh
source "$SCRIPT_DIR/stage_jars.sh"
stage_jars "$JARS_DIR" "$JARS_SOURCE" "$ARCADEDB_TAG"
echo -e "${CYAN}📦 $(ls -1 "$JARS_DIR"/*.jar | wc -l | tr -d ' ') JAR files${NC}"
echo -e "${CYAN}📦 $(find "$JARS_DIR" -maxdepth 1 -name '*.jar' | wc -l | tr -d ' ') JAR files${NC}"

# Always filter excluded jars, whichever source staged them.
apply_jar_exclusions "$JARS_DIR" "$JAR_EXCLUSIONS_FILE"
Expand All @@ -182,7 +182,7 @@ case "$(uname -s)" in
BRIDGE_CP=$(find "$JARS_DIR" -name '*.jar' | paste -sd ':' -)
;;
esac
javac -cp "$BRIDGE_CP" -d "$BRIDGE_CLASSES" $(find "$BRIDGE_SRC" -name "*.java")
find "$BRIDGE_SRC" -name "*.java" -print0 | xargs -0 javac -cp "$BRIDGE_CP" -d "$BRIDGE_CLASSES"
jar cf "$JARS_DIR/arcadedb-python-bridge.jar" -C "$BRIDGE_CLASSES" .
rm -rf "$BRIDGE_CLASSES"
echo -e "${GREEN}✅ Bridge jar built${NC}"
Expand All @@ -206,7 +206,7 @@ if [ ! -d "$JMODS_DIR" ]; then
fi

if [ -d "$JMODS_DIR" ]; then
AVAILABLE_MODULES=$(cd "$JMODS_DIR" && ls *.jmod 2> /dev/null | sed 's/\.jmod$//' | sort -u | paste -sd "|" -)
AVAILABLE_MODULES=$(for jmod in "$JMODS_DIR"/*.jmod; do if [ -e "$jmod" ]; then basename "$jmod" .jmod; fi; done | sort -u | paste -sd "|" -)
if [ -n "$AVAILABLE_MODULES" ]; then
FILTERED_MODULES=$(echo "$DETECTED_MODULES" | tr ',' '\n' | sed '/^$/d' | grep -E "^(${AVAILABLE_MODULES})$" | sort -u | paste -sd "," - || true)
else
Expand Down Expand Up @@ -286,7 +286,7 @@ echo -e "${CYAN}📦 Preparing package...${NC}"
# Build and copy JRE
copy_tree_with_symlinks "$PY_BINDINGS_DIR/temp_jre" "$PY_BINDINGS_DIR/src/arcadedb/jre" "$PYTHON_BIN"

JAR_COUNT=$(ls -1 "$JARS_DIR"/*.jar | wc -l)
JAR_COUNT=$(find "$JARS_DIR" -maxdepth 1 -name '*.jar' | wc -l)
echo -e "${GREEN}✅ Package prepared (${JAR_COUNT} JARs + JRE)${NC}"

# Step 4: Write version to pyproject.toml
Expand Down Expand Up @@ -364,12 +364,16 @@ rm -f "$DIST_DIR"/*.whl

# Rename wheel to have correct platform tag if needed
# (the build may not set it correctly for cross-platform builds)
WHEEL_FILE=$(ls "$DIST_DIR"/*.whl | head -n1)
WHEEL_FILE=""
for wheel in "$DIST_DIR"/*.whl; do
[[ -e "$wheel" ]] && WHEEL_FILE="$wheel"
break
done
if [[ -n "$WHEEL_FILE" ]]; then
# Extract components from wheel filename
WHEEL_NAME=$(basename "$WHEEL_FILE")
# arcadedb_jvm-25.10.1-py3-none-any.whl -> arcadedb_jvm-25.10.1-py3-none-PLAT_NAME.whl
NEW_WHEEL_NAME=$(echo "$WHEEL_NAME" | sed "s|-py3-none-any\.whl|-py3-none-${PLAT_NAME}.whl|")
NEW_WHEEL_NAME="${WHEEL_NAME/-py3-none-any.whl/-py3-none-${PLAT_NAME}.whl}"
if [[ "$WHEEL_NAME" != "$NEW_WHEEL_NAME" ]]; then
mv "$WHEEL_FILE" "$DIST_DIR/$NEW_WHEEL_NAME"
echo -e "${CYAN}🏷️ Renamed wheel to: ${YELLOW}${NEW_WHEEL_NAME}${NC}"
Expand Down
Loading
Loading