Skip to content

Commit 74d03e5

Browse files
committed
Update Version Lab for integrated PyMuPDF builds
1 parent 4401a5d commit 74d03e5

22 files changed

Lines changed: 387 additions & 295 deletions

‎.github/docker/pymupdf-source-stack/ENVIRONMENTS.md‎

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ rebuilt; they must not be used by benchmark jobs.
1717

1818
Container publication is restricted to the repository's default branch and
1919
serialized so two publishers cannot race to update a version tag. The publisher
20-
checks the system environment and RapidOCR import, but does not promote its
20+
checks the system environment and RapidOCR/ONNX imports using the dependencies
21+
in `tools/version_lab/uv.lock`, but does not promote its
2122
digest into the benchmark workflow automatically. Before changing the pinned
2223
benchmark digest, run the all-latest 15-case benchmark against the candidate,
2324
verify exact MuPDF provenance and Layout/OCR compatibility, compare aggregate
@@ -61,13 +62,19 @@ The latest image uses:
6162

6263
The image contains system and build dependencies, not the selected PyMuPDF
6364
source stack or the ParseBench Python environment. The benchmark workflow still
64-
needs to install its locked dependencies and build the selected MuPDF,
65-
PyMuPDF, PyMuPDF Layout, and PyMuPDF4LLM revisions inside the container. The
65+
needs to install its locked dependencies and build one PyMuPDF wheel containing
66+
the selected Layout and PyMuPDF4LLM sources. MuPDF is built from the native
67+
version declared by the selected PyMuPDF revision. The
6668
image includes `unzip` because current MuPDF source uses it while generating
6769
the Extract DOCX template. `rapidocr-onnxruntime==1.2.3` and
6870
`opencv-python==4.13.0.92` were installed only inside the publisher's
6971
validation environment; they were not baked into the image.
7072

73+
The current publisher mounts the repository read-only and installs the Version
74+
Lab lockfile into a temporary environment, skipping the two local projects.
75+
This validates the same RapidOCR and ONNX dependencies used by benchmark runs
76+
without duplicating their version pins in the workflow.
77+
7178
The image writes its principal versions to
7279
`/usr/local/share/parsebench/environment.txt` for runtime verification.
7380

@@ -101,9 +108,11 @@ The production workflow uses the following integration:
101108
3. Run workflow shell steps with Bash inside the container.
102109
4. Mark the workspace and the four source checkouts as Git safe directories,
103110
because Actions mounts them with host ownership.
104-
5. Use `uv sync --locked --extra runners`, excluding the three source-built
105-
PyMuPDF packages as the current workflow does.
111+
5. Use `uv sync --project tools/version_lab --locked`, excluding `pymupdf`,
112+
`pymupdf-layout`, and `pymupdf4llm` as the current workflow does.
106113
6. Verify `/usr/local/share/parsebench/environment.txt`, Python, Tesseract,
107114
SWIG, and the RapidOCR import before compiling the selected source stack.
108-
7. Keep the existing behavioral Layout/OCR compatibility test as the final
109-
environment and source-stack gate.
115+
7. Build PyMuPDF with `PYMUPDF_SETUP_PATH_LAYOUT` and `PYMUPDF_SETUP_PATH_4LLM`
116+
pointing to the selected source checkouts, and the resolved MuPDF source.
117+
8. Verify all four source revisions and run the behavioral Layout/OCR
118+
compatibility test before benchmarking.

‎.github/scripts/pymupdf_source_stack/README.md‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ the adapters in this directory translate GitHub environment variables and
1010
output files to that shared core. GitHub-only publishing and failure-summary
1111
behavior remains here.
1212

