#967954 debcargo: generated ranged build-deps result in "BD-uninstallable" on debian buildd network

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
#967954#5
Date:
2020-08-05 19:33:21 UTC
From:
To:
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).

#967954#12
Date:
2020-08-12 12:59:05 UTC
From:
To:
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.

#967954#17
Date:
2020-10-15 18:18:32 UTC
From:
To:
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. ☹

#967954#22
Date:
2020-10-15 18:51:45 UTC
From:
To:
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

#967954#27
Date:
2020-10-15 19:36:08 UTC
From:
To:
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.

#967954#32
Date:
2020-10-15 20:44:00 UTC
From:
To:
(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.

#967954#37
Date:
2020-10-15 22:06:05 UTC
From:
To:
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.

#967954#40
Date:
2020-10-16 06:40:25 UTC
From:
To:
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

#967954#45
Date:
2020-10-26 16:08:03 UTC
From:
To:
 ><--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.

#967954#54
Date:
2025-08-10 09:10:51 UTC
From:
To:
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