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

Reply via email to