[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

rust-*/librust-* arch:any vs. arch:all and cross-compilation support



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 to: