ADR-102 — Crates C-ABI rlib-only : aligner air-value/air-base-capi/air-object sur ADR-029, débloquer les builds release
Statut : Accepté (2026-07-24, décision BDFL). RFC de correction (ADR-015).
Applique le pattern déjà ratifié par ADR-029
(séparation code rlib / artefact .so cdylib), tel qu’implémenté par
air-libc-capi (rlib) + air-libc-c (cdylib host-only). Découvert en produisant
le premier binaire release de la pile air-com/air-sshd (déploiement ADR-100).
Catégorie : Correction de build-system (couche 2 C-ABI). N’altère aucune couche
scellée : change le crate-type de 3 crates + supprime des commentaires erronés.
Contexte — un bug latent systémique
air-value, air-base-capi, air-object déclarent
crate-type = ["cdylib", "rlib"]. Cargo bâtit la cible lib avec toutes ses
crate-types en une invocation rustc (--crate-type cdylib --crate-type rlib).
Or la cdylib de ces crates ne peut pas se lier :
- Elles sont
#![cfg_attr(not(test), no_std)]— en non-test, no_std. - Une cdylib no_std est un artefact final qui exige un
#[global_allocator]et un#[panic_handler]. Un commentaire dansair-valueprétendait les hériter « dustdprésent dans le graphe viaair-base-lib» — c’est faux :air-base-libest lui-même#![no_std]et n’apporte aucun runtime. - En release (
panic = "abort",[profile.release]racine), la compilation échoue : « no global memory allocator found » + «#[panic_handler]required ».
Jamais détecté : la CI ne fait que test/couverture (sous cfg(test) les
crates redeviennent std — allocateur/panic_handler fournis) et repro ne bâtit
que des rlibs. Aucun binaire release de la pile air-com/air-sshd/air-object
n’avait jamais été produit — d’où la découverte tardive (démon air-sshd, ADR-100).
Précédent déjà ratifié — [ADR-029]
air-libc-capi a exactement ce problème et l’a résolu :
crate-type = ["rlib"] (code pur), avec l’artefact partagé libair_c.so dans
la crate sœur air-libc-c (crate-type = ["cdylib"], host-only) qui dépend
du code et force l’export des symboles. Son manifeste le grave : « un crate-type
qui inclut cdylib est droppé par cargo SANS émettre de rlib » sur la cible
freestanding panic = "abort", ce qui casse les consommateurs on-target. Le
combiné ["cdylib", "rlib"] est donc un anti-pattern vis-à-vis d’ADR-029.
Décision
Aligner les 3 crates C-ABI retardataires sur le pattern ADR-029 :
-
air-value,air-base-capi,air-object→crate-type = ["rlib"](code pur, buildable release ET cible freestanding). Les binaires Rust (air-sshd, tests, fuzz) n’en tirent plus qu’une rlib — le build release passe. -
Créer les crates sœurs
-cproduisant les.so:air-value-c,air-object-c(agrègeair-value),air-base-c. Chacune : dépend de la rlib de code et ré-exporte ses symboles#[no_mangle](pub use) — exactementair-libc-capi/air-libc-c. Nécessaire maintenant (pas différé) : les tests de conformité ABI (AIR_ABI_CONFORMANCE_REQUIRED=1en CI) exigent la.so. Deux détails d’implémentation appris à la dure :crate-type = ["cdylib", "rlib"](pascdylibseul) : un test d’intégration ne peut Rust-dépendre d’une crate cdylib-only ⇒cargo testne produirait pas la.so. Lerlibfait builder le lib (donc la.so, même invocationrustc) pour le test. (OK en release : ces crates sontstd.)[lib] namedistinct (air_value_c→libair_value_c.so, etc., pasair_value) : partager le nom de lib avec la crate de code (rlib) casse à la foiscargo test --workspace(la.son’est plus émise) etcargo doc(collision de sortie). Commeair-libc-c(air_c≠air_libc_capi).- Les tests de conformité vivent dans la crate
-c(pas la crate de code) : c’est là quecargo testproduit la.soà lier. Header committé (air_*.h) conservé dans la crate de code, référencé via../air-X/include.
Mécanisme runtime host (le point subtil) : ces cdylibs utilisent
alloc(contrairement àair-libc-c) → une cdylib no_std exigerait un#[global_allocator]+#[panic_handler]absents. Les crates-csont doncstd(host-only) :stdfournit l’allocateur système + le panic handler, la.sose lie en release. Le code (rlib) resteno_stdbuildable partout ; les crates-cne sont jamais buildées pour*-linux-air. -
Corriger les commentaires erronés (
air-value/air-base-capi: la cdylib n’hérite d’aucun runtimestdviaair-base-lib).
Conséquences
- Positif :
cargo build --releasede toute la pileair-com/air-sshd/air-objectfonctionne (déblocage du démonair-sshdoptimisé, ADR-100) ; discipline C-ABI uniforme (une seule façon : code rlib,.soen crate-c) ; cohérence avec la cible freestanding (le combiné cassait déjà on-target). - Aucune perte : les
.solibair_value.so/libair_object.so/libair_base.sosont toujours produites (par les crates-c) et les tests de conformité ABI restent verts. Le header cbindgen et les symboles#[no_mangle]restent dans les crates de code, ré-exportés par les crates-c. - Filet CI : ajouter (hors périmètre strict, recommandé) une vérification que la pile build en release — la CI ne l’exerçait pas ; c’est le trou qui a laissé passer le bug.
Alternatives rejetées
- Runtime host gaté par feature (
#[global_allocator]/#[panic_handler]dans chaque crate, activés par une featurecapi-host) — recrée le couplage feature↔crate-type, diverge d’ADR-029 (air-libc-capia choisi la séparation de crate, pas la feature), et le combiné reste cassé on-target. Rejeté. - Rendre les crates
std— viole#![no_std]couche livrée (ADR-048/Principe 4). - Statu quo + build dev pour le démon — un démon crypto de prod en
debug(asserts, non optimisé) ; refusé (BDFL).