#967954 debcargo: generated ranged build-deps result in "BD-uninstallable" on debian buildd network #967954
- Package:
- debcargo
- Source:
- rust-debcargo
- Description:
- Create a Debian package from a Cargo crate
- Submitter:
- Daniel Kahn Gillmor
- Date:
- 2025-08-10 09:23:01 UTC
- Severity:
- normal
Looking at buffered-reader version 0.18.0, upstream lists dependencies
in Cargo.toml including:
[dependencies.flate2]
version = ">= 1.0.1, < 1.0.16"
optional = true
debcargo 2.4.2 converts this to a b-d list:
Build-Depends: […]
librust-flate2-1.0.15+default-dev <!nocheck> |
librust-flate2-1.0.14+default-dev <!nocheck> |
librust-flate2-1.0.13+default-dev <!nocheck> |
librust-flate2-1.0.12+default-dev <!nocheck> |
librust-flate2-1.0.11+default-dev <!nocheck> |
librust-flate2-1.0.10+default-dev <!nocheck> |
librust-flate2-1.0.9+default-dev <!nocheck> |
librust-flate2-1.0.8+default-dev <!nocheck> |
librust-flate2-1.0.7+default-dev <!nocheck> |
librust-flate2-1.0.6+default-dev <!nocheck> |
librust-flate2-1.0.5+default-dev <!nocheck> |
librust-flate2-1.0.4+default-dev <!nocheck> |
librust-flate2-1.0.3+default-dev <!nocheck> |
librust-flate2-1.0.2+default-dev <!nocheck> |
librust-flate2-1.0.1+default-dev <!nocheck>,
[…]
But https://buildd.debian.org/status/package.php?p=rust-buffered-reader
says "BD-uninstallable" for all platforms, for example:
rust-buffered-reader build-depends on missing:
- librust-flate2-1.0.15+default-dev:amd64
note that librust-flate2+rust-backend-dev version 1.0.13-2 is in
unstable, and it has:
Provides: […] librust-flate2-1.0.13+default-dev (= 1.0.13-2)
Clearly, the solver is ignoring all but the first build-depends in an
"or"ed dependency. debcargo probably needs to figure out a way around
this :/
(or, maybe something about the resolver for the build daemon network
needs to be fixed to try subsequent entries in an "or"ed dependency? if
so, feel free to reassign this ticket)
I can work around this in rust-buffered-reader by dropping that
particular constraint (it is only added upstream to keep the MSRV low,
which doesn't matter for debian).
It has been said in the past by the release team that the current autobuilder behaviour of only considering the first option for a build-dependency is by design to improve the determinism of the autobuilding process. I don't think you will persuade them to change it. The proper fix IMO would be to add support for version ranges to dependencies in dpkg, but even if you can get the dpkg developers to agree to do that it would not be a quick solution as any change to dependency metadata takes a long time to trickle down to the many tools used in Debian.
Just to clarify why this is a concern: - this behavior on the buildds is deliberate. It's an explicit feature of sbuild. - it is intended to ensure more-deterministic builds - it is also mentioned in debian-policy, in the first footnote of §7.1: So in practice, any Cargo.toml dependency range that has an upper bound will be converted by debcargo to a disjunction in the Build-Depends. This disjunction will be interpreted by sbuild as "the most recent version before the upper bound". This results in unnecessary FTBFS when the build-dep is in debian, but the most recent version before the upper bound is not yet in debian. I will probably be working around this by patching Cargo.toml files so that the generated disjunction targets the version of the dependency that is in debian unstable, but it's an ugly thing to need to do. I'm at a loss for how to address this, or what debcargo *should* do to make the situation better. ☹
Judging by a conversation on #debian-buildd, that seems to be correct (though there are of course many other ways for non-determinism to potentially sneak in, not least of which are different versions of build-deps). I agree that this seems promising -- we'd then need to have debcargo unwind a bunch of its extensive Provides: tags to take advantage of it, but that doesn't sound too ugly. it's possible that introducing ranges into build-dependencies would be a problem for dpkg for other reasons though. I'm Cc'ing Guillem here to see whether he has any thoughts on the matter. For the purposes of resolving #967954, we really only need to worry about ranged dependencies in the build-depends, not in any of the other dependency fields (though obviously if it's simpler to do ranges across all of them then that's fine too). I don't care so much about how long it takes as long as we're moving in the right direction. If it takes two years, the best to start doing this change is two years ago. the second best time is the present :P
another commentator on #debian-buildd noted that alternative build-depends are considered for backports. So maybe there are grounds for considering alternative build-deps on buildd infra elsewhere if we carve the exception with a clear enough line? I don't know how to do that, though.
(sorry for replying to myself here) Thinking this through further, I think cargo dependency ranges like foo = ">=0.4, < 0.6" do actually already do work in dpkg. You represent them as: Build-Depends: librust-foo-dev (>= 0.4), librust-foo-dev (<< 0.6) That would be the easy/simple way to go, so then of course the question is: why does debcargo use these elaborate Provides: renaming schemes instead of the simple conjunction described above? I think the answer is because debcargo wants to be able to explicitly support a named older version of the library in parallel with the "latest and greatest" -- a "pinned" line of the package. so that librust-foo-dev (maybe version 0.6) can be co-installed with the pinned librust-foo-0.4-dev (version 0.4.2), and still have that build-dependency be satisfied. If there's some other reason, maybe Ximin (in Cc) can clarify it. i think he understands debcargo's design choices and constraints better than anyone. However, this approach isn't going to work on the standard buildd network, anyway, because the first element of the disjunction generated by debcargo is likely to be 0.5: Build-Depends: librust-foo-0.5-dev | librust-foo-0.4-dev and sbuild will ignore the latter element of the disjunction, and the package would FTBFS anyway because the BD is uninstallable. So that doesn't seem to solve the problem anyway, except in the lucky case where the highest element of the range happens to also be the one "pinned" in the current release.
Hm, Guillem pointed me toward https://bugs.debian.org/901827 which identifies non-contiguous ranges of dependencies due to "transitive dependencies on multiple versions of the same package" I don't really understand the distinction (still!), but it seems to me like this is probably not the general case. Rather, it's a corner case that seems to be driving all the rest of the complexity here. It's also pretty troubling that mdbook (the example in #901827) includes three different versions of slab and two different versions of mio. Is this really a desirable outcome? I'm looking for ways to reduce the overall complexity of the system. Seems to me like right now we have a choice between (a) making it impossible to package crates with snarled dependency trees like mdbook, or (b) making it hard (but not impossible) to maintain pretty much all other crates that have more straightforward dependency trees, or (c) implementing ranged dependencies in dpkg. At the moment, we seem to be going with (b), which i'm not very fond of because i tend to work on those particular kinds of packages.
Hi, one possible way around this today, would be if librust-flate2-X-dev would add a "Provides: librust-flate2-dev (= X)" and then you could write: Build-Depends: […] librust-flate2-dev (= 1.0.15+default) <!nocheck> | librust-flate2-dev (= 1.0.14+default) <!nocheck> | librust-flate2-dev (= 1.0.13+default) <!nocheck> | librust-flate2-dev (= 1.0.12+default) <!nocheck> | librust-flate2-dev (= 1.0.11+default) <!nocheck> | librust-flate2-dev (= 1.0.10+default) <!nocheck> | librust-flate2-dev (= 1.0.9+default) <!nocheck> | librust-flate2-dev (= 1.0.8+default) <!nocheck> | librust-flate2-dev (= 1.0.7+default) <!nocheck> | librust-flate2-dev (= 1.0.6+default) <!nocheck> | librust-flate2-dev (= 1.0.5+default) <!nocheck> | librust-flate2-dev (= 1.0.4+default) <!nocheck> | librust-flate2-dev (= 1.0.3+default) <!nocheck> | librust-flate2-dev (= 1.0.2+default) <!nocheck> | librust-flate2-dev (= 1.0.1+default) <!nocheck>, […] sbuild will not reduce an OR dependency to just the first entry if the package names are all equal. Thanks! cheers, josch
><--snip-> > Provides: librust-foo-dev = 0.4.2-1 > > then the conjunctive, versioned Depends would be able to figure out how > to satisfy it. The problem is that once versioned provides get involved librust-foo-dev (>= 0.4), librust-foo-dev (<< 0.6) is not equivalent to librust-foo-dev (>= 0.4, << 0.6) Specifically lets say you have three packages. librust-foo-dev version 0.9.1-1 librust-foo-0.1-dev version 0.1.1-1 provides: librust-foo-dev (= 0.1.1-1) librust-foo-0.4-dev version 0.4.1-1 provides: librust-foo-dev (= 0.4.1-1) Installing (or having already installed) librust-foo-dev and librust-foo-0.1-dev will satisfy "librust-foo-dev (>= 0.4), librust-foo-dev (<< 0.6)" just fine but it is almost-certainly not what you as the developer intended.
close 967954 2.7.7-1 thanks debcargo now no longer generates the problematic unversioned virtual package provides in semver suffixed packages, and can thus generate version ranges as upper and lower bounds on the non-semver-suffixed packages