Hi,
I'm writing this mail in order to get further input from knowledgeable, but not
directly involved DDs - mostly those involved with cross-building and
multi-arch matters.
Some background for those not familiar with rust packaging in Debian, skip
below for the actual examples and question.
The debian-rust team uses a rather unified workflow to build a large amount of
rust library crates and a not-quite-as-large amount of binary
crates/applications. Each source package (rust-foo) corresponds to a single
crate (rust package) released on crates.io, building one or more
librust-foo*-dev binary packages that actually contain the crate's (patched)
source code, and in case of a "bin" crate shipping an actual
program/application, a binary package containing the compiled executable +
support files (which can have an arbitrary name - usually just that of the
program in question). The reason that the library packages ship source code in
a binary package is that rust doesn't (properly) support rust->rust dynamic
linking (yet), so each rust executable is statically linking all it's rust
dependencies. This has the side-effect that what are actually (transitive)
build-dependencies are encoded as a mix of build-dependencies (direct) and
regular dependencies (transitive dependencies of direct build-dependencies).
E.g., a crate foo that depends on a crate bar which in turn depends on a crate
baz will have the foo->bar relationship encoded as build-dependency (rust-foo
-> librust-bar-dev) and dependency (librust-foo-dev -> librust-bar-dev, for
packages [build-]depending on crate foo), while the bar->baz dependency will
*only* be encoded as regular dependency (librust-bar-dev -> librust-baz-dev) in
rust-foo's dependency tree.
debcargo, the tool used by the Debian rust team to streamline the work of
transforming upstream crates into Debian source packages generates d/control
entries for librust-* binary packages qualified as arch:any and M-A:same, in
order to support cross compilation. This solution was suggested by dpkg
developers (Guillem?) according to the following comment left in the debcargo
source code:
// This is the best but not ideal option for us.
//
// Currently Debian M-A spec has a deficiency where a package X that
// build-depends on a (M-A:foreign+arch:all) package that itself
// depends on an arch:any package Z, will pick up the BUILD_ARCH of
// package Z instead of the HOST_ARCH. This is because we currently
// have no way of telling dpkg to use HOST_ARCH when checking that
the
// dependencies of Y are satisfied, which is done at install-time
// without any knowledge that we're about to do a cross-compile. It
// is also problematic to tell dpkg to "accept any arch" because of
// the presence of non-M-A:same packages in the archive, that are
not
// co-installable - different arches of Z might be depended-upon by
// two conflicting chains. (dpkg has so far chosen not to add an
// exception for the case where package Z is M-A:same
co-installable).
//
// The recommended work-around for now from the dpkg developers is
to
// make our packages arch:any M-A:same even though this results in
// duplicate packages in the Debian archive. For very large crates
we
// will eventually want to make debcargo generate -data packages
that
// are arch:all and have the arch:any -dev packages depend on it.
https://salsa.debian.org/rust-team/debcargo/-/blob/master/src/debian/control.rs#L342
Some time ago, Jonas (CCed) started packaging rust crates in the same
"namespace" (src:rust-foo building bin:librust-foo-*) using a slightly
different approach, but in a *mostly* compatible fashion. More recently, Jonas
started switching over his rust packages from arch:any, M-A:same (like the rust
team's) to arch:all, M-A:foreign. There was no agreement between the Debian
rust team and Jonas whether this change is problematic and should be reverted
(or not), after a bit of discussion we agreed on getting feedback from people
more involved with cross-building efforts (hence this mail).
One example of a working in practice (as in, the build doesn't fail), but
technically wrong (cross-)build (pulls in dependencies from wrong architecture)
can be found here:
https://paste.debian.net/1270516/
The chain is
rust-lscolors (arch:any) --BD--> librust-tempfile-dev (arch:any)
librust-tempfile-dev (arch:any) --D--> librust-remove-dir-all-0.7+default-dev
(arch:any) --D--> librust-log-0.4+default-dev (arch:any) --D-->
librust-value-bag-1.0.0+serde-dev (arch:any) --D-->
librust-serde-fmt-1+default-dev (arch:all) --D--> librust-serde-dev (arch:any,
but wrongly resolved!)
with librust-serde-fmt-dev being switched to arch:all, it will resolve to
:amd64 instead of :i386 and in turn pull in its own dependencies
librust-serde-1+std-dev & librust-serde-1-dev (both provided by
librust-serde-dev) from amd64 (in addition to librust-serde-dev:i386, which is
also part of the arch:any (build-)dependency tree of rust-lscolors itself,
without the "hop" over librust-serde-fmt-dev).
src:rust-lscolors was taken as minimal example bin crate here - many rust
binary crates are affected by this issue since a few crates which are part of
common dependency chains are maintained by Jonas and recently got switched to
arch:all (serde-fmt and ahash are the two most prominent). Note that much of
the rust ecosystem relies on a crate called bindgen to generate rust bindings
for C libraries, which in turn leverages clang under the hood, which
unfortunately makes those packages currently not cross-compilable because
libclang-common-14-dev is not co-installable on multiple architectures.
Many/most rust-*-sys packages and crates depending on those are practically not
cross-compilable for this reason (in addition to pulling in packages from the
wrong architecture via arch:all, as described above).
Another example would be src:rust-hashbrown, which directly build-depends on
librust-ahash-0.7-dev which got switched to arch:all, cross building it on an
amd64 machine for i386 with
DEB_BUILD_OPTIONS='' sbuild -c debian-unstable-amd64-sbuild -d unstable --host
i386 --profiles cross rust-hashbrown_0.12.3-1.dsc
will pull in all librust-* packages from the wrong architecture (am64 instead
of i386):
$ grep -E '^Get.*librust-serde' rust-hashbrown_0.12.3-1_i386.build
Get:144 http://deb.debian.org/debian unstable/main amd64 librust-cfg-if-dev
amd64 1.0.0-1 [10.4 kB]
Get:145 http://deb.debian.org/debian unstable/main amd64 librust-libc-dev amd64
0.2.139-1 [290 kB]
[ .. snip lots of librust-*-dev amd64 downloads ..]
Get:170 http://deb.debian.org/debian unstable/main amd64
librust-version-check-dev amd64 0.9.4-1 [15.9 kB]
Get:171 http://deb.debian.org/debian unstable/main amd64 librust-ahash-0.7-dev
all 0.7.6-11 [477 kB]
Reverting librust-ahash-0.7-dev to arch:any, M-A:same (and injecting both the
resulting amd64 and i386 package into the build env, to override the arch:all
one from the archive) makes the cross-build correctly only pull in
librust-*-dev packages from i386.
TL;DR: Is the switch to arch:all one that should be reverted in the face of it
apparently breaking cross builds? Or is there another alternative (nowadays)
that makes the "workaround" employed by debcargo no longer needed?
Depending on feedback, the rust team would either ask Jonas to switch back his
packages to arch:any, M-A:same (probably after bookworm, to prevent further
fallout/need for RMs/.. during the freeze), or will evaluate whether switching
to arch:all is an option for debcargo-managed packages as well, and which
changes on the team tooling side are needed to avoid losing test coverage or
increasing friction.
Thanks for reading and any informed input,
Fabian