Skip to content

Commit eb460d8

Browse files
committed
Merge remote-tracking branch 'origin/main' into port/stpyv8
2 parents 2994d28 + 9dc2d66 commit eb460d8

64 files changed

Lines changed: 4180 additions & 125 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/build-cchardet.yml‎

Lines changed: 63 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,24 @@ jobs:
5151
CCHARDET_VERSION: ${{ matrix.version }}
5252

5353
steps:
54+
# Upstream tagged releases bare up to 2.1.7 and v-prefixed from 2.2.0 on.
55+
- name: Resolve the cChardet tag
56+
id: tag
57+
run: |
58+
for ref in "v$CCHARDET_VERSION" "$CCHARDET_VERSION"; do
59+
if git ls-remote --exit-code --tags https://github.com/PyYoshi/cChardet "refs/tags/$ref" >/dev/null; then
60+
echo "name=$ref" >> "$GITHUB_OUTPUT"
61+
exit 0
62+
fi
63+
done
64+
echo "::error::PyYoshi/cChardet has no tag for $CCHARDET_VERSION"
65+
exit 1
66+
5467
- name: Checkout cChardet ${{ env.CCHARDET_VERSION }}
5568
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
5669
with:
5770
repository: PyYoshi/cChardet
58-
ref: ${{ env.CCHARDET_VERSION }}
71+
ref: ${{ steps.tag.outputs.name }}
5972
submodules: recursive
6073
persist-credentials: false
6174

@@ -106,86 +119,68 @@ jobs:
106119
name: cchardet-${{ env.CCHARDET_VERSION }}-sdist
107120
path: dist/
108121

