Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package sdrangel for openSUSE:Factory checked in at 2026-11-11 16:29:53 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/sdrangel (Old) and /work/SRC/openSUSE:Factory/.sdrangel.new.3759290 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "sdrangel" Wed Nov 11 16:29:53 2026 rev:2 rq:1396856 version:8.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/sdrangel/sdrangel.changes 2026-10-26 10:00:42.919506875 +0100 +++ /work/SRC/openSUSE:Factory/.sdrangel.new.3759290/sdrangel.changes 2026-11-11 16:31:04.931351631 +0100 @@ -1,0 +2,54 @@ +Mon Nov 9 10:55:25 UTC 2026 - Martin Pluskal <[email protected]> + +- Update to version 8.1.0 + New plugins for aero communications + * Add ACARS demodulator channel plugin, decoding various digital + aero messages. + * Add Aircraft feature plugin, collating aircraft related data + from various sources. + Other changes + * Analog TV demodulator: add colour support. + * Record software identification in WAV files. + * Fix Coverity and static-analysis findings and compiler + warnings. + * Fix Flatpak build issue and update faad2 download URL. + * libSGP4 range rate API compatibility. + * Fix MCP tune_gain tool input schema. +- Add pkgconfig(libacars-2) BuildRequires for the new ACARS + demodulator plugin and pkgconfig(libzstd) for the map plugin's + MSFS converter (upstream falls back to a network download + without it, which fails in offline builds). +- Declare the new demodacars plugin's accidentally committed + Windows build objects as other-platform (the Linux build compiles + only the C sources). +- Build the ThirdParty wasm blobs from source instead of + shipping upstream's prebuilts, and drop them from the tarball: + * wasm_splats_bg.wasm from CesiumGS/cesium-wasm-utils + (wasm-splats 0.1.0-alpha.2, Apache-2.0), built offline + with the pinned Rust 1.98 toolchain (wasm32 std compiled + from the matching rust-src) plus a wasm-bindgen CLI + built from the same vendored sources. + * zip-module.wasm from gildas-lormeau/zlib-streams + (BSD-3-Clause, zlib submodule at 5a82f71), built with + system emscripten (upstream make zip_module recipe). + * draco_decoder.wasm from google/draco 1.5.7 (Apache-2.0), + built with system emscripten (upstream Emscripten decoder + recipe); the fresh module's linkage names are adapted to + the older interface the shipped Cesium glue was generated + against (draco-relink.py; symbol-joined bindings, proven by + a decode smoke test with that glue). Backports the + upstream FindPython3 switch (draco-emscripten-python3.patch) + so the build configures with cmake>=4. + * basis_transcoder.wasm from BinomialLLC/basis_universal + 1.15_final (Apache-2.0), built with system emscripten (upstream + webgl/transcoder recipe). The new module needs the new + emscripten runtime, so basis-splice.py swaps the fresh + factory into the vendored worker bundle (outer Cesium + logic untouched; restores the wasmBinaryFile contract), + proven by a worker end-to-end transcode test. Needs + basis-cxx17.patch (system emscripten embind requires + C++17; upstream pins C++11). All four + ThirdParty wasm now build from source. +- Spec cleanup: drop Group tag, sort BuildRequires. + +------------------------------------------------------------------- Old: ---- sdrangel-8.0.0.tar.gz New: ---- basis-check-data.tar.xz basis-cxx17.patch basis-e2e.cjs basis-splice.py basis_universal-1.15_final.tar.gz cesium-wasm-utils-96a2fba.tar.gz draco-1.5.7.tar.gz draco-check.mjs draco-emscripten-python3.patch draco-relink.py rust-src-1.98.1.tar.xz sdrangel-8.1.0.tar.gz splats-check.mjs wasm-bindgen-cli-0.2.100.crate wasm-splats-vendor.tar.xz zlib-5a82f71.tar.gz zlib-streams-ff804b0.tar.gz ----------(New B)---------- New: proven by a worker end-to-end transcode test. Needs basis-cxx17.patch (system emscripten embind requires C++17; upstream pins C++11). All four New: a decode smoke test with that glue). Backports the upstream FindPython3 switch (draco-emscripten-python3.patch) so the build configures with cmake>=4. ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ sdrangel.spec ++++++ --- /var/tmp/diff_new_pack.NYEPhe/_old 2026-11-11 16:31:14.164739047 +0100 +++ /var/tmp/diff_new_pack.NYEPhe/_new 2026-11-11 16:31:14.168739215 +0100 @@ -26,41 +26,113 @@ %bcond_without freedv %bcond_with qt6 Name: sdrangel -Version: 8.0.0 +Version: 8.1.0 Release: 0 Summary: SDR/Analyzer frontend for Airspy, BladeRF, HackRF, RTL-SDR and FunCube License: GPL-3.0-or-later -Group: Productivity/Hamradio/Other URL: https://github.com/f4exb/sdrangel -Source: https://github.com/f4exb/sdrangel/archive/v%{version}.tar.gz#/%{name}-%{version}.tar.gz +# Repacked tarball minus the four rebuilt ThirdParty wasm (all built from +# source in %%build; pristine at https://github.com/f4exb/sdrangel/archive/v8.1.0.tar.gz) +Source: %{name}-%{version}.tar.gz +Source1: https://github.com/CesiumGS/cesium-wasm-utils/archive/96a2fbae7ab1d117dd533fe558f0e061bed6762b.tar.gz#/cesium-wasm-utils-96a2fba.tar.gz +Source2: wasm-splats-vendor.tar.xz +Source3: https://static.crates.io/crates/wasm-bindgen-cli/wasm-bindgen-cli-0.2.100.crate +Source4: rust-src-1.98.1.tar.xz +# Repacked upstream rust-src minus wit-bindgen's prebuilt WASI objects +# (only its sources are read; the wasm32 std is compiled from them) +Source5: zlib-streams-ff804b0.tar.gz +Source6: https://github.com/madler/zlib/archive/5a82f71ed1dfc0bec044d9702463dbdf84ea3b71.tar.gz#/zlib-5a82f71.tar.gz +Source7: splats-check.mjs +Source8: draco-1.5.7.tar.gz +# Repacked upstream draco minus its prebuilt wasm + nested Unity/Maya +# plugin archives (the Emscripten decoder build needs none of them) +Source9: basis_universal-1.15_final.tar.gz +# Repacked upstream 1.15_final tag tarball minus its 3 prebuilt wasm +# (webgl encoder/transcoder/videotest builds; the transcoder is rebuilt below) +Source10: basis-splice.py +# Swaps the source-built factory into the vendored worker bundle (the +# outer Cesium logic is untouched; restores the wasmBinaryFile contract) +Source11: draco-check.mjs +Source12: basis-e2e.cjs +# Worker end-to-end smoke test for the rebuilt basis factory + wasm. +Source13: draco-relink.py +# Adapts the rebuilt draco_decoder.wasm to the linkage interface of the +# shipped Cesium glue (symbol/role-joined names; fails loudly on drift). +Source14: basis-check-data.tar.xz +# Khronos KTX-Software sample transcode input for the %%check worker test. +# PATCH-FIX-UPSTREAM: draco-emscripten-python3.patch -- use FindPython3 module (cmake>=4 removed FindPythonInterp); backport of upstream 8b1f23e04391 + a4365c7935ca +Patch0: draco-emscripten-python3.patch +# PATCH-FIX-OPENSUSE: basis-cxx17.patch -- embind needs C++17 (upstream pins C++11) +Patch1: basis-cxx17.patch +# Repacked upstream archive minus its dist/ prebuilt blobs (rebuilt below) +# and the empty src/zlib submodule placeholder filled from Source6 +# Source-Binary-Exception: wasm-splats-vendor.tar.xz:vendor-full/ring-*/pregenerated/* -- other-platform: Windows-only objects; Linux builds ring from its perlasm sources +# Source-Binary-Exception: wasm-splats-vendor.tar.xz:vendor-full/windows_* -- other-platform: Windows import libraries unused by the Linux build +# Source-Binary-Exception: wasm-splats-vendor.tar.xz:vendor-full/walrus-*/benches/fixtures/* -- test-data: benchmark input parsed by tests, never executed +# Source-Binary-Exception: */plugins/channelrx/demodacars/atn/asn1/*.obj -- other-platform: Windows MSVC objects committed upstream; the Linux build compiles only the *.c files +BuildRequires: binaryen +BuildRequires: cargo1.98 BuildRequires: cmake BuildRequires: doxygen BuildRequires: dsdcc-devel >= 1.9.3 +BuildRequires: emscripten BuildRequires: gcc-c++ BuildRequires: hicolor-icon-theme +BuildRequires: libboost_headers-devel +BuildRequires: limesuite-devel +BuildRequires: nodejs +BuildRequires: patch +BuildRequires: pkgconfig +BuildRequires: python3-ply +BuildRequires: serialdv-devel +BuildRequires: pkgconfig(SoapySDR) +BuildRequires: pkgconfig(alsa) +BuildRequires: pkgconfig(codec2) +BuildRequires: pkgconfig(faad2) +BuildRequires: pkgconfig(fftw3f) +BuildRequires: pkgconfig(flac) +BuildRequires: pkgconfig(libacars-2) +BuildRequires: pkgconfig(libairspy) +BuildRequires: pkgconfig(libairspyhf) +BuildRequires: pkgconfig(libavcodec) +BuildRequires: pkgconfig(libavformat) +BuildRequires: pkgconfig(libavutil) +BuildRequires: pkgconfig(libbladeRF) +BuildRequires: pkgconfig(libhackrf) +BuildRequires: pkgconfig(libiio) +BuildRequires: pkgconfig(liblz4) +BuildRequires: pkgconfig(libpostproc) +BuildRequires: pkgconfig(librtlsdr) +BuildRequires: pkgconfig(libswscale) +# It does not build with libmirisdr from upstream +# https://github.com/f4exb/libmirisdr-4 is needed +#BuildRequires: pkgconfig(libmirisdr) +BuildRequires: pkgconfig(libunwind) +BuildRequires: pkgconfig(libusb-1.0) +BuildRequires: pkgconfig(libxtrx) +BuildRequires: pkgconfig(libzstd) +BuildRequires: pkgconfig(opus) +BuildRequires: pkgconfig(uhd) +Requires: python3-requests %if 0%{with qt6} #BuildRequires: libQt6Gui-private-headers-devel #BuildRequires: qt6-core-private-devel %else BuildRequires: libQt5Gui-private-headers-devel %endif -BuildRequires: libboost_headers-devel -BuildRequires: limesuite-devel -BuildRequires: pkgconfig -BuildRequires: serialdv-devel %if 0%{with qt6} BuildRequires: pkgconfig(Qt6Charts) BuildRequires: pkgconfig(Qt6Core) -BuildRequires: pkgconfig(Qt6Quick) -BuildRequires: pkgconfig(Qt6QuickWidgets) BuildRequires: pkgconfig(Qt6Multimedia) BuildRequires: pkgconfig(Qt6MultimediaWidgets) BuildRequires: pkgconfig(Qt6OpenGL) BuildRequires: pkgconfig(Qt6OpenGLWidgets) BuildRequires: pkgconfig(Qt6Positioning) +BuildRequires: pkgconfig(Qt6Quick) +BuildRequires: pkgconfig(Qt6QuickWidgets) BuildRequires: pkgconfig(Qt6SerialPort) -BuildRequires: pkgconfig(Qt6Svg) BuildRequires: pkgconfig(Qt6StateMachine) +BuildRequires: pkgconfig(Qt6Svg) BuildRequires: pkgconfig(Qt6WebSockets) BuildRequires: pkgconfig(Qt6Widgets) %else @@ -77,33 +149,6 @@ BuildRequires: pkgconfig(Qt5WebSockets) BuildRequires: pkgconfig(Qt5Widgets) %endif -BuildRequires: pkgconfig(SoapySDR) -BuildRequires: pkgconfig(alsa) -BuildRequires: pkgconfig(faad2) -BuildRequires: pkgconfig(codec2) -BuildRequires: pkgconfig(flac) -BuildRequires: pkgconfig(fftw3f) -BuildRequires: pkgconfig(libairspy) -BuildRequires: pkgconfig(libairspyhf) -BuildRequires: pkgconfig(libavcodec) -BuildRequires: pkgconfig(libavformat) -BuildRequires: pkgconfig(libavutil) -BuildRequires: pkgconfig(libbladeRF) -BuildRequires: pkgconfig(libhackrf) -BuildRequires: pkgconfig(libiio) -BuildRequires: pkgconfig(liblz4) -BuildRequires: pkgconfig(libpostproc) -BuildRequires: pkgconfig(librtlsdr) -BuildRequires: pkgconfig(libswscale) -# It does not build with libmirisdr from upstream -# https://github.com/f4exb/libmirisdr-4 is needed -#BuildRequires: pkgconfig(libmirisdr) -BuildRequires: pkgconfig(libunwind) -BuildRequires: pkgconfig(libusb-1.0) -BuildRequires: pkgconfig(libxtrx) -BuildRequires: pkgconfig(opus) -BuildRequires: pkgconfig(uhd) -Requires: python3-requests %if 0%{?suse_version} >= 1550 BuildRequires: pkgconfig(opencv4) %else @@ -125,8 +170,126 @@ %setup -q sed -i 's/\r$//' Readme.md sed -i 's|#!%{_bindir}/env python|#!%{_bindir}/python3|g' swagger/sdrangel/examples/*.py +# Stage the wasm sources (built in %%build to replace the dropped blobs) +mkdir wasm-src wasm-zlib wasm-vendor wasm-cli rust-src-full draco-1.5.7 basis-univ +tar -xzf %{SOURCE1} -C wasm-src --strip-components=1 +tar -xzf %{SOURCE5} -C wasm-zlib --strip-components=1 +mkdir -p wasm-zlib/src/zlib +tar -xzf %{SOURCE6} -C wasm-zlib/src/zlib --strip-components=1 +tar -xJf %{SOURCE2} -C wasm-vendor --strip-components=1 +tar -xzf %{SOURCE3} -C wasm-cli --strip-components=1 +tar -xJf %{SOURCE4} -C rust-src-full +tar -xzf %{SOURCE8} -C draco-1.5.7 --strip-components=1 +tar -xzf %{SOURCE9} -C basis-univ --strip-components=1 +# Upstream backport: FindPythonInterp is gone in cmake>=4 +patch -p1 -s --no-backup-if-mismatch < %{PATCH0} +# System emscripten embind needs C++17 (upstream pins C++11) +patch -d basis-univ -p1 -s --no-backup-if-mismatch < %{PATCH1} %build +# Rebuild the four dropped ThirdParty wasm blobs from source in place +# of the prebuilt ones +cd %{_builddir}/%{name}-%{version} +export CARGO_NET_OFFLINE=true RUSTC_BOOTSTRAP=1 +export CARGO_TARGET_DIR=$PWD/wasm-target +WASM_CFG1='source.crates-io.replace-with="vendored-sources"' +WASM_CFG2="source.vendored-sources.directory=\"$PWD/wasm-vendor\"" +# wasm32 std for the pinned toolchain: system rustc ships host std only and +# /usr is read-only, so shadow its sysroot with the matching rust-src. +mkdir -p shadow-sysroot/lib/rustlib/src/rust +cp -r rust-src-full/rust-src-*/rust-src/lib/rustlib/src/rust/library shadow-sysroot/lib/rustlib/src/rust/ +RUSTC_REAL=$(command -v rustc) +REAL_SYSROOT=$($RUSTC_REAL --print sysroot) +export RUSTC_REAL REAL_SYSROOT SHADOW_SYSROOT +SHADOW_SYSROOT=$PWD/shadow-sysroot +# Shadow answers rustc's sysroot probes (read-only /usr); the wrapper also +# adds the forgotten panic-runtime --extern to cdylib builds once built. +cat > rustc-wasm.sh <<'WRAPEOF' +#!/bin/bash +if [ " $* " = " --print sysroot " ] || [ " $* " = " --print=sysroot " ]; then + echo "$SHADOW_SYSROOT" + exit 0 +fi +case " $* " in + *" --print=sysroot "*|*" --print sysroot "*) + set -o pipefail + "$RUSTC_REAL" "$@" | sed "s|^${REAL_SYSROOT}\$|${SHADOW_SYSROOT}|" + exit $? + ;; +esac +case " $* " in + *" --target wasm32-unknown-unknown "*) + case " $* " in + *" --crate-type cdylib "*) + case " $* " in + *" --print "*|*" --crate-name ___ "*) ;; + *) + ABORT=$(ls -t "$CARGO_TARGET_DIR"/wasm32-unknown-unknown/release/deps/libpanic_abort-*.rlib 2>/dev/null | head -1) + [ -n "$ABORT" ] && set -- "$@" --extern "panic_abort=$ABORT" + ;; + esac + ;; + esac + ;; +esac +exec "$RUSTC_REAL" "$@" +WRAPEOF +chmod +x rustc-wasm.sh +export RUSTC=$PWD/rustc-wasm.sh +# wasm_splats_bg.wasm (CesiumGS/cesium-wasm-utils, Apache-2.0): plain cargo +# build plus the era-matching wasm-bindgen CLI from the same vendor tree +cargo build --offline --release -Z build-std --target wasm32-unknown-unknown \ + --manifest-path wasm-src/wasm-splats/Cargo.toml \ + --config "$WASM_CFG1" --config "$WASM_CFG2" +rm -f wasm-cli/Cargo.lock +(cd wasm-cli && cargo build --offline --release \ + --config "$WASM_CFG1" --config "$WASM_CFG2") +wasm-target/release/wasm-bindgen --target web --no-typescript \ + --out-dir wasm-out \ + wasm-target/wasm32-unknown-unknown/release/wasm_splats.wasm +cp wasm-out/wasm_splats_bg.wasm \ + plugins/feature/map/Cesium/ThirdParty/wasm_splats_bg.wasm +# zip-module.wasm (gildas-lormeau/zlib-streams, BSD-3-Clause): upstream recipe +%make_build -C wasm-zlib zip_module EMCC=emcc WASM_OPT=wasm-opt +cp wasm-zlib/dist/zip-module.wasm \ + plugins/feature/map/Cesium/ThirdParty/zip-module.wasm +# draco_decoder.wasm (google/draco 1.5.7, Apache-2.0): upstream Emscripten +# decoder recipe; draco-relink.py adapts its linkage to the shipped glue. +export EMSCRIPTEN=%{_datadir}/emscripten +mkdir draco-build && cd draco-build +# FIXME: you should use the %%cmake macros +emcmake cmake ../draco-1.5.7 -DCMAKE_BUILD_TYPE=Release -DDRACO_WASM=ON +%make_build draco_decoder +cd .. +python3 %{SOURCE13} draco-1.5.7/javascript/npm/draco3d/draco_decoder_nodejs.js \ + draco-build/draco_decoder.js draco-build/draco_decoder.wasm \ + draco-build/draco_decoder.relinked.wasm "a:b,b:c,c:e,d:d,e:a" +# new->old runtime imports: throw->b, fd_write->c, resize_heap->e, +# fd_close->d (decoder opens no fds), abort->a; rest derived in script. +cp draco-build/draco_decoder.relinked.wasm \ + plugins/feature/map/Cesium/ThirdParty/draco_decoder.wasm +# basis (BinomialLLC/basis_universal 1.15_final): upstream webgl/transcoder +# recipe; basis-splice.py swaps the fresh factory into the worker bundle. +mkdir basis-build && cd basis-build +# Upstream declares cmake_minimum_required(VERSION 3.0), refused by cmake>=4 +export CMAKE_POLICY_VERSION_MINIMUM=3.5 +# FIXME: you should use the %%cmake macros +emcmake cmake ../basis-univ/webgl/transcoder -DCMAKE_BUILD_TYPE=Release +%make_build +# Link paths in objects1.rsp are relative to this dir (as in the recipe run) +mkdir -p ../basis-glue +%{_datadir}/emscripten/em++ --bind -s ALLOW_MEMORY_GROWTH=1 -O3 -s ASSERTIONS=0 \ + -s MODULARIZE=1 -s EXPORT_NAME=BASIS -sEXPORTED_RUNTIME_METHODS=HEAP8,HEAPU8 \ + -Wl,--export-all @CMakeFiles/basis_transcoder.js.dir/objects1.rsp \ + -o ../basis-glue/basis_transcoder.js +cd .. +python3 %{SOURCE10} basis-glue/basis_transcoder.js \ + plugins/feature/map/Cesium/Workers/transcodeKTX2.js \ + plugins/feature/map/Cesium/Workers/transcodeKTX2.js.new +mv plugins/feature/map/Cesium/Workers/transcodeKTX2.js.new \ + plugins/feature/map/Cesium/Workers/transcodeKTX2.js +cp basis-glue/basis_transcoder.wasm \ + plugins/feature/map/Cesium/ThirdParty/basis_transcoder.wasm export CXXFLAGS="%{optflags} -Wno-return-type -fpermissive" %cmake \ %if 0%{with qt6} @@ -142,6 +305,26 @@ -DRX_SAMPLE_24BIT=ON %make_jobs +%check +cd %{_builddir}/%{name}-%{version} +# Upstream's own AES-HMAC suite for the rebuilt zip-module.wasm (needs node) +%make_build -C wasm-zlib test_zip_module +# Smoke-test the rebuilt wasm_splats_bg.wasm against verified outputs +node %{SOURCE7} wasm-out +# Draco decode tests with draco's own glue and test data (needs node) +mkdir -p draco-check +cp draco-1.5.7/javascript/npm/draco3d/draco_decoder_nodejs.js draco-1.5.7/javascript/npm/draco3d/bunny.drc draco-check/ +cp draco-1.5.7/testdata/cube_pc.drc draco-1.5.7/testdata/cube_att.drc draco-check/ +cp draco-build/draco_decoder.relinked.wasm draco-check/draco_decoder.wasm +node %{SOURCE11} draco-check +# Basis worker end-to-end: the spliced bundle + rebuilt wasm transcode +# kodim23.ktx2 through the real worker task path (needs node) +mkdir -p basis-check +tar -xf %{SOURCE14} -C basis-check +mv basis-check/test/kodim23.ktx2 basis-check/ && rmdir basis-check/test +cp plugins/feature/map/Cesium/ThirdParty/basis_transcoder.wasm basis-check/ +node %{SOURCE12} plugins/feature/map/Cesium/Workers/transcodeKTX2.js basis-check + %install %cmake_install rm %{buildroot}%{_datadir}/sdrangel/Readme.md ++++++ basis-cxx17.patch ++++++ --- a/webgl/transcoder/CMakeLists.txt +++ b/webgl/transcoder/CMakeLists.txt @@ -15,7 +15,7 @@ message("KTX2_ZSTANDARD=${KTX2_ZSTANDARD}") if (EMSCRIPTEN) - set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD 17) set(SRC_LIST ../../transcoder/basisu_transcoder.cpp ++++++ basis-e2e.cjs ++++++ 'use strict'; // End-to-end smoke test for the source-built basis transcoder inside the // shipped Cesium worker bundle: drives Workers/transcodeKTX2.js through its // real two-message task protocol (init, then KTX2 transcode of kodim23.ktx2) // and checks dimensions and level bytes against verified values. // Usage: node basis-e2e.cjs <bundle-path> <workdir> // <workdir> holds copies of kodim23.ktx2 and basis_transcoder.wasm. const fs = require('node:fs'); const path = require('node:path'); const assert = require('node:assert'); const bundlePath = path.resolve(process.argv[2]); const dir = path.resolve(process.argv[3]); // Test-only shims: the Emscripten factory's node branch reads __dirname and // require (in production it runs in a WebEngine worker, neither is touched). globalThis.require = require; globalThis.__dirname = dir; globalThis.__filename = path.join(dir, 'basis-e2e.cjs'); const posted = []; globalThis.self = {}; globalThis.postMessage = (msg) => { posted.push(msg); }; (async () => { await import(bundlePath); const onmessage = globalThis.self.onmessage; assert.equal(typeof onmessage, 'function'); const ktx2 = new Uint8Array(fs.readFileSync(path.join(dir, 'kodim23.ktx2'))); const wasmFile = path.join(dir, 'basis_transcoder.wasm'); await onmessage({ data: { id: 1, baseUrl: 'http://localhost/', canTransferArrayBuffer: true, parameters: { webAssemblyConfig: { wasmBinaryFile: wasmFile } } } }); await new Promise((r) => setTimeout(r, 500)); assert.equal(posted.length, 1); assert.equal(posted[0].result, true); console.log('basis-e2e: init OK'); await onmessage({ data: { id: 2, baseUrl: 'http://localhost/', canTransferArrayBuffer: true, parameters: { ktx2Buffer: ktx2, supportedTargetFormats: { etc: true } } } }); await new Promise((r) => setTimeout(r, 500)); assert.equal(posted.length, 2); const m = posted[1]; if (m.error) { console.error('WORKER ERROR:', m.error); process.exit(1); } assert.equal(m.result.length, 10); const px = m.result[0].positiveX; console.log('basis-e2e: level0 ' + px.width + 'x' + px.height + ' internalFormat=' + px.internalFormat + ' bufBytes=' + px.levelBuffer.byteLength); assert.equal(px.width, 768); assert.equal(px.height, 512); assert.equal(px.levelBuffer.byteLength, 192 * 128 * 8); console.log('basis-e2e: OK'); })().catch((e) => { console.error('HARNESS FAIL:', e); process.exit(1); }); ++++++ basis-splice.py ++++++ #!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 """Splice a source-built Emscripten basis factory into Cesium's worker bundle. Provenance: - BUNDLE (vendored, Cesium 1.145.0): plugins/feature/map/Cesium/Workers/transcodeKTX2.js The Emscripten MODULARIZE factory blob ("var Pt=(function(){...})();", built by the Cesium team with 2021-era emscripten from BinomialLLC/basis_universal webgl/transcoder/basis_wasm_transcoder.cpp) is byte-replaced with a factory built from source in %build with system emscripten from the SAME upstream C++ file. - The surrounding Cesium worker logic (KTX2 parse, format selection, task wiring) is NOT touched: it only uses the stable embind public API (initializeBasis, KTX2File + methods, transcoder_texture_format), which the replacement provides identically. - The wasm instantiated by the factory is likewise built from source in %build. Usage: basis-splice.py <new-glue.js> <bundle-in.js> <bundle-out.js> <new-glue.js> is the emcc MODULARIZE output (UMD wrapper stripped here). """ import sys START = 'var Pt=(function(){' END = '})();typeof ot' def extract_factory(glue: str) -> str: head = 'var BASIS=' assert glue.startswith(head), 'unexpected glue wrapper' tail = ';if(typeof exports==="object"' i = glue.find(tail) assert i > 0, 'UMD tail anchor not found' expr = glue[len(head):i] assert expr.startswith('(()=>{') and expr.endswith('})()'), 'factory IIFE shape changed' # Cesium always configures this worker with {wasmBinaryFile: url} (see the # initWebAssemblyModule call site in Cesium.js). emscripten >= 4 no longer # reads Module['wasmBinaryFile'], so without help the factory would ignore # the URL and fetch its own baked-in filename next to the worker (404 in # production). Wrap the factory to restore the old contract via the # still-supported Module['locateFile'] hook (default INCOMING_MODULE_JS_API): # when the caller passes wasmBinaryFile and no custom locateFile, every # findWasmBinary() call resolves to that URL. Pure JS, per factory call, # no emcc involvement; the Emscripten factory blob itself is untouched. return ('(()=>{var F=' + expr + ';return function(m){m=m||{};' 'if(m.wasmBinaryFile&&!m.locateFile){var u=m.wasmBinaryFile;' 'm.locateFile=function(){return u}}return F(m)}})()') def main(): glue_p, in_p, out_p = sys.argv[1:4] glue = open(glue_p).read() bundle = open(in_p).read() factory = extract_factory(glue) a = bundle.find(START) assert a >= 0 and bundle.count(START) == 1, 'factory start anchor not unique' e = bundle.find(END) assert e > a, 'factory end anchor missing' e += len('})();') spliced = bundle[:a] + 'var Pt=' + factory + ';' + bundle[e:] # The outer Cesium logic must be byte-identical: only the blob changed. assert bundle[:a] == spliced[:a] assert bundle[e:] == spliced[a + len('var Pt=' + factory + ';'):] open(out_p, 'w').write(spliced) print(f'splice ok: blob {e - a} -> {len(factory) + len("var Pt=;")} bytes') main() ++++++ draco-check.mjs ++++++ // Decode smoke test for the rebuilt draco_decoder.wasm: instantiates draco's // own nodejs glue (the same interface version the shipped Cesium glue was // built against) with the freshly relinked wasm and checks structural output // against values verified with the prebuilt module. Covers both Cesium decode // paths: mesh (GltfDraco) and point cloud (I3S), plus graceful rejection of // corrupt input (Status, never a throw). import { readFileSync } from 'node:fs'; import { createRequire } from 'node:module'; import { strict as assert } from 'node:assert'; import path from 'node:path'; const dir = path.resolve(process.argv[2]); const require = createRequire(import.meta.url); const createDecoderModule = require(path.join(dir, 'draco_decoder_nodejs.js')); const module = await createDecoderModule({}); // Mesh path (bunny.drc, draco's own sample) { const data = new Uint8Array(readFileSync(path.join(dir, 'bunny.drc'))); const decoder = new module.Decoder(); const buf = new module.DecoderBuffer(); buf.Init(data, data.length); assert.equal(decoder.GetEncodedGeometryType(buf), module.TRIANGULAR_MESH); const geom = new module.Mesh(); const status = decoder.DecodeBufferToMesh(buf, geom); assert.ok(status.ok(), 'decode failed'); const faces = geom.num_faces(), points = geom.num_points(); console.log('draco-check: faces=' + faces + ' points=' + points); assert.equal(faces, 69451); assert.equal(points, 34834); const posId = decoder.GetAttributeId(geom, module.POSITION); const pos = decoder.GetAttribute(geom, posId); const tmp = new module.DracoFloat32Array(); decoder.GetAttributeFloatForAllPoints(geom, pos, tmp); const vals = [tmp.GetValue(0), tmp.GetValue(1), tmp.GetValue(2)]; console.log('draco-check: pos0=' + vals.join(',')); const expected = [-0.09055579453706741, 0.14438968896865845, 0.027556151151657104]; for (let i = 0; i < 3; ++i) { assert.ok(Math.abs(vals[i] - expected[i]) < 1e-9, 'pos mismatch at ' + i); } // face indices (the Cesium decodeDraco path) const fi = new module.DracoInt32Array(); assert.ok(decoder.GetFaceFromMesh(geom, 0, fi)); console.log('draco-check: face0=' + [fi.GetValue(0), fi.GetValue(1), fi.GetValue(2)].join(',')); module.destroy(fi); // unique-id lookup (the Cesium compressed-attributes path) const pa2 = decoder.GetAttributeByUniqueId(geom, pos.unique_id()); const tmp2 = new module.DracoFloat32Array(); decoder.GetAttributeFloatForAllPoints(geom, pa2, tmp2); assert.equal(tmp2.GetValue(0), tmp.GetValue(0)); assert.equal(tmp2.GetValue(1), tmp.GetValue(1)); console.log('draco-check: unique-id roundtrip OK'); module.destroy(tmp2); module.destroy(buf); module.destroy(geom); module.destroy(decoder); } // Point-cloud path (cube_pc.drc from draco's testdata) { const data = new Uint8Array(readFileSync(path.join(dir, 'cube_pc.drc'))); const decoder = new module.Decoder(); const buf = new module.DecoderBuffer(); buf.Init(data, data.length); assert.equal(decoder.GetEncodedGeometryType(buf), module.POINT_CLOUD); const geom = new module.PointCloud(); assert.ok(decoder.DecodeBufferToPointCloud(buf, geom).ok()); assert.equal(geom.num_points(), 24); assert.equal(geom.num_attributes(), 3); const pa = decoder.GetAttribute(geom, decoder.GetAttributeId(geom, module.POSITION)); const tmp = new module.DracoFloat32Array(); decoder.GetAttributeFloatForAllPoints(geom, pa, tmp); assert.equal(tmp.size(), 72); const vals = []; for (let i = 0; i < 6; ++i) vals.push(tmp.GetValue(i)); assert.deepEqual(vals, [0, 0, 0, 1, 1, 0]); console.log('draco-check: pointcloud OK (24 pts, pos head 0,0,0,1,1,0)'); module.destroy(buf); module.destroy(geom); module.destroy(decoder); } // Small quantized mesh (cube_att.drc from draco's testdata) { const data = new Uint8Array(readFileSync(path.join(dir, 'cube_att.drc'))); const decoder = new module.Decoder(); const buf = new module.DecoderBuffer(); buf.Init(data, data.length); assert.equal(decoder.GetEncodedGeometryType(buf), module.TRIANGULAR_MESH); const geom = new module.Mesh(); assert.ok(decoder.DecodeBufferToMesh(buf, geom).ok()); assert.equal(geom.num_faces(), 12); assert.equal(geom.num_points(), 24); console.log('draco-check: small mesh OK (12 faces, 24 points)'); module.destroy(buf); module.destroy(geom); module.destroy(decoder); } // Corrupt input must fail gracefully (Status, never a throw) { const decoder = new module.Decoder(); const buf = new module.DecoderBuffer(); buf.Init(new Uint8Array([68, 82, 65, 67, 79, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 16); const geom = new module.Mesh(); assert.ok(!decoder.DecodeBufferToMesh(buf, geom).ok(), 'corrupt input decoded?!'); console.log('draco-check: corrupt input rejected as expected'); module.destroy(buf); module.destroy(geom); module.destroy(decoder); } console.log('draco-check: OK'); ++++++ draco-emscripten-python3.patch ++++++ >From 8b1f23e04391f60f9e2c7566c39d93e53fb54e33 Mon Sep 17 00:00:00 2001 From: upstream google/draco main Date: backported to v1.5.7 Subject: [PATCH] Use FindPython module for Emscripten builds CMake >= 4 removed the FindPythonInterp module, so the Emscripten configure step fails. Switch to find_package(Python COMPONENTS Interpreter) like upstream main does (commits 8b1f23e04391 and the a4365c7935ca follow-up fix). --- a/draco-1.5.7/CMakeLists.txt +++ b/draco-1.5.7/CMakeLists.txt @@ -37,7 +37,7 @@ if("${draco_root}" STREQUAL "${draco_bu "And re-run CMake from the draco_build directory.") endif() -include(FindPythonInterp) +find_package(Python COMPONENTS Interpreter) include("${draco_root}/cmake/draco_build_definitions.cmake") include("${draco_root}/cmake/draco_cpu_detection.cmake") include("${draco_root}/cmake/draco_dependencies.cmake") --- a/draco-1.5.7/cmake/draco_emscripten.cmake +++ b/draco-1.5.7/cmake/draco_emscripten.cmake @@ -18,7 +18,7 @@ endif() # DRACO_CMAKE_DRACO_EMSCRIPTEN_C # Checks environment for Emscripten prerequisites. macro(draco_check_emscripten_environment) - if(NOT PYTHONINTERP_FOUND) + if(NOT Python_Interpreter_FOUND) message( FATAL_ERROR "Python required for Emscripten builds, but cmake cannot find it.") @@ -118,7 +118,7 @@ macro(draco_generate_emscripten_glue) # Generate the glue source. execute_process( - COMMAND ${PYTHON_EXECUTABLE} $ENV{EMSCRIPTEN}/tools/webidl_binder.py + COMMAND ${Python_EXECUTABLE} $ENV{EMSCRIPTEN}/tools/webidl_binder.py ${glue_INPUT_IDL} ${glue_OUTPUT_PATH}) if(NOT EXISTS "${glue_OUTPUT_PATH}.cpp") message(FATAL_ERROR "JS glue generation failed for ${glue_INPUT_IDL}.") @@ -129,7 +129,7 @@ macro(draco_generate_emscripten_glue) # Create a dependency so that it regenerated on edits. add_custom_command( OUTPUT "${glue_OUTPUT_PATH}.cpp" - COMMAND ${PYTHON_EXECUTABLE} $ENV{EMSCRIPTEN}/tools/webidl_binder.py + COMMAND ${Python_EXECUTABLE} $ENV{EMSCRIPTEN}/tools/webidl_binder.py ${glue_INPUT_IDL} ${glue_OUTPUT_PATH} DEPENDS ${draco_js_dec_idl} COMMENT "Generating ${glue_OUTPUT_PATH}.cpp." ++++++ draco-relink.py ++++++ """Interface-adapt a fresh emscripten-6 wasm to the OLD vendored glue. The shipped Cesium glue (pinned in the sdrangel tree) was generated with an old emscripten; its wasm import/export names are version-specific. A rebuild with the current toolchain produces a functionally identical module with different linkage names. This script rewrites the new module's import/export sections so the OLD glue loads it, verified by: - symbol-identity for every bound function (joined on the binding symbol the two glues agree on; sets must match EXACTLY), - role-identity for memory/table/ctors/malloc/free (joined by section kind and by the glue's init protocol, not by name), - explicit semantic mapping for the runtime imports (the only part that needs human judgment; everything else is derived), - dropping every export the old glue never references (asserted), and - a node smoke test with the OLD glue as the behavioral proof. Usage: relink.py OLDGLUE NEWGLUE IN.wasm OUT.wasm IMPORTMAP IMPORTMAP: comma list newimportname:oldimportname (module is "a") Fails loudly on any toolchain drift (changed symbol sets, missing names, unexpected import counts). Deterministic: same inputs -> same bytes. """ import re import sys def read_uleb(buf, pos): r = 0 s = 0 while True: b = buf[pos] pos += 1 r |= (b & 0x7f) << s if not (b & 0x80): return r, pos s += 7 def write_uleb(v): out = bytearray() while True: b = v & 0x7f v >>= 7 if v: out.append(b | 0x80) else: out.append(b) return bytes(out) def read_str(buf, pos): ln, pos = read_uleb(buf, pos) return buf[pos:pos + ln].decode(), pos + ln def split_sections(wasm): parts = [] pos = 8 while pos < len(wasm): sid = wasm[pos] pos += 1 size, pos = read_uleb(wasm, pos) parts.append((sid, bytes(wasm[pos:pos + size]))) pos += size return parts def parse_imports(body): n, p = read_uleb(body, 0) out = [] for _ in range(n): mod, p = read_str(body, p) nm, p = read_str(body, p) kind = body[p] p += 1 start = p if kind == 0: _, p = read_uleb(body, p) elif kind == 1: p += 1 fl = body[p] p += 1 _, p = read_uleb(body, p) if fl & 1: _, p = read_uleb(body, p) elif kind == 2: p += 1 fl = body[p] p += 1 _, p = read_uleb(body, p) if fl & 1: _, p = read_uleb(body, p) elif kind == 3: fl = body[p] p += 1 _, p = read_uleb(body, p) if fl & 1: _, p = read_uleb(body, p) elif kind == 4: p += 2 else: raise AssertionError(f'import kind {kind}') out.append((mod, nm, kind, body[start:p])) assert p == len(body) return out def parse_exports(body): n, p = read_uleb(body, 0) out = [] for _ in range(n): nm, p = read_str(body, p) kind = body[p] p += 1 idx, p = read_uleb(body, p) out.append((nm, kind, idx)) assert p == len(body) return out def old_npm_symbols(src): """{symbol: exportname} from draco3d-npm-style glue (a.asm.X refs).""" m = {} pat1 = (r'a\.(_emscripten_\w+|_[A-Za-z][A-Za-z0-9_]*)\s*=\s*function\(\)' r'\s*\{\s*return\s*\(\s*[A-Za-z$]+\s*=\s*a\.\1\s*=\s*a\.asm\.' r'([A-Za-z0-9_$]+)\s*\)') for x in re.finditer(pat1, src): m[x.group(1)] = x.group(2) pat2 = (r'[\w$]+\s*=\s*function\(\)\s*\{\s*return\s*\(\s*[\w$]+\s*=\s*a\.' r'(_emscripten_\w+|_[A-Za-z][A-Za-z0-9_]*)\s*=\s*a\.asm\.' r'([A-Za-z0-9_$]+)\s*\)') for x in re.finditer(pat2, src): m[x.group(1)] = x.group(2) # direct form: a._malloc=function(){return(a._malloc=a.asm.Tb).apply...} pat3 = (r'a\.(_emscripten_\w+|_[A-Za-z][A-Za-z0-9_]*)\s*=\s*function\(\)' r'\s*\{\s*return\s*\(\s*a\.\1\s*=\s*a\.asm\.' r'([A-Za-z0-9_$]+)\s*\)') for x in re.finditer(pat3, src): m[x.group(1)] = x.group(2) return m def old_npm_direct_refs(src): """Set of bare a.asm.X names (memory/ctors/table slots).""" return set(re.findall(r'a\.asm\.([A-Za-z0-9_$]+)(?![A-Za-z0-9_$])', src)) def new_symbols(src): """{symbol: exportname} from emscripten-6 glue (wasmExports refs).""" m = {} for x in re.finditer(r'(_emscripten_\w+|_[A-Za-z][A-Za-z0-9_]*|__[A-Za-z]\w*)' r'=Module\["\1"\]=wasmExports\["([^"]+)"\]', src): m[x.group(1)] = x.group(2) return m def new_init_ctors(src): """Export name invoked by the new glue's initRuntime (ctors runner).""" m = re.search(r'initRuntime\(\)\{[^}]*?wasmExports\["([^"]+)"\]\(\)', src) assert m, 'no ctors call in new glue initRuntime' return m.group(1) def main(): old_glue_path, new_glue_path, in_wasm, out_wasm = sys.argv[1:5] import_map = {} if len(sys.argv) > 5 and sys.argv[5]: for pair in sys.argv[5].split(','): n, o = pair.split(':') import_map[n] = o old_src = open(old_glue_path).read() new_src = open(new_glue_path).read() old = old_npm_symbols(old_src) new = new_symbols(new_src) # _webidl_* are new-glue-internal malloc aliases: the old glue never # mentions them (proved below), its _malloc/_free mapping covers them. glue_internal = {s for s in new if s not in old} assert all('webidl' in s for s in glue_internal), glue_internal assert 'webidl' not in old_src, 'old glue needs webidl symbols' for s in glue_internal: del new[s] only_old = sorted(set(old) - set(new)) only_new = sorted(set(new) - set(old)) assert not only_old, f'symbols lost in rebuild: {only_old}' assert not only_new, f'symbols added in rebuild: {only_new}' print(f'symbol sets identical: {len(new)} symbols') wasm = bytearray(open(in_wasm, 'rb').read()) assert wasm[:4] == b'\0asm' parts = split_sections(wasm) imp_body = next(b for s, b in parts if s == 2) exp_body = next(b for s, b in parts if s == 7) imports = parse_imports(imp_body) exports = parse_exports(exp_body) # --- import mapping: every module-"a" import must be covered --- new_a_imports = sorted(nm for mod, nm, k, d in imports if mod == 'a') assert set(import_map) == set(new_a_imports), \ f'import map mismatch: map={sorted(import_map)} wasm={new_a_imports}' # --- export mapping --- exp_by_name = {nm: (kind, idx) for nm, kind, idx in exports} old_refs = old_npm_direct_refs(old_src) # memory: unique kind=2 on both sides; old name = the one old glue derefs mems = [nm for nm, k, i in exports if k == 2] assert len(mems) == 1, mems # old memory name by role from the init sequence: # <var>=a.asm.<MEM>; (e.g. ja=a.asm.h;) — unique in the glue. memstmts = re.findall(r'(\w+)=a\.asm\.([A-Za-z0-9_$]+);', old_src) assert len(memstmts) == 1, memstmts old_mem = memstmts[0][1] # ctors: the export queued into the init callbacks cm = re.search(r'(?:unshift|addOnInit)\(a\.asm\.([A-Za-z0-9_$]+)\)', old_src) assert cm, 'old ctors hook not found' old_ctors = cm.group(1) new_ctors = new_init_ctors(new_src) assert new_ctors in exp_by_name, f'ctors {new_ctors} not exported' export_map = {} for sym in new: if new[sym] != old[sym]: export_map[new[sym]] = old[sym] # runtime roles assert old_mem in old_refs, old_mem export_map[mems[0]] = old_mem export_map[new_ctors] = old_ctors # malloc/free by jsname (already in symbol tables via _malloc/_free) # every old-referenced name must resolve, except the one known-dead # alias: Vb is an orphan lazy binding (var ya=...a.asm.Vb, never called # except from the C++ exception-unwind path). Draco reports errors via # Status, never via C++ exceptions, so that path is unreachable; the # corrupt-input smoke test proves graceful Status failure instead. assigned_old = set(export_map.values()) dead = sorted(r for r in old_refs if r not in assigned_old) assert dead == ['Vb'], f'unexpected dead refs: {dead}' # drop unmapped new exports (provably unused: old glue never names them) keep = set(export_map) | {mems[0], new_ctors} new_exp = [(export_map.get(nm, nm), kind, idx) for nm, kind, idx in exports if nm in keep or nm in export_map] # collision check: kept original names must not clash with mapped names final_names = [nm for nm, k, i in new_exp] assert len(set(final_names)) == len(final_names), 'export name collision' assert len(new_exp) == len(keep), (len(new_exp), len(keep)) new_imp = [] for mod, nm, kind, desc in imports: nm2 = import_map.get(nm, nm) if mod == 'a' else nm if mod == 'a': assert nm in import_map, nm new_imp.append((mod, nm2, kind, desc)) def enc_section(entries, is_imp): out = bytearray(write_uleb(len(entries))) for e in entries: if is_imp: mod, nm, kind, desc = e out += write_uleb(len(mod)) + mod.encode() out += write_uleb(len(nm)) + nm.encode() out += bytes([kind]) + desc else: nm, kind, idx = e out += write_uleb(len(nm)) + nm.encode() out += bytes([kind]) + write_uleb(idx) return bytes(out) out = bytearray(wasm[:8]) for sid, body in parts: if sid == 2: body = enc_section(new_imp, True) elif sid == 7: body = enc_section(new_exp, False) out += bytes([sid]) + write_uleb(len(body)) + body open(out_wasm, 'wb').write(out) print(f'relinked: {len(export_map)} exports renamed, ' f'{len(new_exp)}/{len(exports)} exports kept, ' f'{len(new_imp)} imports mapped -> {out_wasm}') if __name__ == '__main__': main() ++++++ sdrangel-8.0.0.tar.gz -> sdrangel-8.1.0.tar.gz ++++++ /work/SRC/openSUSE:Factory/sdrangel/sdrangel-8.0.0.tar.gz /work/SRC/openSUSE:Factory/.sdrangel.new.3759290/sdrangel-8.1.0.tar.gz differ: char 4, line 1 ++++++ splats-check.mjs ++++++ // Smoke-test for the from-source wasm_splats_bg.wasm build: instantiates the // module with its generated JS glue and checks both exported functions against // outputs verified bit-identical with the previously shipped prebuilt module. import { readFileSync } from 'node:fs'; import { pathToFileURL } from 'node:url'; import { strict as assert } from 'node:assert'; const outDir = process.argv[2]; const glue = await import(pathToFileURL(`${outDir}/wasm_splats.js`).href); const bytes = readFileSync(`${outDir}/wasm_splats_bg.wasm`); await glue.default(new Response(bytes, { headers: { 'Content-Type': 'application/wasm' } })); const positions = new Float32Array([0,0,1, 1,2,3, -1,0,2, 0.5,0.5,0.5, 2,2,2]); const scales = new Float32Array([1,1,1, 2,2,2, 0.5,0.5,0.5, 1,2,1, 3,3,3]); const rotations = new Float32Array([0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1]); const colors = new Uint8Array([255,0,0,255, 0,255,0,255, 0,0,255,255, 255,255,0,255, 0,255,255,255]); const modelView = new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]); const tex = glue.generate_splat_texture(positions, scales, rotations, colors, 5); assert.equal(tex.width, 2048); assert.equal(tex.height, 1); assert.deepEqual(Array.from(tex.data.slice(0, 8)), [0, 0, 1065353216, 0, 17408, 1140850688, 1140850688, 4278190335]); const idx = glue.radix_sort_gaussians_indexes(positions, modelView, 5); assert.deepEqual(Array.from(idx), [3, 0, 2, 4, 1]); console.log('splats-check: generate_splat_texture + radix_sort_gaussians_indexes OK');