13-
MuPDF is an independently selected source component. The workflow resolves its
14-
latest PyPI release tag or current branch head to a full SHA before checkout, then gives
15-
PyMuPDF's build system a reproducible
13+
MuPDF follows the version declared in the selected PyMuPDF commit's `setup.py`;
14+
developers do not select it separately. The workflow resolves that native
15+
release tag to a full SHA before checkout, then gives PyMuPDF's build system a reproducible
1616
`git:--sha <sha> https://github.com/ArtifexSoftware/mupdf.git` selector through
1717
`PYMUPDF_SETUP_MUPDF_BUILD`. The compatibility gate independently compares
1818
that selector with the source embedded in the installed
@@ -36,12 +36,16 @@ reports, scores, or upstream modules.
3636
Each adapter is a small command with one responsibility. `resolve_dataset.py`
3737
resolves the `current` Hugging Face branch or validates a user-supplied full
3838
commit SHA. `resolve_refs.py` uses the shared source resolver for all four
39-
components. Source inputs default to `latest`, which resolves the current
40-
PyPI release to its Git tag. Existing branch names, all default branches, and
41-
the newest branch head per repository are also supported. Historical release
39+
components. PyMuPDF, Layout, and PyMuPDF4LLM inputs default to `latest`, which
40+
resolves the current PyMuPDF PyPI release to its Git tag in each repository.
41+
Existing branch names, all default branches, and the newest branch head per
42+
repository are also supported; MuPDF always follows the selected PyMuPDF commit. Historical release
4243
tags and arbitrary source commits are rejected. Layout always comes from
4344
`ArtifexSoftware/pymupdf_layout`. `resolve_sources.py` verifies every source
44-
checkout against its resolved SHA before the source builds begin. Finally,
45+
checkout against its resolved SHA and exports absolute source roots. PyMuPDF
46+
builds one wheel using `PYMUPDF_SETUP_PATH_LAYOUT` and
47+
`PYMUPDF_SETUP_PATH_4LLM`; the embedded commit IDs are checked before benchmarking.
48+
There are no standalone Layout/4LLM installs or older packaging fallbacks. Finally,
4549
`benchmark.py download` reuses only a complete cached snapshot whose internal
4650
revision marker matches that SHA. A missing, stale, or incomplete snapshot is
4751
removed and downloaded again. Inputs supplied by the workflow are passed

‎.github/scripts/pymupdf_source_stack/common.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
COMPONENT_SPECS,
1717
DATASET_BRANCHES,
1818
DATASET_REPOSITORY,
19+
SELECTABLE_COMPONENTS,
1920
)
2021
from parsebench_version_lab.provenance import mupdf_build_spec # noqa: E402, F401 - compatibility re-export
2122
from parsebench_version_lab.util import required_env as env # noqa: E402, F401

‎.github/scripts/pymupdf_source_stack/configure.py‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import re
77
from pathlib import Path
88

9-
from common import COMPONENT_SPECS, append_summary, env, write_github_outputs, write_json
9+
from common import COMPONENT_SPECS, SELECTABLE_COMPONENTS, append_summary, env, write_github_outputs, write_json
1010

1111
RUN_SCOPES = {
1212
"Quick test (15 cases)": ("test", "data/test"),
@@ -20,7 +20,7 @@
2020
"Text content": "text_content",
2121
"Text formatting": "text_formatting",
2222
}
23-
LATEST_REFS = {name: component.default_branch for name, component in COMPONENT_SPECS.items()}
23+
LATEST_REFS = {name: component.default_branch for name, component in SELECTABLE_COMPONENTS.items()}
2424
LATEST_ANY_BRANCH_REFS = dict.fromkeys(LATEST_REFS, "latest-any-branch")
2525

2626