109-
- name: Extract sdist and stage the wheel smoke tests
110-
# Upstream's own suite (src/tests/test.py) uses nose, dead on modern
111-
# Python; this reproduces its checks with plain pytest assertions
112-
# against the same bundled testdata/samples.
122+
- name: Extract sdist and stage the wheel smoke test
123+
# Upstream's own cibuildwheel config (test-command/test-sources in
124+
# pyproject.toml) runs tools/wheel_smoke.py, but MANIFEST.in doesn't
125+
# ship tools/ in the sdist, so this reproduces that same script here.
113126
run: |
114127
mkdir pkg
115128
tar xzf dist/*.tar.gz --strip-components=1 -C pkg
116129
cat > pkg/riscv64_test_cchardet.py <<'EOF'
117-
import glob
130+
import json
118131
import os
132+
import subprocess
133+
import sys
134+
import sysconfig
135+
import tempfile
136+
from pathlib import Path
119137
120138
import cchardet
121139
122-
TESTDATA_DIR = "pkg/src/tests/testdata"
123-
SAMPLES_DIR = "pkg/src/tests/samples"
124-
125-
SKIP_LIST = [
126-
TESTDATA_DIR + "/ja/utf-16le.txt",
127-
TESTDATA_DIR + "/ja/utf-16be.txt",
128-
TESTDATA_DIR + "/es/iso-8859-15.txt",
129-
TESTDATA_DIR + "/da/iso-8859-1.txt",
130-
TESTDATA_DIR + "/he/iso-8859-8.txt",
131-
]
132-
SKIP_LIST_DECODE = SKIP_LIST + [
133-
TESTDATA_DIR + "/vi/viscii.txt",
134-
TESTDATA_DIR + "/zh/euc-tw.txt",
135-
]
136140
141+
def main() -> None:
142+
# Upstream only added this assertion in tools/wheel_smoke.py starting at
143+
# v2.3.0; v2.2.0's _cchardet extension doesn't declare free-threading
144+
# support, so CPython 3.14t legitimately re-enables the GIL on import.
145+
version = tuple(int(p) for p in os.environ["CCHARDET_VERSION"].split("."))
146+
if sysconfig.get_config_var("Py_GIL_DISABLED") and version >= (2, 3, 0):
147+
assert not getattr(sys, "_is_gil_enabled")()
137148
138-
def test_ascii():
139-
assert cchardet.detect(b"abcdefghijklmnopqrstuvwxyz")["encoding"].lower() == "ascii"
149+
package_dir = Path(cchardet.__file__).parent
150+
assert (package_dir / "py.typed").is_file()
151+
assert (package_dir / "_cchardet.pyi").is_file()
140152
153+
expected = cchardet.detect(b"plain ASCII")
154+
assert expected["encoding"] == "ASCII"
155+
assert cchardet.detect(bytearray(b"plain ASCII")) == expected
156+
assert cchardet.detect(memoryview(b"plain ASCII"), max_bytes=32) == expected
141157
142-
def test_detect():
143-
testfiles = glob.glob(TESTDATA_DIR + "/*/*.txt")
144-
assert testfiles
145-
for testfile in testfiles:
146-
if testfile in SKIP_LIST:
147-
continue
148-
expected = os.path.splitext(os.path.basename(testfile))[0]
149-
with open(testfile, "rb") as f:
150-
result = cchardet.detect(f.read())
151-
assert result["encoding"] is not None, testfile
152-
assert result["encoding"].lower() == expected.lower(), (testfile, result)
158+
french = ("Français, où êtes-vous ? Voilà l'été.").encode(
159+
"windows-1252"
160+
)
161+
candidates = cchardet.detect_all(french)
162+
assert candidates
163+
assert candidates[0] == cchardet.detect(french)
164+
assert candidates[0]["language"] == "fr"
153165
154-
155-
def test_detector():
156166
detector = cchardet.UniversalDetector()
157-
with open(SAMPLES_DIR + "/wikipediaJa_One_Thousand_and_One_Nights_SJIS.txt", "rb") as f:
158-
for line in f:
159-
detector.feed(line)
160-
if detector.done:
161-
break
167+
detector.feed(french[:10])
168+
detector.feed(french[10:])
162169
detector.close()
163-
assert detector.result["encoding"].lower() == "shift_jis"
164-
165-
166-
def test_github_issue_20():
167-
msg = b"\x8f"
168-
cchardet.detect(msg)
169-
detector = cchardet.UniversalDetector()
170-
detector.feed(msg)
171-
detector.close()
172-
173-
174-
def test_decode():
175-
for testfile in glob.glob(TESTDATA_DIR + "/*/*.txt"):
176-
if testfile in SKIP_LIST_DECODE:
177-
continue
178-
with open(testfile, "rb") as f:
179-
msg = f.read()
180-
msg.decode(cchardet.detect(msg)["encoding"])
181-
170+
assert detector.result == candidates[0]
182171
183-
def test_utf8_with_bom():
184-
assert cchardet.detect(b"\xef\xbb\xbf")["encoding"].lower() == "utf-8-sig"
172+
with tempfile.TemporaryDirectory() as directory:
173+
sample = Path(directory) / "sample.txt"
174+
sample.write_bytes(french)
175+
output = subprocess.check_output(
176+
[sys.executable, "-m", "cchardet", "--json", str(sample)],
177+
text=True,
178+
)
179+
assert json.loads(output)["language"] == "fr"
185180
186181
187-
def test_null_bytes():
188-
assert cchardet.detect(b"ABC\x00\x80\x81")["encoding"] is None
182+
if __name__ == "__main__":
183+
main()
189184
EOF
190185
191186
- name: Build and test wheels
@@ -196,10 +191,12 @@ jobs:
196191
only: ${{ matrix.python }}-manylinux_riscv64
197192
env:
198193
CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }}
199-
CIBW_TEST_REQUIRES: pytest
200194
# CIBW_TEST_SOURCES is relative to the cwd, not package-dir, hence "pkg/".
201-
CIBW_TEST_SOURCES: pkg/riscv64_test_cchardet.py pkg/src/tests/testdata pkg/src/tests/samples
202-
CIBW_TEST_COMMAND: python -m pytest pkg/riscv64_test_cchardet.py -v
195+
CIBW_TEST_SOURCES: pkg/riscv64_test_cchardet.py
196+
CIBW_TEST_COMMAND: python pkg/riscv64_test_cchardet.py
197+
# A job-level `env:` never reaches the Linux container (gotcha 129), so the
198+
# test script's os.environ["CCHARDET_VERSION"] read needs it via CIBW_ENVIRONMENT.
199+
CIBW_ENVIRONMENT: CCHARDET_VERSION=${{ env.CCHARDET_VERSION }}
203200

