%global commit 6565932c434827a2e2b103bb4f80e46f449cba40 %global shortcommit %(c=%{commit}; echo ${c:0:7}) %global snapdate 20251104 # CLI shape-analysis tools; strip the debug subpackage to keep it lean. %global debug_package %{nil} Name: SPHARM-PDM Version: 2.0.0 Release: 0.6.%{snapdate}git%{shortcommit}%{?dist} Summary: Spherical harmonic point distribution model shape analysis tools License: Apache-2.0 URL: https://github.com/NIRALUser/SPHARM-PDM Source0: %{url}/archive/%{commit}/%{name}-%{commit}.tar.gz BuildRequires: gcc-c++ BuildRequires: cmake BuildRequires: ninja-build BuildRequires: chrpath BuildRequires: git-core # The three superbuild dependencies — all provided as system packages, so we # disable the (offline-fatal) superbuild and build the inner project directly. BuildRequires: InsightToolkit5-devel BuildRequires: vtk-devel BuildRequires: SlicerExecutionModel-devel # VTK cmake config runs find_package for all its module deps at configure time. BuildRequires: freetype-devel BuildRequires: pugixml-devel BuildRequires: fmt-devel BuildRequires: utf8cpp-devel BuildRequires: PEGTL-devel BuildRequires: jsoncpp-devel BuildRequires: nlohmann-json-devel BuildRequires: hdf5-devel BuildRequires: double-conversion-devel BuildRequires: expat-devel BuildRequires: glew-devel BuildRequires: lz4-devel BuildRequires: libxml2-devel BuildRequires: libtiff-devel BuildRequires: libjpeg-turbo-devel BuildRequires: libpng-devel # Pin the exact VTK ABI this package was built against. VTK freezes its soname at # libvtk*.so.1 across all of 9.x, so rpm cannot tell 9.5 from 9.6 from 9.7: a consumer # built against one installs cleanly against another and fails at LOAD with undefined # symbols. A >= floor does not help -- 9.7.0 satisfies >= 9.6.2 -- so this is an exact # match. %%vtk_abi comes from vtk-devel's macros.vtk, so it re-pins on every rebuild. # # The missing-macro check lives in %%build, deliberately, NOT at parse time. A # parse-time %%error cannot distinguish "COPR is generating the SRPM" from "this is a # real buildroot": rpkg srpm parses the spec with no buildroot and therefore no # vtk-devel, so a parse-time guard killed every SCM build of every guarded package # before any chroot existed (python-simpleitk 10980296 failed in 0 minutes with # "can't parse specfile"). Expanding to UNSET keeps the SRPM parse alive, and the # %%build test still fails loudly in a buildroot lacking vtk-devel, so no RPM carrying # the placeholder can ever be produced. Requires: vtk(abi) = %{?vtk_abi}%{!?vtk_abi:UNSET} # itk(abi): ITK_LEGACY_REMOVE changes class layouts at unchanged sonames, so the itk_ver floor alone let a # consumer built against the removed state install over the present one (python3-simpleitk -11 on ITK -23 # returned all-zero images). Equality on the legacy-state token; see InsightToolkit5.spec. Requires: itk(abi) = %{?itk_abi}%{!?itk_abi:UNSET} %description SPHARM-PDM is a set of command-line tools for spherical-harmonic-based shape analysis. It computes correspondent point-based shape models (PDMs) from binary segmentations by parameterising the surface with spherical harmonics and sampling a corresponding icosahedral mesh, enabling group statistical shape analysis of brain structures and other objects. Part of the NIRAL / SlicerSALT shape-analysis ecosystem. This build uses the system ITK, VTK and SlicerExecutionModel (no superbuild). %prep %autosetup -n %{name}-%{commit} # ITK_LEGACY_REMOVE. Lifted VERBATIM from 3dslicer-spharm-pdm.spec, which is # the same upstream project (NIRALUser/SPHARM-PDM) at a different commit -- # 6565932 here, 522de7b there -- and which took three releases discovering # these three constructs one build at a time. # # This port did NOT repeat that. ci/prep-tdd.sh ran the assertions against this # commit's unpatched source first (RED: 7 + 12 + 1 hits), then against the # patched source (GREEN: zero). The counts match the sibling's exactly, so the # three blocks transfer unchanged. # # Worth recording why the harness earned its keep: my own inventory of this # tarball reported only TWO constructs, because the inline grep pattern left # `()` unescaped and silently matched nothing for ComputeBoundingBox. The RED # step found the third before a builder did. # ITK_LEGACY_REMOVE. InsightToolkit5 5.4.7-19 ships with ITK_LEGACY_REMOVE # defined, which deletes the class-scope aliases for the pre-5.4 enum names, so # MeshType::CellsAllocatedDynamicallyCellByCell stopped existing and 7 call # sites here stopped compiling. Nothing in this package changed; the buildroot # did. That is not reversible from our side either: ITK declares # # cmake_dependent_option(ITK_LEGACY_REMOVE "..." OFF "NOT ITK_WRAPPING" ON) # # so enabling ITK_WRAP_PYTHON FORCES it on, and -DITK_LEGACY_SILENT=ON is # forced back off by the same mechanism. python3-itk and the legacy ITK API # cannot both be had from one build. # # The modern spelling is what upstream ITK wants anyway and compiles against # both settings, since the enum class itself is unconditional in # itkCommonEnums.h. Worth sending to NIRAL rather than carrying forever. python3 - <<'PYEOF' import pathlib, re old = re.compile(r"\b(?:Output)?MeshType::CellsAllocatedDynamicallyCellByCell\b") new = "itk::MeshEnums::MeshClassCellsAllocationMethod::CellsAllocatedDynamicallyCellByCell" total = 0 touched = [] for f in sorted(pathlib.Path(".").rglob("*")): if f.suffix not in (".cxx", ".txx", ".h", ".hxx") or not f.is_file(): continue src = f.read_text(encoding="utf-8", errors="surrogateescape") n = len(old.findall(src)) if n: f.write_text(old.sub(new, src), encoding="utf-8", errors="surrogateescape") total += n touched.append("%s (%d)" % (f, n)) assert total == 7, "expected 7 legacy enum uses, replaced %d: %s" % (total, touched) # Zero-residual: a substitution that ran is not the same as one that finished. left = sum(len(old.findall(f.read_text(encoding="utf-8", errors="surrogateescape"))) for f in pathlib.Path(".").rglob("*") if f.suffix in (".cxx", ".txx", ".h", ".hxx") and f.is_file()) assert left == 0, "%d legacy enum uses remain" % left print("ITK legacy enum: replaced %d uses in %d files" % (total, len(touched))) for t in touched: print(" ", t) PYEOF # Second ITK_LEGACY_REMOVE casualty in this package (the first is the mesh # allocation enum above). The cell-geometry enumerators were class-scope aliases # too: CellType::LINE_CELL and friends. Found only after the first fix let the # build get far enough to reach them -- legacy removal does not fail once, it # fails one layer at a time, so a clean build of the first fix proved nothing # about the second. python3 - <<'PYEOF' import pathlib, re pat = re.compile(r"\bCellType::((?:VERTEX|LINE|POLYGON|TRIANGLE)_CELL)\b") exts = (".cxx", ".txx", ".h", ".hxx") total, touched = 0, [] for f in sorted(pathlib.Path(".").rglob("*")): if f.suffix not in exts or not f.is_file(): continue src = f.read_text(encoding="utf-8", errors="surrogateescape") n = len(pat.findall(src)) if n: f.write_text(pat.sub(r"itk::CommonEnums::CellGeometry::\1", src), encoding="utf-8", errors="surrogateescape") total += n touched.append("%s (%d)" % (f, n)) assert total == 12, "expected 12 legacy cell-geometry uses, replaced %d: %s" % (total, touched) left = sum(len(pat.findall(f.read_text(encoding="utf-8", errors="surrogateescape"))) for f in pathlib.Path(".").rglob("*") if f.suffix in exts and f.is_file()) assert left == 0, "%d legacy cell-geometry uses remain" % left print("ITK legacy cell geometry: replaced %d uses in %d files" % (total, len(touched))) PYEOF # THIRD ITK_LEGACY_REMOVE casualty. SpatialObject::ComputeBoundingBox() was a # legacy shim and is gone. # # gcc suggests ComputeMyBoundingBox and that suggestion is WRONG, twice over. # itkSpatialObject.h:669-677 shows what the removed method actually was: # # #if !defined(ITK_LEGACY_REMOVE) # itkLegacyMacro(void ComputeBoundingBox()) # { # this->Update(); /* Update() should be used instead of outdated ComputeBoundingBox() */ # } # # so the behaviour-preserving replacement is Update(), full stop. And # ComputeMyBoundingBox() is declared at :697, under the `protected:` at :688 -- # not callable from this call site at all. Taking the compiler's hint would # either fail to compile or, from inside a derived class, silently compute only # the object's own box and skip the transform/family update Update() performs. # # One call site. The line after it is `this->Update()` on a DIFFERENT object # (this, vs m_CoefsMeshSpatialObject), so it is not redundant and must stay. python3 - <<'PYEOF' import pathlib, re pat = re.compile(r"->ComputeBoundingBox\(\)") exts = (".cxx", ".txx", ".h", ".hxx") total, touched = 0, [] for f in sorted(pathlib.Path(".").rglob("*")): if f.suffix not in exts or not f.is_file(): continue src = f.read_text(encoding="utf-8", errors="surrogateescape") n = len(pat.findall(src)) if n: f.write_text(pat.sub("->Update()", src), encoding="utf-8", errors="surrogateescape") total += n touched.append("%s (%d)" % (f, n)) assert total == 1, "expected 1 legacy ComputeBoundingBox use, replaced %d: %s" % (total, touched) rest = "".join(f.read_text(encoding="utf-8", errors="surrogateescape") for f in pathlib.Path(".").rglob("*") if f.suffix in exts and f.is_file()) assert "ComputeBoundingBox()" not in rest, "legacy ComputeBoundingBox() remains" # Assert no CALL to ComputeMyBoundingBox was introduced. The declaration and # definition are legitimate and must survive: this package OVERRIDES the # protected virtual (SphericalHarmonicSpatialObject.{h:107,cxx:109}). That # override is also why gcc's suggestion was dangerous rather than merely wrong # -- ComputeMyBoundingBox() IS accessible from inside this class, so the hint # would have compiled and silently skipped the transform update Update() does. import re as _re calls = _re.findall(r"[.>]ComputeMyBoundingBox\s*\(", rest) assert not calls, "a CALL to ComputeMyBoundingBox was introduced: %r" % calls print("ITK legacy ComputeBoundingBox -> Update: %d use in %s" % (total, touched)) PYEOF # CMake/CMakeLists.txt does nothing but file(DOWNLOAD) a Windows-only # FindVcvars.cmake from GitHub at configure time (fatal offline). FindVcvars is # never used on Linux — neutralise the whole sub-CMakeLists. echo '# FindVcvars download removed: Windows-only, unused on Linux, offline build' \ > CMake/CMakeLists.txt # GCC 16 rejects this legacy code's unqualified std names (e.g. MeshMath.cxx # uses bare `cout`/`endl`, relying on a `using namespace std` that older # standard headers pulled in transitively). Force-include a small compat header # that brings the std names into scope for every translation unit. cat > std-compat.h <<'EOF' #pragma once #include #include #include #include #include #include #include using namespace std; EOF %build # Pin the ITK layout state this build sees (see the Requires above). test -n "%{?itk_abi}" || { echo "ERROR: itk_abi undefined - needs InsightToolkit5-devel >= 5.4.7-24 (macros.itk)" >&2; exit 1; } # vtk_abi comes from vtk-devel's macros.vtk. Absent means BuildRequires: vtk-devel is # missing, and the Requires above would ship as the vacuous "vtk(abi) = UNSET". test -n "%{?vtk_abi}" || { echo "ERROR: vtk_abi undefined - add 'BuildRequires: vtk-devel'" >&2; exit 1; } export CXXFLAGS="%{optflags} -include ${PWD}/std-compat.h -fpermissive" %cmake -GNinja \ -DCMAKE_BUILD_TYPE=Release \ -DSPHARM-PDM_SUPERBUILD:BOOL=OFF \ -DUSE_SYSTEM_ITK:BOOL=ON \ -DUSE_SYSTEM_VTK:BOOL=ON \ -DUSE_SYSTEM_SlicerExecutionModel:BOOL=ON \ -DBUILD_TESTING:BOOL=OFF \ -DCMAKE_SKIP_INSTALL_RPATH:BOOL=ON %cmake_build %install %cmake_install # This is a CLI-tools package: drop the static dev libraries and headers the # build also installs (the tools are statically linked and self-contained). rm -rf %{buildroot}%{_includedir}/Shape find %{buildroot} -name '*.a' -delete # Strip any residual build-tree RPATHs the SEM macros may leave. find %{buildroot} -type f -perm -111 \ -exec chrpath --delete {} \; 2>/dev/null || : %files %license License.txt %{_bindir}/* %changelog * Tue Oct 06 2026 Morgan Hough - 2.0.0-0.6.20251104git6565932 - Pin the ITK layout state: Requires: itk(abi) = %%{itk_abi}, from InsightToolkit5-devel's macros.itk, plus a %%build guard. ITK -23 flipped ITK_LEGACY_REMOVE back off, which changes class layouts at unchanged sonames; this package was built against -10..-21 (legacy removed) and the itk_ver floor let it install over -23, giving silent wrong results or crashes. Rebuild against ITK >= 5.4.7-24. * Mon Oct 05 2026 Morgan Hough - 2.0.0-0.5.20251104git6565932 - Port 3dslicer-spharm-pdm.spec's three ITK_LEGACY_REMOVE blocks. Same upstream project at a different commit (6565932 here, 522de7b there), and the construct counts match exactly: 7 mesh-allocation enums, 12 cell-geometry enums, 1 SpatialObject::ComputeBoundingBox call. - Surfaced by medInria failing on fedora-rawhide-x86_64, where nothing provides libitkvnl-5.4.so.1: InsightToolkit5 5.4.7-21 moved to the system vxl and ships no bundled vnl. Eight packages on that chroot still link it and every one carries an .fc45 tag -- builds never redone for fc46. Only three are genuinely stale rather than superseded leftovers, and this is one. - Verified with ci/prep-tdd.sh rather than by building: RED on unpatched source (7 + 12 + 1), GREEN after. That caught an error in my own inventory, which reported only two constructs because an inline grep left `()` unescaped and matched nothing for ComputeBoundingBox. The sibling spent three builds finding its three layers serially; this found all three in about thirty seconds. * Sat Sep 19 2026 Morgan Hough - 2.0.0-0.4.git - Rebuild against InsightToolkit5 5.4.7-8, which unbundles Eigen3, VXL and double-conversion. No source or spec change; the Release bump is what makes the rebuild reach anyone. COPR adds a sibling at an already-published NVR rather than replacing it, and dnf then serves the older copy -- so a rebuild at the same NVR would leave this package linked against the bundled ITK while appearing to succeed. - Validated against -8 in mhough/neurofedora-itk5-test before this bump. * Sun Aug 23 2026 Morgan Hough - 2.0.0-0.2.20251104git6565932 - Rebuild against VTK 9.7.0. VTK here installs unversioned sonames (libvtk*.so.1), so 9.7 replaces 9.6 in place and every consumer must be rebuilt or it fails at runtime with undefined symbols. * Mon Jun 15 2026 Morgan Hough - 2.0.0-0.1.20251104git6565932 - Initial package: SPHARM-PDM spherical-harmonic shape-analysis CLI tools (master snapshot 6565932). Built against system ITK/VTK/SlicerExecutionModel with the superbuild disabled (Fedora no-superbuild policy). Complements the FSL FIRST mesh shape-analysis workflow.