@@ -53,7 +53,7 @@ def main() -> int:
5353
raise SystemExit("Select either all latest default-branch commits or latest commits from any branch, not both")
5454
requested_refs = {
5555
"dataset": env("DATASET_REF"),
56-
"mupdf": env("MUPDF_REF"),
56+
"mupdf": "auto",
5757
"pymupdf": env("PYMUPDF_REF"),
5858
"pymupdf_layout": env("PYMUPDF_LAYOUT_REF"),
5959
"pymupdf4llm": env("PYMUPDF4LLM_REF"),
@@ -70,7 +70,6 @@ def main() -> int:
7070

7171
stack_label = "_".join(
7272
(
73-
f"mupdf-{safe_ref(refs['mupdf'])}",
7473
f"pymupdf-{safe_ref(refs['pymupdf'])}",
7574
f"layout-{safe_ref(refs['pymupdf_layout'])}",
7675
f"4llm-{safe_ref(refs['pymupdf4llm'])}",

‎.github/scripts/pymupdf_source_stack/resolve_refs.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,16 @@ def main() -> int:
1717
request = json.loads(request_path.read_text(encoding="utf-8"))
1818
manager = SourceManager(CommandRunner())
1919
outputs = {}
20+
resolved = {}
2021
for name, component in COMPONENT_SPECS.items():
2122
selection = request[name]["ref"]
2223
try:
23-
source = manager.resolve_ref(component, selection)
24+
source = (
25+
manager.resolve_mupdf(resolved["pymupdf"])
26+
if name == "mupdf"
27+
else manager.resolve_ref(component, selection)
28+
)
29+
resolved[name] = source
2430
except (OSError, RuntimeError, ValueError) as error:
2531
message = f"Cannot resolve {component.label} selection {selection!r}: {error}"
2632
write_json(

‎.github/scripts/pymupdf_source_stack/resolve_sources.py‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
#!/usr/bin/env python3
2-
"""Verify pinned source checkouts and locate their installable packages."""
2+
"""Verify pinned source checkouts for the integrated PyMuPDF build."""
33

44
from __future__ import annotations
55

66
import json
77
from pathlib import Path
88

99
from common import COMPONENT_SPECS, env, git_sha, mupdf_build_spec, write_github_outputs, write_json
10-
from parsebench_version_lab.local import package_dir
11-
from parsebench_version_lab.sources import ResolvedSource
1210

1311

1412
def main() -> int:
@@ -18,11 +16,9 @@ def main() -> int:
1816
for name, component in COMPONENT_SPECS.items():
1917
root = Path(".source") / component.checkout_dir
2018
sha = git_sha(root)
21-
source = ResolvedSource(name, component.label, component.repository, request[name]["ref"], sha, root)
2219
try:
2320
if sha != request[name]["resolved_sha"]:
2421
raise RuntimeError(f"{component.label} checkout {sha} differs from the resolved commit.")
25-
package = root if name == "mupdf" else package_dir(source)
2622
except RuntimeError as error:
2723
write_json(
2824
output_dir / "_failure.json",
@@ -35,7 +31,7 @@ def main() -> int:
3531
},
3632
)
3733
raise SystemExit(str(error)) from error
38-
outputs[f"{name}_dir"] = str(package)
34+
outputs[f"{name}_dir"] = str(root.resolve())
3935
outputs[f"{name}_sha"] = sha
4036
outputs["mupdf_build"] = mupdf_build_spec(outputs["mupdf_sha"])
4137
write_github_outputs(outputs)

‎.github/workflows/pymupdf-source-stack-environment.yml‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
env:
6161
IMAGE_TAG: ${{ steps.candidate.outputs.tag }}
6262
run: |
63-
docker run --rm "$IMAGE_TAG" bash -euo pipefail -c '
63+
docker run --rm --volume "$GITHUB_WORKSPACE:/workspace:ro" "$IMAGE_TAG" bash -euo pipefail -c '
6464
set -x
6565
test "$(python --version)" = "Python 3.12.13"
6666
test "$(uv --version | cut -d" " -f2)" = "0.11.29"
@@ -76,11 +76,11 @@ jobs:
7676
cc --version
7777
swig -version
7878
cat /usr/local/share/parsebench/environment.txt
79-
uv venv /tmp/rapidocr-smoke
80-
uv pip install --python /tmp/rapidocr-smoke/bin/python \
81-
"rapidocr-onnxruntime==1.2.3" "opencv-python==4.13.0.92"
79+
UV_PROJECT_ENVIRONMENT=/tmp/rapidocr-smoke \
80+
uv sync --project /workspace/tools/version_lab --locked \
81+
--no-install-project --no-install-package parse-bench
8282
/tmp/rapidocr-smoke/bin/python -c \
83-
"from rapidocr_onnxruntime import RapidOCR; print(RapidOCR)"
83+
"from importlib.metadata import version; from rapidocr import RapidOCR; import onnxruntime; print(version(\"rapidocr\"), onnxruntime.__version__, RapidOCR)"
8484
'
8585
8686
- name: Log in to GitHub Container Registry

‎.github/workflows/pymupdf-source-stack-parsebench.yml‎

Lines changed: 13 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@ run-name: >-
77
inputs.pipeline,
88
inputs.latest_any_branch && 'Latest branch' ||
99
inputs.all_latest && 'Latest default' ||
10-
inputs.mupdf_ref == inputs.pymupdf_ref &&
11-
inputs.mupdf_ref == inputs.pymupdf_layout_ref &&
12-
inputs.mupdf_ref == inputs.pymupdf4llm_ref &&
13-
inputs.mupdf_ref ||
10+
inputs.pymupdf_ref == inputs.pymupdf_layout_ref &&
11+
inputs.pymupdf_ref == inputs.pymupdf4llm_ref &&
12+
inputs.pymupdf_ref ||
1413
format(
15-
'M:{0},P:{1},L:{2},4:{3}',
16-
inputs.mupdf_ref,
14+
'P:{0},L:{1},4:{2}',
1715
inputs.pymupdf_ref,
1816
inputs.pymupdf_layout_ref,
1917
inputs.pymupdf4llm_ref
@@ -30,12 +28,12 @@ on:
3028
workflow_dispatch:
3129
inputs:
3230
all_latest:
33-
description: "Use latest default-branch commits for MuPDF, PyMuPDF, Layout and PyMuPDF4LLM. Their ref inputs are ignored."
31+
description: "Use default branches for PyMuPDF, Layout and PyMuPDF4LLM. MuPDF follows PyMuPDF. Ref inputs are ignored."
3432
required: true
3533
default: false
3634
type: boolean
3735
latest_any_branch:
38-
description: "Use the newest branch-head commit in each of the four source repositories. Cannot be combined with latest default-branch commits."
36+
description: "Use newest branch heads for PyMuPDF, Layout and PyMuPDF4LLM. MuPDF follows PyMuPDF. Cannot combine with latest default branches."
3937
required: true
4038
default: false
4139
type: boolean
@@ -56,23 +54,18 @@ on:
5654
required: true
5755
default: current
5856
type: string
59-
mupdf_ref:
60-
description: "MuPDF: latest PyPI release or a current branch name. Ignored by either automatic branch mode."
61-
required: true
62-
default: "latest"
63-
type: string
6457
pymupdf_ref:
6558
description: "PyMuPDF: latest PyPI release or a current branch name. Ignored by either automatic branch mode."
6659
required: true
6760
default: "latest"
6861
type: string
6962
pymupdf_layout_ref:
70-
description: "PyMuPDF Layout: latest PyPI release or a current branch name. Ignored by either automatic branch mode."
63+
description: "PyMuPDF Layout: current PyMuPDF release tag or a current branch name. Ignored by either automatic branch mode."
7164
required: true
7265
default: "latest"
7366
type: string
7467
pymupdf4llm_ref:
75-
description: "PyMuPDF4LLM: latest PyPI release or a current branch name. Ignored by either automatic branch mode."
68+
description: "PyMuPDF4LLM: current PyMuPDF release tag or a current branch name. Ignored by either automatic branch mode."
7669
required: true
7770
default: "latest"
7871
type: string
@@ -137,7 +130,6 @@ jobs:
137130
DATASET_REF: ${{ inputs.dataset_version }}
138131
GROUP_SELECTION: ${{ inputs.group }}
139132
LATEST_ANY_BRANCH: ${{ inputs.latest_any_branch }}
140-
MUPDF_REF: ${{ inputs.mupdf_ref }}
141133
PYMUPDF4LLM_REF: ${{ inputs.pymupdf4llm_ref }}
142134
PYMUPDF_LAYOUT_REF: ${{ inputs.pymupdf_layout_ref }}
143135
PYMUPDF_REF: ${{ inputs.pymupdf_ref }}
@@ -249,64 +241,30 @@ jobs:
249241
run: python3 "$WORKFLOW_SCRIPTS/write_run_summary.py"
250242

251243
- name: Install ParseBench dependencies without selected PyMuPDF packages
252-
env:
253-
MODERN_RAPIDOCR_PIPELINE: pymupdf4llm_html_tables_rapidocr_v3
254244
run: |
255245
export UV_PROJECT_ENVIRONMENT="$GITHUB_WORKSPACE/.venv"
256-
sync_args=(--project tools/version_lab --locked)
257-
if [[ "$PIPELINE" == "$MODERN_RAPIDOCR_PIPELINE" ]]; then
258-
sync_args+=(--extra rapidocr-modern)
259-
fi
260-
uv sync "${sync_args[@]}" \
246+
uv sync --project tools/version_lab --locked \
261247
--no-install-package pymupdf \
262248
--no-install-package pymupdf-layout \
263249
--no-install-package pymupdf4llm
264-
uv pip install --python .venv "pipcl==12" "swig==4.3.1"
250+
uv pip install --python .venv "swig==4.3.1"
265251
.venv/bin/swig -version
266252
267253
- name: Verify RapidOCR native runtime
268-
if: ${{ inputs.pipeline != 'pymupdf4llm_html_tables_rapidocr_v3' }}
269-
run: .venv/bin/python -c "from rapidocr_onnxruntime import RapidOCR; print(RapidOCR)"
270-
271-
- name: Verify modern RapidOCR native runtime
272-
if: ${{ inputs.pipeline == 'pymupdf4llm_html_tables_rapidocr_v3' }}
273254
run: >-
274255
.venv/bin/python -c
275256
"from importlib.metadata import version; from rapidocr import RapidOCR; import onnxruntime;
276257
print(f'rapidocr={version(\"rapidocr\")} onnxruntime={onnxruntime.__version__} engine={RapidOCR}')"
277258
278-
- name: Build and install selected PyMuPDF
259+
- name: Build and install integrated PyMuPDF
279260
env:
280261
PACKAGE_DIR: ${{ steps.sources.outputs.pymupdf_dir }}
281262
PYMUPDF_SETUP_MUPDF_BUILD: ${{ steps.sources.outputs.mupdf_build }}
282263
PYMUPDF_SETUP_SWIG: ${{ github.workspace }}/.venv/bin/swig
264+
PYMUPDF_SETUP_PATH_LAYOUT: ${{ steps.sources.outputs.pymupdf_layout_dir }}
265+
PYMUPDF_SETUP_PATH_4LLM: ${{ steps.sources.outputs.pymupdf4llm_dir }}
283266
run: uv pip install --python .venv --reinstall --no-deps "$PACKAGE_DIR"
284267

285-
- name: Build and install selected PyMuPDF Layout
286-
env:
287-
PACKAGE_DIR: ${{ steps.sources.outputs.pymupdf_layout_dir }}
288-
PYMUPDF_LAYOUT_SETUP_BUILD_PYMUPDF: "1"
289-
PYMUPDF_LAYOUT_SETUP_SWIG: ${{ github.workspace }}/.venv/bin/swig
290-
run: uv pip install --python .venv --reinstall --no-build-isolation --no-deps "$PACKAGE_DIR"
291-
292-
- name: Build and install selected PyMuPDF4LLM
293-
env:
294-
PACKAGE_DIR: ${{ steps.sources.outputs.pymupdf4llm_dir }}
295-
run: uv pip install --python .venv --reinstall --no-deps "$PACKAGE_DIR"
296-
297-
- name: Verify modern RapidOCR and native HTML capabilities
298-
if: ${{ inputs.pipeline == 'pymupdf4llm_html_tables_rapidocr_v3' }}
299-
run: >-
300-
.venv/bin/python -c
301-
"import inspect, pymupdf4llm; from pymupdf4llm.helpers.pymupdf_rag import to_markdown as rag_to_markdown;
302-
from pymupdf4llm.ocr.detect_rapidocr import detect_rapidocr_backend;
303-
backend = detect_rapidocr_backend(); assert backend == 'rapidocr',
304-
f'Expected modern rapidocr backend, received {backend!r}';
305-
assert 'table_output' in inspect.signature(rag_to_markdown).parameters and
306-
'table_output' in inspect.getsource(pymupdf4llm.to_markdown),
307-
'Selected PyMuPDF4LLM revision lacks native table_output support';
308-
print('modern RapidOCR and native HTML table output are available')"
309-
310268
- name: Check source stack compatibility
311269
env:
312270
MUPDF_REF: ${{ steps.refs.outputs.mupdf_ref }}

0 commit comments

Comments
 (0)