This is me floating an idea I had for creating packages that have variable
dependencies based on what features are requested, that doesn't rely on
creating a unique subpackage per feature definition. Any and all feedback,
criticism, corrections, etc. welcome. Please poke as many holes in this as you
can find. (Bonus points if you have an idea for how said hole can be plugged.)
## The problem
IMHO the explosion of feature-subpackages creates distracting amounts of
clutter in the package collection, as well as overly verbose package names
(inserting the feature name into the package name for each feature provided),
when all of those feature packages contain _nothing_ other than unique
`Provides:` and `Requires:` dependencies (no actual files), meaning there'd be
no reason for the subpackages at all if the main package could express the
necessary information.
## An example
Take the `rust-hyper` package in Fedora 44. Like many Rust packages, it
consists of a main package (`rust-hyper-devel`) that contains all of the files
for the package, plus EIGHT subpackages for its various features.
The subpackages look like this:
```console
$ for sub in default server http1 http2 client full capi tracing; \
do name="rust-hyper+$sub-devel"; \
echo "\n$name\n---------------"; \
echo "===== Files"; rpm -ql $name; \
echo "===== Provides"; rpm -q --provides $name; \
echo "===== Requires"; (rpm -q --requires $name | grep -v rpmlib); \
echo; done
rust-hyper+default-devel
---------------
===== Files
/usr/share/cargo/registry/hyper-1.11.0/Cargo.toml
===== Provides
crate(hyper/default) = 1.11.0
rust-hyper+default-devel = 1.11.0-1.fc44
===== Requires
cargo
crate(hyper) = 1.11.0
rust-hyper+server-devel
---------------
===== Files
/usr/share/cargo/registry/hyper-1.11.0/Cargo.toml
===== Provides
crate(hyper/server) = 1.11.0
rust-hyper+server-devel = 1.11.0-1.fc44
===== Requires
(crate(httpdate/default) >= 1.0.0 with crate(httpdate/default) < 2.0.0~)
(crate(pin-project-lite/default) >= 0.2.4 with crate(pin-project-lite/default)
< 0.3.0~)
(crate(smallvec/const_generics) >= 1.12.0 with crate(smallvec/const_generics) <
2.0.0~)
(crate(smallvec/const_new) >= 1.12.0 with crate(smallvec/const_new) < 2.0.0~)
(crate(smallvec/default) >= 1.12.0 with crate(smallvec/default) < 2.0.0~)
cargo
crate(hyper) = 1.11.0
rust-hyper+http1-devel
---------------
===== Files
/usr/share/cargo/registry/hyper-1.11.0/Cargo.toml
===== Provides
crate(hyper/http1) = 1.11.0
rust-hyper+http1-devel = 1.11.0-1.fc44
===== Requires
(crate(atomic-waker/default) >= 1.1.2 with crate(atomic-waker/default) < 2.0.0~)
(crate(futures-channel/default) >= 0.3.0 with crate(futures-channel/default) <
0.4.0~)
(crate(futures-core/default) >= 0.3.31 with crate(futures-core/default) <
0.4.0~)
(crate(httparse/default) >= 1.9.0 with crate(httparse/default) < 2.0.0~)
(crate(itoa/default) >= 1.0.0 with crate(itoa/default) < 2.0.0~)
cargo
crate(hyper) = 1.11.0
rust-hyper+http2-devel
---------------
===== Files
/usr/share/cargo/registry/hyper-1.11.0/Cargo.toml
===== Provides
crate(hyper/http2) = 1.11.0
rust-hyper+http2-devel = 1.11.0-1.fc44
===== Requires
(crate(atomic-waker/default) >= 1.1.2 with crate(atomic-waker/default) < 2.0.0~)
(crate(futures-channel/default) >= 0.3.0 with crate(futures-channel/default) <
0.4.0~)
(crate(futures-core/default) >= 0.3.31 with crate(futures-core/default) <
0.4.0~)
(crate(h2/default) >= 0.4.14 with crate(h2/default) < 0.5.0~)
cargo
crate(hyper) = 1.11.0
rust-hyper+client-devel
---------------
===== Files
/usr/share/cargo/registry/hyper-1.11.0/Cargo.toml
===== Provides
crate(hyper/client) = 1.11.0
rust-hyper+client-devel = 1.11.0-1.fc44
===== Requires
(crate(pin-project-lite/default) >= 0.2.4 with crate(pin-project-lite/default)
< 0.3.0~)
(crate(smallvec/const_generics) >= 1.12.0 with crate(smallvec/const_generics) <
2.0.0~)
(crate(smallvec/const_new) >= 1.12.0 with crate(smallvec/const_new) < 2.0.0~)
(crate(smallvec/default) >= 1.12.0 with crate(smallvec/default) < 2.0.0~)
(crate(want/default) >= 0.3.0 with crate(want/default) < 0.4.0~)
cargo
crate(hyper) = 1.11.0
rust-hyper+full-devel
---------------
===== Files
/usr/share/cargo/registry/hyper-1.11.0/Cargo.toml
===== Provides
crate(hyper/full) = 1.11.0
rust-hyper+full-devel = 1.11.0-1.fc44
===== Requires
cargo
crate(hyper) = 1.11.0
crate(hyper/client) = 1.11.0
crate(hyper/http1) = 1.11.0
crate(hyper/http2) = 1.11.0
crate(hyper/server) = 1.11.0
rust-hyper+capi-devel
---------------
===== Files
/usr/share/cargo/registry/hyper-1.11.0/Cargo.toml
===== Provides
crate(hyper/capi) = 1.11.0
rust-hyper+capi-devel = 1.11.0-1.fc44
===== Requires
cargo
crate(hyper) = 1.11.0
rust-hyper+tracing-devel
---------------
===== Files
/usr/share/cargo/registry/hyper-1.11.0/Cargo.toml
===== Provides
crate(hyper/tracing) = 1.11.0
rust-hyper+tracing-devel = 1.11.0-1.fc44
===== Requires
(crate(tracing) >= 0.1.0 with crate(tracing) < 0.2.0~)
(crate(tracing/std) >= 0.1.0 with crate(tracing/std) < 0.2.0~)
cargo
crate(hyper) = 1.11.0
```
## The proposal
What I would propose, instead of the current glut of subpackages, is a way of
defining dependencies _"for"_ only specific `Provides:`. All of the various
feature `Provides:` (`crate(hyper/server)`, `crate(hyper/default)`, etc.) would
be defined on the main package, instead of in subpackages, and the dependencies
for each `Provides:` would be set as `Requires:` conditional on a specific
`Provides:`.
So, for example, the `.spec` syntax, wrapped for readability, might be (and
this is of course subject to change, it's just an example of one possible
format — also, in reality these `Requires:` would be created by the Rust build
tooling, only the `Provides:` for the feature crates would be defined in the
`.spec` file, in place of the current subpackages):
```spec
Provides: crate(hyper/server) = 1.11.0
Requires: (crate(httpdate/default) >= 1.0.0
with crate(httpdate/default) < 2.0.0~)
for crate(hyper/server)
Requires: (crate(pin-project-lite/default) >= 0.2.4
with crate(pin-project-lite/default) < 0.3.0~)
for crate(hyper/server)
Requires: (crate(smallvec/const_generics) >= 1.12.0
with crate(smallvec/const_generics) < 2.0.0~)
for crate(hyper/server)
Requires: (crate(smallvec/const_new) >= 1.12.0
with crate(smallvec/const_new) < 2.0.0~)
for crate(hyper/server)
Requires: (crate(smallvec/default) >= 1.12.0
with crate(smallvec/default) < 2.0.0~)
for crate(hyper/server)
Provides: crate(hyper/client) = 1.11.0
Requires: (crate(pin-project-lite/default) >= 0.2.4
with crate(pin-project-lite/default) < 0.3.0~)
for crate(hyper/client)
Requires: (crate(smallvec/const_generics) >= 1.12.0
with crate(smallvec/const_generics) < 2.0.0~)
for crate(hyper/client)
Requires: (crate(smallvec/const_new) >= 1.12.0
with crate(smallvec/const_new) < 2.0.0~)
for crate(hyper/client)
Requires: (crate(smallvec/default) >= 1.12.0
with crate(smallvec/default) < 2.0.0~)
for crate(hyper/client)
Requires: (crate(want/default) >= 0.3.0
with crate(want/default) < 0.4.0~)
for crate(hyper/client)
Provides: crate(hyper/full) = 1.11.0
Requires: crate(hyper/client) = 1.11.0 for crate(hyper/full)
Requires: crate(hyper/http1) = 1.11.0 for crate(hyper/full)
Requires: crate(hyper/http2) = 1.11.0 for crate(hyper/full)
Requires: crate(hyper/server) = 1.11.0 for crate(hyper/full)
```
The idea is that, installing the package
`rust-hyper-devel-1.11.0-1.fc44.noarch` either by package name or via its
`crate(hyper)` `Provides:` would install the package files, and pull in the
unconditional `Requires:` (the ones without `for %{some_provide}` conditions)
as dependencies.
Installing, or using as a dependency, one of the
<code>crate(hyper/<var>foo</var>)</code> provides, would also pull in any
`Requires:` listed as <code>for crate(hyper/<var>foo</var>)</code>.
## Open questions
1. Is this remotely workable, from an RPM package definition / database
perspective? (Obviously it would require new features developed for both RPM
and DNF5, but does it seem _possible_ to write that code within the current
codebase / APIs?)
2. Is this remotely implementable, in the Rust or Python build tooling? (Since
the `Provides:` for extras subpackages are autogenerated based on the packaging
metadata, that tooling would need to be able to generate conditional `Requires:
... for` metadata instead.)
3. Are there any feature/extra packages that contain files _other_ than the
`%ghost` copy of their package definition? For example, source files that are
unique to the feature in question?
If so, that would probably torpedo this, but I _**think**_ the answer is
"No". Even if such files could theoretically exist (in terms of the native
package definition), I don't believe our current tooling has any means of
expressing that kind of relationship. The complete set of source files is
included in the main package. (I _believe_. PLEASE correct me if I'm wrong!)
4. Would the conditional `Requires: ... for` dependency definitions be
sufficient for this to work, or would the RPM definition of "installed" need to
change to separately track individual `Provides:`?
Currently if a package is installed, it's assumed that all of its
`Provides:` are available, and all of its `Requires:` are installed (or RPM
flags the package as missing dependencies). But for these packages, having the
physical package installed means that not all `Provides:` will necessarily be
"active", and so not all of their `Requires: ... for` dependencies will
necessarily be installed. A conditional `Requires: ... for %{some_provide}`
only needs to be installed if there's some installed package that `Requires:
%{some_provide}`.
So, would it be sufficient to have the `Requires: ... for` dependencies
only activate when the `Provides:` is activated as a dependency (or an explicit
request, like `sudo dnf install %{some_provide}`), or would the RPM database
need to track _which_ `Provides:` of a given package have been installed, and
which have not? (Or is there some other way of making this work?)
5. Perhaps the most important question, as it makes the others moot if the
answer is no: Would there be any interest in migrating some future Fedora /
RHEL release's Rust, Python, etc. packaging to use these features, instead of
the current glut-o'-subpackages setup?
--
Reply to this email directly or view it on GitHub:
https://github.com/rpm-software-management/rpm/discussions/4298
You are receiving this because you are subscribed to this thread.
Message ID: <rpm-software-management/rpm/repo-discussions/[email protected]>_______________________________________________
Rpm-maint mailing list
[email protected]
https://lists.rpm.org/mailman/listinfo/rpm-maint