204201
- name: Verify the wheel ships the compiled extension
205202
run: |
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# SPDX-FileCopyrightText: 2026 The RISE Project
2+
# SPDX-License-Identifier: MIT
3+
#
4+
# This workflow is based on: https://github.com/apple/coremltools/blob/main/scripts/build.sh
5+
name: Build coremltools wheels (riscv64)
6+
7+
on:
8+
workflow_dispatch:
9+
inputs:
10+
version:
11+
description: 'Version glob to (re)build; empty builds every version of docs/packages/coremltools.yaml not released yet'
12+
required: false
13+
default: ''
14+
pull_request:
15+
branches: [main]
16+
paths:
17+
- '.github/workflows/build-coremltools.yml'
18+
- 'docs/packages/coremltools.yaml'
19+
- 'patches/coremltools/**'
20+
push:
21+
branches: [main]
22+
paths:
23+
- '.github/workflows/build-coremltools.yml'
24+
- 'docs/packages/coremltools.yaml'
25+
- 'patches/coremltools/**'
26+
27+
concurrency:
28+
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
29+
cancel-in-progress: true
30+
31+
permissions:
32+
contents: read
33+
34+
env:
35+
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64
36+
PIP_EXTRA_INDEX_URL: https://pypi.riseproject.dev/simple/
37+
38+
jobs:
39+
setup:
40+
uses: $/.github/workflows/_setup.yml
41+
with:
42+
package: coremltools
43+
version: ${{ inputs.version }}
44+
45+
build_wheels:
46+
needs: [setup]
47+
if: needs.setup.outputs.versions != '[]'
48+
name: Build coremltools ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64
49+
runs-on: ubuntu-24.04-riscv
50+
timeout-minutes: 720
51+
strategy:
52+
fail-fast: false
53+
matrix:
54+
version: ${{ fromJSON(needs.setup.outputs.versions) }}
55+
# Upstream stops at 3.13, and the vendored pybind11 2.13.1 predates 3.14.
56+
python: [cp312-cp312, cp313-cp313]
57+
58+
env:
59+
COREMLTOOLS_VERSION: ${{ matrix.version }}
60+
61+
steps:
62+
- name: Checkout apple/coremltools ${{ env.COREMLTOOLS_VERSION }}
63+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
64+
with:
65+
repository: apple/coremltools
66+
ref: ${{ env.COREMLTOOLS_VERSION }}
67+
path: coremltools
68+
persist-credentials: false
69+
70+
- name: Checkout python-wheels
71+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
72+
with:
73+
path: python-wheels
74+
persist-credentials: false
75+
76+
- name: Patch coremltools source
77+
working-directory: coremltools
78+
run: git apply ../python-wheels/patches/coremltools/${{ env.COREMLTOOLS_VERSION }}/00*.patch
79+
80+
- name: Build the C++ libraries and the wheel
81+
run: |
82+
mkdir -p output "$HOME/.cache/coremltools-ccache"
83+
docker run -i --rm \
84+
-v "$PWD/coremltools:/coremltools" \
85+
-v "$PWD/output:/output" \
86+
-v "$HOME/.cache/coremltools-ccache:/ccache" \
87+
-e CCACHE_DIR=/ccache \
88+
-e COREMLTOOLS_VERSION \
89+
"$MANYLINUX_RISCV64_IMAGE" bash -s <<'COREMLTOOLS_BUILD_EOF'
90+
#!/usr/bin/env bash
91+
set -euxo pipefail
92+
93+
COREMLTOOLS_VERSION="${COREMLTOOLS_VERSION:?must be set, e.g. 9.0}"
94+
PYBIN=/opt/python/${{ matrix.python }}/bin
95+
# CMakeLists.txt builds the vendored kmeans1d with a bare `python3`, so the
96+
# interpreter this wheel is for has to be the one that wins on PATH.
97+
export PATH="$PYBIN:$PATH"
98+
# The vendored protobuf 3.19 declares cmake_minimum_required(VERSION 3.0.0).
99+
export CMAKE_POLICY_VERSION_MINIMUM=3.5
100+
101+
dnf install -y --setopt=install_weak_deps=False libuuid-devel
102+
103+
python3 -m pip install -U setuptools wheel auditwheel
104+
105+
# Upstream's NOTICE.txt covers only kmeans1d; the rest of deps/ is compiled
106+
# into the shipped .so files, and auditwheel grafts in libuuid.
107+
cp /coremltools/deps/protobuf/LICENSE /coremltools/LICENSE.protobuf
108+
cp /coremltools/deps/pybind11/LICENSE /coremltools/LICENSE.pybind11
109+
cp /coremltools/deps/nlohmann/LICENSE.MIT /coremltools/LICENSE.nlohmann-json
110+
cp /coremltools/deps/FP16/LICENSE /coremltools/LICENSE.FP16
111+
uuid_pkg=$(rpm -qf --qf '%{NAME}\n' "$(ldconfig -p | awk '/libuuid\.so\.1 /{print $NF; exit}')")
112+
dnf reinstall -y --setopt=tsflags= "$uuid_pkg" || true
113+
cat /usr/share/licenses/"$uuid_pkg"/* > /coremltools/LICENSE.libuuid 2>/dev/null \
114+
|| rpm -q --qf '%{NAME} %{VERSION}: %{LICENSE}\n' "$uuid_pkg" > /coremltools/LICENSE.libuuid
115+
116+
CCACHE_ARGS=""
117+
command -v ccache >/dev/null 2>&1 && \
118+
CCACHE_ARGS="-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache"
119+
120+
# FindPythonLibs searches standard prefixes and would find the image's own
121+
# /usr/local interpreter instead of /opt/python/<tag>, so pin it the way
122+
# scripts/env_activate.sh pins it for upstream's conda env.
123+
py_inc=$(python3 -c 'import sysconfig; print(sysconfig.get_path("include"))')
124+
py_lib=$(python3 -c 'import os, sysconfig; print(os.path.join(sysconfig.get_config_var("LIBDIR"), sysconfig.get_config_var("LIBRARY")))')
125+
126+
cmake -S /coremltools -B /tmp/build \
127+
-DCMAKE_BUILD_TYPE=Release \
128+
-DPYTHON_EXECUTABLE:FILEPATH="$PYBIN/python3" \
129+
-DPYTHON_INCLUDE_DIR="$py_inc" \
130+
-DPYTHON_LIBRARY="$py_lib" \
131+
-DOVERWRITE_PB_SOURCE=0 \
132+
${CCACHE_ARGS}
133+
cmake --build /tmp/build -j "$(nproc)"
134+
cmake --build /tmp/build --target dist
135+
136+
auditwheel repair --strip /tmp/build/dist/coremltools-*.whl -w /output
137+
ls -la /output
138+
COREMLTOOLS_BUILD_EOF
139+
140+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
141+
with:
142+
name: coremltools-${{ env.COREMLTOOLS_VERSION }}-${{ matrix.python }}-manylinux_riscv64
143+
path: output/*.whl
144+
if-no-files-found: error
145+
146+
- name: Test coremltools wheel
147+
run: |
148+
docker run -i --rm \
149+
-v "$PWD/output:/output" \
150+
-e PIP_EXTRA_INDEX_URL \
151+
-e PIP_ONLY_BINARY=numpy,pillow \
152+
"$MANYLINUX_RISCV64_IMAGE" bash -s <<'COREMLTOOLS_TEST_EOF'
153+
#!/usr/bin/env bash
154+
set -euxo pipefail
155+
156+
PYBIN=/opt/python/${{ matrix.python }}/bin
157+
158+
"$PYBIN/pip" install -U pytest pytest-timeout parameterized pillow
159+
"$PYBIN/pip" install /output/coremltools-*.whl
160+
161+
"$PYBIN/python3" -c "
162+
from coremltools import libmilstoragepython, libmodelpackage
163+
assert libmilstoragepython.__file__.endswith('.so'), libmilstoragepython.__file__
164+
assert libmodelpackage.__file__.endswith('.so'), libmodelpackage.__file__
165+
"
166+
167+
# Upstream's scripts/test.sh runs the suite out of the installed wheel with
168+
# --pyargs, from a directory that is not the checkout. torch, tensorflow,
169+
# scikit-learn and xgboost have no riscv64 wheels, so this is the subset of
170+
# upstream's coremltools.test / mil jobs that collects without them; the two
171+
# -k exclusions are tests that import torch inside the test body.
172+
mkdir -p /tmp/testrun && cd /tmp/testrun
173+
"$PYBIN/python3" -m pytest -v -ra --timeout=600 \
174+
-k "not (TestApiVisibilities and test_optimize) and not test_model_save_no_extension" \
175+
--pyargs \
176+
coremltools.test.api \
177+
coremltools.test.blob \
178+
coremltools.test.modelpackage.test_modelpackage \
179+
coremltools.converters.mil.backend \
180+
coremltools.converters.mil.mil.tests.test_types
181+
COREMLTOOLS_TEST_EOF
182+
183+
publish:
184+
name: Publish coremltools ${{ matrix.version }}
185+
needs: [setup, build_wheels]
186+
if: needs.setup.outputs.versions != '[]'
187+
strategy:
188+
fail-fast: false
189+
matrix:
190+
version: ${{ fromJSON(needs.setup.outputs.versions) }}
191+
permissions:
192+
contents: write
193+
pull-requests: write
194+
uses: $/.github/workflows/_publish-wheel.yml
195+
secrets:
196+
app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }}
197+
with:
198+
artifact-pattern: coremltools-${{ matrix.version }}-*-manylinux_riscv64

0 commit comments

Comments
 (0)