%global pypi_name navinibs_lsl_output # The importable name, and the directory the NaviNIBS addon loader looks for. %global module NaviNIBS_LSL_Output # The binary RPM name, minus the pythonX- prefix. %global srcname NaviNIBS-LSL-Output Name: python-NaviNIBS-LSL-Output Version: 0.1.0 Release: 1%{?dist} Summary: NaviNIBS addon for streaming data out via Lab Streaming Layer License: LGPL-3.0-only URL: https://github.com/PrecisionNeuroLab/NaviNIBS_LSL_Output Source0: %{pypi_source %{pypi_name} %{version} tar.gz} BuildArch: noarch BuildRequires: python3-devel # pylsl dlopens the native liblsl at import time and python3-pylsl does not # pull it in, so without this %%check fails at "LSL binary library file was not # found". This addon imports pylsl directly (NaviNIBS does too), so the # BuildRequires is not merely inherited from python-NaviNIBS.spec. BuildRequires: liblsl %global _description %{expand: An addon for NaviNIBS that publishes live navigation data -- coil pose, target, and the derived pose metrics -- as Lab Streaming Layer outlets, so that EEG/EMG recorders and other LSL consumers can record them on the same clock as their own data.} %description %_description %package -n python3-%{srcname} Summary: %{summary} %description -n python3-%{srcname} %_description %prep %autosetup -n %{pypi_name}-%{version} # --------------------------------------------------------------------------- # Four problems in the published sdist, all verified against # navinibs_lsl_output-0.1.0.tar.gz as published on PyPI: # # 1. There is no [build-system] table at all, so the build falls back to the # legacy setuptools backend -- and the [project] table upstream does have # contains *only* `classifiers`, with name, version and dependencies left # in [tool.poetry]. Running a build on the extracted sdist gives: # # ValueError: invalid pyproject.toml config: `project`. # configuration error: `project` must contain ['version'] properties # # PEP 621 requires name and version in [project] once [project] exists, so # this cannot build with any setuptools, and it cannot build with poetry # either (see 2). # # 2. [tool.poetry] uses packages = [{ include = "NaviNIBS_LSL_Output", # from = ".." }]. ".." is upstream's development layout, where the addon is # cloned next to a NaviNIBS checkout; inside the extracted sdist it points # outside the source tree, and the module's contents ARE the sdist root # rather than a subdirectory of it. Same defect as # python-NaviNIBS-Simulated-Tools. # # 3. The sdist ships upstream's entire .git directory -- 73 entries, 86 KiB -- # and so does the published wheel, which installs it into site-packages. # # 4. pylsl, the whole point of the addon, is declared only in requirements.txt. # It is absent from [tool.poetry.dependencies], so the wheel metadata # upstream publishes claims navinibs is the only requirement. attrs, numpy # and pytransform3d are undeclared too. # # Plus the requires-python cap, which here is <3.12 -- two interpreter series # below Fedora 44's 3.14. Nothing in the code is 3.11-specific; %%check # import-tests every module under 3.14 and would catch it if it were. # # So the build config is replaced wholesale. There is no minimal edit: the # file has no usable backend, no PEP 621 metadata, and a package path that # points out of the tree. # --------------------------------------------------------------------------- # Fail if upstream has since fixed any of this, rather than reapplying a # rewrite aimed at a file that no longer looks like this. # NOTE on the shell here: these are the "I expect NO match" direction, so they # are written as `if grep -q ...; then fail; fi`. Writing them as # `grep -q X && { exit 1; }` puts the whole build at the mercy of whether # `set -e` fires on the non-matching case, which is the inverted-grep trap. if grep -q '^\[build-system\]' pyproject.toml; then echo "ERROR: pyproject.toml now has a [build-system] table; the %%prep" >&2 echo "rewrite below was written for its absence. Re-check it." >&2 exit 1 fi # And this one is the "I expect a match" direction, so it is negated. It has to # be scoped to the [tool.poetry] section: a bare `grep -q '^name = '` matches # the name *inside* [tool.poetry] and so fired on the very file it was meant to # accept. (It did, on the first mock run.) if ! awk '/^\[tool\.poetry\]/ {p=1; next} /^\[/ {p=0} p && /^name = "NaviNIBS_LSL_Output"/ {found=1} END {exit !found}' pyproject.toml; then echo "ERROR: the project name is no longer declared under [tool.poetry]." >&2 echo "The %%prep rewrite below was written for metadata that lived only" >&2 echo "there, with [project] holding nothing but classifiers. Re-check it." >&2 exit 1 fi rm -rf .git .gitignore test ! -e .git || exit 1 mkdir -p %{module} mv Navigator addon_configuration.json %{module}/ # The addon loader (NaviNIBS/Navigator/Model/Addons.py) opens # /addon_configuration.json and then imports # "%%{module}.Navigator...." by name, so both must end up under a directory # called exactly %%{module}. Assert rather than trust the mv. test -f %{module}/addon_configuration.json || exit 1 test -f %{module}/Navigator/LSLOutputSource.py || exit 1 test -f %{module}/Navigator/Model/LSLOutputConfiguration.py || exit 1 cat > pyproject.toml <<'EOF' [build-system] requires = ["setuptools >= 77"] build-backend = "setuptools.build_meta" [project] name = "navinibs-lsl-output" version = "0.1.0" description = "NaviNIBS addon for streaming data out via LSL" readme = "README.md" license = "LGPL-3.0-only" authors = [ {name = "Christopher C. Cline", email = "chriscline@users.noreply.github.com"}, ] # Upstream caps this at <3.12; see the %%prep note. The floor is upstream's. requires-python = ">=3.10" # navinibs is the only dependency upstream's wheel metadata declares. pylsl is # in requirements.txt only; attrs, numpy and pytransform3d are declared # nowhere. All four are imported by the shipped modules. Listing them here # rather than as bare Requires: means one list feeds both the generated # BuildRequires (%%check has to import them) and the generated Requires. dependencies = [ "navinibs >= 0.1.5", "pylsl", "attrs", "numpy", "pytransform3d", ] [project.urls] Homepage = "https://github.com/PrecisionNeuroLab/NaviNIBS" Repository = "https://github.com/PrecisionNeuroLab/NaviNIBS_LSL_Output" Documentation = "https://precisionneurolab.github.io/navinibs-docs" [tool.setuptools.packages.find] include = ["NaviNIBS_LSL_Output*"] # Nothing in this addon carries an __init__.py -- not the top level, not # Navigator/, not Navigator/Model/. They are implicit namespace packages, # which import fine but are invisible to plain find_packages: without # namespaces=true the wheel comes out with no Python files in it at all and # the build still goes green. %%install asserts the files are there. namespaces = true [tool.setuptools.package-data] "*" = ["*.json"] EOF %generate_buildrequires %pyproject_buildrequires %build %pyproject_wheel %install %pyproject_install %pyproject_save_files -l %{module} # Verify against the installed tree, not the build log. Two modules and the # addon config; see the namespaces note above for how this can come out empty. test -f %{buildroot}%{python3_sitelib}/%{module}/addon_configuration.json || exit 1 test -f %{buildroot}%{python3_sitelib}/%{module}/Navigator/LSLOutputSource.py || exit 1 test -f %{buildroot}%{python3_sitelib}/%{module}/Navigator/Model/LSLOutputConfiguration.py || exit 1 test ! -e %{buildroot}%{python3_sitelib}/%{module}/.git || exit 1 %check # Nothing is excluded here: this addon has no GUI panel, so both of its # modules -- the AddonExtra that owns the LSL outlets (LSLOutputSource) and # the session-attribute model NaviNIBS instantiates from # addon_configuration.json (LSLOutputConfiguration) -- import in a build root. # That exercises pylsl -> liblsl, pytransform3d, attrs and numpy, and it is # also the check that says whether upstream's <3.12 cap has any basis: this # runs on Python 3.14. test -s %{_pyproject_modules} || { echo "ERROR: %{_pyproject_modules} is missing or empty -- there would be" >&2 echo "nothing to import-check, so %%check would pass by testing nothing." >&2 exit 1 } # Both real modules must be in the list. A namespace-package mis-detection # would leave only the directory entries, and %%pyproject_check_import would # report success having imported two empty namespaces. grep -qx '%{module}\.Navigator\.LSLOutputSource' %{_pyproject_modules} || { echo "ERROR: LSLOutputSource is not in the module list." >&2 cat %{_pyproject_modules} >&2 exit 1 } grep -qx '%{module}\.Navigator\.Model\.LSLOutputConfiguration' %{_pyproject_modules} || { echo "ERROR: LSLOutputConfiguration is not in the module list." >&2 cat %{_pyproject_modules} >&2 exit 1 } echo "import-checking $(wc -l < %{_pyproject_modules}) modules:" cat %{_pyproject_modules} %pyproject_check_import %files -n python3-%{srcname} -f %{pyproject_files} %doc README.md %changelog * Sun Sep 27 2026 Morgan Hough - 0.1.0-1 - Initial package. - Replace the build config in %%prep. The published sdist has no [build-system] table and a [project] table holding only `classifiers`, so setuptools refuses it ("`project` must contain ['version'] properties"), and [tool.poetry] points at ".." which is outside the extracted tree. - find_packages needs namespaces=true: this addon ships no __init__.py at any level, so plain discovery produces a wheel with no Python files and a green build. %%install asserts the files are installed. - Strip .git/ (73 entries), which the sdist and the published wheel both carry. - Declare pylsl: upstream has it in requirements.txt only, not in the wheel metadata. Also attrs, numpy and pytransform3d, which are declared nowhere. - Drop the <3.12 requires-python cap; f44 is Python 3.14. %%check import-tests both modules under 3.14, which is what would catch a real incompatibility.