diff --git a/.github/workflows/build-essentia.yml b/.github/workflows/build-essentia.yml new file mode 100644 index 000000000000..e92bead85e21 --- /dev/null +++ b/.github/workflows/build-essentia.yml @@ -0,0 +1,268 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on: https://github.com/MTG/essentia/blob/8dbdc0735c0dc54fc2c57e083883b4baba7bf272/.github/workflows/build-wheels-cibuildwheel.yml +name: Build essentia wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/essentia.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-essentia.yml' + - 'docs/packages/essentia.yaml' + - 'patches/essentia/**' + push: + branches: [main] + paths: + - '.github/workflows/build-essentia.yml' + - 'docs/packages/essentia.yaml' + - 'patches/essentia/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + # Upstream publishes untagged dev builds whose version is `git describe`'s + # commit count; this is the commit 2.1b6.dev1438 was built from. Move both together. + ESSENTIA_REF: 8dbdc0735c0dc54fc2c57e083883b4baba7bf272 + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: essentia + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build essentia ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 720 + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + python: ["cp312", "cp313", "cp314"] + + env: + ESSENTIA_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout essentia ${{ env.ESSENTIA_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: MTG/essentia + ref: ${{ env.ESSENTIA_REF }} + fetch-depth: 0 + persist-credentials: false + + - name: Fetch release tags from GitHub + run: git fetch --tags --force + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Patch essentia source + run: git apply python-wheels/patches/essentia/${{ env.ESSENTIA_VERSION }}/*.patch + + - name: Stage the statically linked libraries' licence texts + run: | + . packaging/build_config.sh + mkdir -p ../licences && cd ../licences + stage() { + name=$1 url=$2 pattern=${3:-} + curl -fsSL --retry 5 "$url" -o "$name.tar.gz" + mkdir "$name" && tar -xf "$name.tar.gz" -C "$name" --strip-components=1 + if [ -n "$pattern" ]; then + cat "$name/$pattern" > "$GITHUB_WORKSPACE/LICENSE.$name" + else + find "$name" -maxdepth 1 -type f \( -name 'COPYING*' -o -name 'LICEN[CS]E*' -o -name 'COPYRIGHT*' \) \ + | sort | xargs cat > "$GITHUB_WORKSPACE/LICENSE.$name" + fi + test -s "$GITHUB_WORKSPACE/LICENSE.$name" + } + stage eigen "https://gitlab.com/libeigen/eigen/-/archive/$EIGEN_VERSION/eigen-$EIGEN_VERSION.tar.gz" + stage fftw "http://www.fftw.org/$FFTW_VERSION.tar.gz" + stage lame "https://sourceforge.net/projects/lame/files/lame/$LAME_VERSION/lame-$LAME_VERSION.tar.gz/download" + stage ffmpeg "https://ffmpeg.org/releases/$FFMPEG_VERSION.tar.gz" + stage libsamplerate "https://github.com/libsndfile/libsamplerate/releases/download/0.1.9/$LIBSAMPLERATE_VERSION.tar.gz" + # zlib 1.2.12 has no LICENSE file; its licence text is in the README. + stage zlib "https://zlib.net/fossils/$ZLIB_VERSION.tar.gz" README + stage taglib "http://taglib.github.io/releases/$TAGLIB_VERSION.tar.gz" + stage libyaml "http://pyyaml.org/download/libyaml/$LIBYAML_VERSION.tar.gz" + stage chromaprint "https://github.com/acoustid/chromaprint/releases/download/v$CHROMAPRINT_VERSION/chromaprint-$CHROMAPRINT_VERSION.tar.gz" + cd "$GITHUB_WORKSPACE" + cp src/3rdparty/kiss_fft130/COPYING LICENSE.kissfft + cp "Essentia Licensing.txt" LICENSE.essentia-third-party + rm -rf ../licences + + - name: Write the smoke test + run: | + cat > riscv64_smoke.py <<'EOF' + import os + import tempfile + + import numpy as np + + import essentia + import essentia.standard as es + import essentia.streaming + from essentia.standard import Chromaprinter, MetadataReader, MonoLoader, YamlInput + + sr = 44100 + t = np.arange(sr * 5) / sr + x = (0.5 * np.sin(2 * np.pi * 440 * t)).astype(np.float32) + spec = es.Spectrum()(es.Windowing(type="hann")(x[:4096])) + peak = np.argmax(spec) * sr / 4096 + assert abs(peak - 440) < sr / 4096, peak + f = os.path.join(tempfile.mkdtemp(), "sine.wav") + es.MonoWriter(filename=f, sampleRate=sr, format="wav")(x) + y = MonoLoader(filename=f, sampleRate=22050)() + assert abs(len(y) - 5 * 22050) < 2048, len(y) + meta = MetadataReader(filename=f)() + assert meta[-2] == sr, meta[-4:] + fp = Chromaprinter(sampleRate=sr)(x) + assert isinstance(fp, str) and fp, fp + print("essentia", essentia.__version__, "ok", fp[:24], meta[-4:]) + EOF + + - name: Build wheels + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + config-file: cibuildwheel.toml + output-dir: wheelhouse/ + only: ${{ matrix.python }}-manylinux_riscv64 + env: + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + CIBW_ENVIRONMENT_LINUX: >- + PROJECT_NAME=essentia + ESSENTIA_PROJECT_NAME=essentia + ESSENTIA_WHEEL_SKIP_3RDPARTY=1 + ESSENTIA_WHEEL_ONLY_PYTHON=1 + PKG_CONFIG_PATH=/usr/local/lib/pkgconfig + CMAKE_POLICY_VERSION_MINIMUM=3.5 + PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + PIP_ONLY_BINARY=numpy + # Upstream's image ships these static dependencies prebuilt; it has no + # riscv64 variant, so build them with upstream's own script. No Gaia: its + # Linux build compiles Qt 5 from source, and the macOS wheels skip it too. + CIBW_BEFORE_ALL_LINUX: >- + git config --global --add safe.directory '*' && + PREFIX=/usr/local packaging/build_3rdparty_static_debian.sh && + python3 waf configure --build-static --static-dependencies --pkg-config-path="${PKG_CONFIG_PATH}" && + python3 waf && + python3 waf install + CIBW_TEST_SOURCES: riscv64_smoke.py + CIBW_TEST_COMMAND: python riscv64_smoke.py + + - name: Check the wheel version, extension and licences + run: | + python3 - wheelhouse/*.whl <<'EOF' + import os, sys, zipfile + version = os.environ["ESSENTIA_VERSION"] + expected = {"AUTHORS", "COPYING.txt", "LICENSE.kissfft", "LICENSE.essentia-third-party"} | { + f"LICENSE.{d}" for d in ("eigen", "fftw", "lame", "ffmpeg", "libsamplerate", "zlib", "taglib", "libyaml", "chromaprint") + } + for whl in sys.argv[1:]: + assert os.path.basename(whl).startswith(f"essentia-{version}-"), whl + names = zipfile.ZipFile(whl).namelist() + assert any(n.startswith("essentia/_essentia.") and n.endswith(".so") for n in names), whl + licences = {n.rsplit("/", 1)[1] for n in names if ".dist-info/licenses/" in n} - {""} + assert licences == expected, (whl, sorted(licences ^ expected)) + print(whl, "ok") + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: essentia-${{ env.ESSENTIA_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: ./wheelhouse/*.whl + if-no-files-found: error + + gpl_sources: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + name: Collect GPL sources for essentia ${{ matrix.version }} + runs-on: ubuntu-latest + + env: + ESSENTIA_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout essentia ${{ env.ESSENTIA_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: MTG/essentia + ref: ${{ env.ESSENTIA_REF }} + path: essentia + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Download the essentia and third-party library sources + run: | + mkdir sources + git -C essentia archive --prefix="essentia-${ESSENTIA_VERSION}/" -o "$PWD/sources/essentia-${ESSENTIA_VERSION}.tar.gz" HEAD + cp -r "python-wheels/patches/essentia/${ESSENTIA_VERSION}" sources/essentia-patches + . essentia/packaging/build_config.sh + cd sources + fetch() { curl -fsSL --retry 5 "$2" -o "$1"; } + fetch "eigen-$EIGEN_VERSION.tar.gz" "https://gitlab.com/libeigen/eigen/-/archive/$EIGEN_VERSION/eigen-$EIGEN_VERSION.tar.gz" + fetch "$FFTW_VERSION.tar.gz" "http://www.fftw.org/$FFTW_VERSION.tar.gz" + fetch "lame-$LAME_VERSION.tar.gz" "https://sourceforge.net/projects/lame/files/lame/$LAME_VERSION/lame-$LAME_VERSION.tar.gz/download" + fetch "$FFMPEG_VERSION.tar.gz" "https://ffmpeg.org/releases/$FFMPEG_VERSION.tar.gz" + fetch "$LIBSAMPLERATE_VERSION.tar.gz" "https://github.com/libsndfile/libsamplerate/releases/download/0.1.9/$LIBSAMPLERATE_VERSION.tar.gz" + fetch "$ZLIB_VERSION.tar.gz" "https://zlib.net/fossils/$ZLIB_VERSION.tar.gz" + fetch "$TAGLIB_VERSION.tar.gz" "http://taglib.github.io/releases/$TAGLIB_VERSION.tar.gz" + fetch "$LIBYAML_VERSION.tar.gz" "http://pyyaml.org/download/libyaml/$LIBYAML_VERSION.tar.gz" + fetch "chromaprint-$CHROMAPRINT_VERSION.tar.gz" "https://github.com/acoustid/chromaprint/releases/download/v$CHROMAPRINT_VERSION/chromaprint-$CHROMAPRINT_VERSION.tar.gz" + cd .. + tar cf gpl-sources.tar -C sources . + tar tvf gpl-sources.tar + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: essentia-${{ matrix.version }}-gpl-sources + path: gpl-sources.tar + if-no-files-found: error + + publish: + name: Publish essentia ${{ matrix.version }} + needs: [setup, build_wheels, gpl_sources] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + secrets: + app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }} + with: + artifact-pattern: essentia-${{ matrix.version }}-*-manylinux_riscv64 + gpl-sources-artifact: essentia-${{ matrix.version }}-gpl-sources + gpl-sources-description: Essentia (AGPL-3.0) with the statically linked FFTW (GPL-2.0), FFmpeg, LAME, TagLib, Chromaprint, libsamplerate, libyaml, zlib and Eigen sources diff --git a/docs/packages/essentia.yaml b/docs/packages/essentia.yaml new file mode 100644 index 000000000000..47a6d5561ef6 --- /dev/null +++ b/docs/packages/essentia.yaml @@ -0,0 +1,13 @@ +package-name: essentia +source-code: https://github.com/MTG/essentia +license: AGPL-3.0-only +versions: +- version: 2.1b6.dev1438 + patched: true + comment: >- + Built without Gaia (no GaiaTransform), as upstream's macOS wheels are: upstream's Linux + Gaia build compiles Qt 5 from source. The static third-party libraries (FFTW, FFmpeg, + LAME, TagLib, Chromaprint, libsamplerate, libyaml, zlib, Eigen) are built with upstream's + own packaging scripts; the patches drop FFTW's x86-only SSE2 configure flags, refresh + the stale config.guess/config.sub of the older autotools tarballs, and drop TagLib 1.11.1's + cmake_policy(SET CMP0022 OLD) call, which CMake 4 rejects (backported from TagLib 1.12). diff --git a/patches/essentia/2.1b6.dev1438/0001-Only-pass-FFTW-s-x86-SIMD-configure-flags-on-x86-hos.patch b/patches/essentia/2.1b6.dev1438/0001-Only-pass-FFTW-s-x86-SIMD-configure-flags-on-x86-hos.patch new file mode 100644 index 000000000000..cb23a83d479c --- /dev/null +++ b/patches/essentia/2.1b6.dev1438/0001-Only-pass-FFTW-s-x86-SIMD-configure-flags-on-x86-hos.patch @@ -0,0 +1,46 @@ +From 6045cee15d84ffbe220450d8e8f9462a71dd8cf6 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Tue, 13 Oct 2026 18:44:25 +0000 +Subject: [PATCH 1/2] Only pass FFTW's x86 SIMD configure flags on x86 hosts + +packaging/build_config.sh passes --enable-sse2 and +--with-incoming-stack-boundary=2 to FFTW's configure unconditionally. +Both are x86-only: on any other architecture FFTW's configure probes the +compiler for -msse2, fails, and aborts with "Need a version of gcc with +-msse2", so build_3rdparty_static_debian.sh cannot build FFTW on +aarch64, riscv64 or any non-x86 Linux host. Keep the two flags for +i?86/x86_64 build machines (which also covers the mingw cross build) and +drop them elsewhere, leaving FFTW's portable scalar codelets. + +Upstream-Status: To upstream [not yet submitted; upstream publishes no non-x86 Linux wheel, so the gap only shows when the static 3rdparty build runs on another architecture] + +Signed-off-by: Ludovic Henry +--- + packaging/build_config.sh | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/packaging/build_config.sh b/packaging/build_config.sh +index d193130a..25604638 100755 +--- a/packaging/build_config.sh ++++ b/packaging/build_config.sh +@@ -205,10 +205,16 @@ FFMPEG_AUDIO_FLAGS_MUXERS=" + # see http://www.fftw.org/install/windows.html + FFTW_FLAGS=" + --enable-float ++ --with-our-malloc16 ++" ++case "$(uname -m)" in ++ i?86|x86_64) ++ FFTW_FLAGS="$FFTW_FLAGS + --enable-sse2 + --with-incoming-stack-boundary=2 +- --with-our-malloc16 + " ++ ;; ++esac + + LIBSAMPLERATE_FLAGS=" + --disable-fftw +-- +2.43.0 + diff --git a/patches/essentia/2.1b6.dev1438/0002-Refresh-config.guess-config.sub-for-the-autotools-3r.patch b/patches/essentia/2.1b6.dev1438/0002-Refresh-config.guess-config.sub-for-the-autotools-3r.patch new file mode 100644 index 000000000000..2cca166ede8f --- /dev/null +++ b/patches/essentia/2.1b6.dev1438/0002-Refresh-config.guess-config.sub-for-the-autotools-3r.patch @@ -0,0 +1,74 @@ +From 01ac25a8e00178fc49ea949ef646c3e9807a8288 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Tue, 13 Oct 2026 18:44:31 +0000 +Subject: [PATCH 2/2] Refresh config.guess/config.sub for the autotools + 3rdparty builds + +The FFTW 3.3.2, libyaml 0.1.5, libsamplerate 0.1.9 and LAME 3.100 +release tarballs ship config.guess/config.sub copies dated 2009-2015, +which predate the riscv64 triplet, so their configure scripts stop with +"cannot guess build type; you must specify one". Passing --build does +not help because the same stale config.sub rejects the triplet. Copy the +build image's automake copies over them before running configure. + +Upstream-Status: Inappropriate [relies on the build image shipping a current automake under /usr/share; only needed to build these old tarballs on riscv64] + +Signed-off-by: Ludovic Henry +--- + packaging/debian_3rdparty/build_fftw3.sh | 1 + + packaging/debian_3rdparty/build_lame.sh | 1 + + packaging/debian_3rdparty/build_libsamplerate.sh | 1 + + packaging/debian_3rdparty/build_yaml.sh | 1 + + 4 files changed, 4 insertions(+) + +diff --git a/packaging/debian_3rdparty/build_fftw3.sh b/packaging/debian_3rdparty/build_fftw3.sh +index 846101e1..8f46f5ac 100755 +--- a/packaging/debian_3rdparty/build_fftw3.sh ++++ b/packaging/debian_3rdparty/build_fftw3.sh +@@ -11,6 +11,7 @@ echo "Building fftw $FFTW_VERSION" + curl -SLO http://www.fftw.org/$FFTW_VERSION.tar.gz + tar -xf $FFTW_VERSION.tar.gz + cd $FFTW_VERSION ++cp /usr/share/automake-*/config.guess /usr/share/automake-*/config.sub ./ + + CPPFLAGS=-fPIC ./configure \ + --prefix=$PREFIX \ +diff --git a/packaging/debian_3rdparty/build_lame.sh b/packaging/debian_3rdparty/build_lame.sh +index c363dc9d..8915274b 100755 +--- a/packaging/debian_3rdparty/build_lame.sh ++++ b/packaging/debian_3rdparty/build_lame.sh +@@ -12,6 +12,7 @@ echo "Building lame $LAME_VERSION" + curl -SL -o lame-$LAME_VERSION.tar.gz "http://downloads.sourceforge.net/project/lame/lame/$LAME_VERSION/lame-$LAME_VERSION.tar.gz?r=https%3A%2F%2Fsourceforge.net%2Fprojects%2Flame%2F&ts=1476009914&use_mirror=ufpr" + tar -xf lame-$LAME_VERSION.tar.gz + cd lame-$LAME_VERSION ++cp /usr/share/automake-*/config.guess /usr/share/automake-*/config.sub ./ + CPPFLAGS=-fPIC ./configure --prefix=$PREFIX \ + $SHARED_OR_STATIC + make +diff --git a/packaging/debian_3rdparty/build_libsamplerate.sh b/packaging/debian_3rdparty/build_libsamplerate.sh +index ed20edcd..f3e38ad4 100755 +--- a/packaging/debian_3rdparty/build_libsamplerate.sh ++++ b/packaging/debian_3rdparty/build_libsamplerate.sh +@@ -11,6 +11,7 @@ echo "Building libsamplerate $LIBSAMPLERATE_VERSION" + curl -SLO https://github.com/libsndfile/libsamplerate/releases/download/0.1.9/$LIBSAMPLERATE_VERSION.tar.gz + tar -xf $LIBSAMPLERATE_VERSION.tar.gz + cd $LIBSAMPLERATE_VERSION ++cp /usr/share/automake-*/config.guess /usr/share/automake-*/config.sub ./Cfg/ + + CPPFLAGS=-fPIC ./configure \ + --prefix=$PREFIX \ +diff --git a/packaging/debian_3rdparty/build_yaml.sh b/packaging/debian_3rdparty/build_yaml.sh +index 9a87e049..e4cc9a83 100755 +--- a/packaging/debian_3rdparty/build_yaml.sh ++++ b/packaging/debian_3rdparty/build_yaml.sh +@@ -11,6 +11,7 @@ echo "Building libyaml $LIBYAML_VERSION" + curl -SLO http://pyyaml.org/download/libyaml/$LIBYAML_VERSION.tar.gz + tar -xf $LIBYAML_VERSION.tar.gz + cd $LIBYAML_VERSION ++cp /usr/share/automake-*/config.guess /usr/share/automake-*/config.sub ./config/ + + # fails to compile shared library, building only static + CFLAGS="-DYAML_DECLARE_STATIC -fPIC" ./configure \ +-- +2.43.0 + diff --git a/patches/essentia/2.1b6.dev1438/0003-Drop-TagLib-s-CMP0022-OLD-policy-so-it-configures-wi.patch b/patches/essentia/2.1b6.dev1438/0003-Drop-TagLib-s-CMP0022-OLD-policy-so-it-configures-wi.patch new file mode 100644 index 000000000000..7faf4e0285b6 --- /dev/null +++ b/patches/essentia/2.1b6.dev1438/0003-Drop-TagLib-s-CMP0022-OLD-policy-so-it-configures-wi.patch @@ -0,0 +1,41 @@ +From f0239c0c66f2c82ce42c8e98053f6a407e25f11d Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sat, 24 Oct 2026 01:38:21 +0000 +Subject: [PATCH 3/3] Drop TagLib's CMP0022 OLD policy so it configures with + CMake 4 + +TagLib 1.11.1's top-level CMakeLists.txt calls +cmake_policy(SET CMP0022 OLD). CMake 4 removed support for the OLD +behaviour of every policy introduced before CMake 3.5, so configure +stops with "Policy CMP0022 may not be set to OLD behavior because this +version of CMake no longer supports it", and +CMAKE_POLICY_VERSION_MINIMUM cannot override an explicit +cmake_policy(SET ... OLD). TagLib itself dropped the call in 1.12 +(taglib/taglib@085180e9a477). Delete the line from the extracted +tarball before running cmake. The only property it affected is +LINK_INTERFACE_LIBRARIES on the tag target, which is irrelevant for the +static library built here. + +Upstream-Status: Backport [https://github.com/taglib/taglib/commit/085180e9a477b590cf5fac3e5821825ddad79eb1] + +Signed-off-by: Ludovic Henry +--- + packaging/debian_3rdparty/build_taglib.sh | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/packaging/debian_3rdparty/build_taglib.sh b/packaging/debian_3rdparty/build_taglib.sh +index a132b21..31348e3 100755 +--- a/packaging/debian_3rdparty/build_taglib.sh ++++ b/packaging/debian_3rdparty/build_taglib.sh +@@ -11,6 +11,8 @@ echo "Building taglib $TAGLIB_VERSION" + curl -SLO http://taglib.github.io/releases/$TAGLIB_VERSION.tar.gz + tar -xf $TAGLIB_VERSION.tar.gz + cd $TAGLIB_VERSION/ ++# CMake >= 4 rejects setting CMP0022 to OLD (dropped upstream in taglib 1.12) ++sed -i '/cmake_policy(SET CMP0022 OLD)/d' CMakeLists.txt + + cmake \ + -D CMAKE_CXX_FLAGS="-fPIC" \ +-- +2.43.0 +