Commit Graph

4 Commits

Author SHA1 Message Date
Miro Hrončok
45c711b85e getopt: Fix global macro clobbering with save/restore stack
When calling one getopt-using macro from another,
the macro definitions from the inner macro leaked to the outer one.

rpm.define()/rpm.undefine() operate globally, not scoped to parametric
macro invocations. When a macro using getopt() is called nested inside
another getopt-using macro with overlapping option letters, the inner
cleanup_macros() wipes the outer macro's __pyproject_opt_* values.

Note: RPM's native %define/%undefine have stack-like behavior (pushing
and popping definitions), and we can invoke %define from Lua via
rpm.expand(). However, there is no way to push "not defined" onto
this stack and later pop it. Since getopt treats "not defined" and
"defined to %{nil}" differently (the former means the option was not
passed, the latter means a flag was passed without a value), the
native stacking cannot represent the full state space. We implement
our own save/restore stack on the Lua side instead.

Add a Lua-side save/restore stack: getopt() saves current macro state
before cleanup, and a new restore() function pops and reinstates it.
All getopt-using macros call %{__pyproject_getopt_restore} at the end
of their body.

Assisted-By: Claude Opus 4.6
Assisted-By: Codex
(cherry picked from Fedora commit 83ea9a1a01)
2026-08-19 17:32:22 +02:00
Miro Hrončok
c9e1c24300 getopt: Add %__pyproject_optflag_{short} macros for option forwarding
Defines a ready-to-forward form alongside %__pyproject_opt_{short}:
"-X" for flags, "-X value" for value options. This replaces the verbose
%{?__pyproject_opt_X:-X %{__pyproject_opt_X}} pattern with %{?__pyproject_optflag_X}.

Assisted-By: Claude Opus 4.6
(cherry picked from Fedora commit 9108ee5b74)
2026-08-19 17:32:22 +02:00
Miro Hrončok
b2145641c0 Fix a regression wrt option parsing for macros with backslash-escaped newlines in argument list
When macro arguments span across multiple lines with backslash-escaped newlines, e.g.:

    %{pyproject_buildrequires \
        -x pathops \
        -x autohint \
        -x json \
        -x repacker}

rpm.define() for positional args would fail with:

    Macro %__pyproject_positional_args has empty body
    error: lua script failed: .../pyproject_getopt.lua:252: error defining macro

Because the "-x pathops" would be consumed by value parsing
and all the positional arguments would be just backslash-escaped newlines.

Defining the %__pyproject_positional_args macro to whitespace is not possible,
unless properly quoted.

Filter out line-continuation artifacts (tokens containing only whitespace,
backslashes, and newlines) before parsing, so they cannot be misinterpreted
as option values or positional args. Extract a shared quote_value() function
for safe embedding of values in rpm.define() calls.

Additionally, RPM may embed newlines within tokens when macros use
%{expand:} with literal newlines. Such tokens are split on newlines
before parsing.

The regression impacted parsing of 14 Fedora Rawhide spec files:

- dcm2niix
- fontmake
- fonttools
- libarrow
- python-django5
- python-django6
- python-pydantic-extra-types
- python-pydantic-settings
- python-pylero
- python-pynetdicom
- python-tablib
- python-trimesh
- python-urllib3
- scapy

And all are now fixed.

No other Fedora Rawhide spec files fail to parse due to the long option support
after this fix.

Assisted-By: Claude Opus 4.6
(cherry picked from Fedora commit 2acda15168)
2026-08-19 17:32:22 +02:00
Miro Hrončok
ef9de25d3f Add long option support for all public parametric macros
All public parametric macros now accept long options in addition
to short options. For example, %pyproject_buildrequires --no-runtime
is equivalent to %pyproject_buildrequires -R.

This is implemented via a Lua option parser (pyproject_getopt.lua)
installed to %{_rpmluadir}/fedora/rpm/. All macros use (-) to opt out
of RPM native getopt and call the Lua parser for validation, mutual
exclusion checks, and value extraction.

Other changes:
- Toxenv construction inlined in Lua, replacing pyproject_construct_toxenv.py
- Added --config-settings and --directory long forms to Python argparse
- Renamed --read-pyproject-dependencies to --pyproject-dependencies
  (this was not part of API and now it is, so I picked a shorter name)
- Added test_getopt_consistency.py to assert option mappings match
  across macros.pyproject, Python scripts, and README.md
- Converted ~half of test specs to use long options
- Documented all short/long mappings in README.md
- All Python scripts expose their argparser() functions fer easier consistency asserts

Co-Authored-By: Maxwell G <maxwell@gtmx.me>
Assisted-By: Claude Opus 4.6
(cherry picked from Fedora commit 6d3ad45c08)
2026-08-19 17:32:22 +02:00