Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 dans air-value prétendait les hériter « du std présent dans le graphe via air-base-lib » — c’est faux : air-base-lib est 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 :

  1. air-value, air-base-capi, air-objectcrate-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.

  2. Créer les crates sœurs -c produisant les .so : air-value-c, air-object-c (agrège air-value), air-base-c. Chacune : dépend de la rlib de code et ré-exporte ses symboles #[no_mangle] (pub use) — exactement air-libc-capi/air-libc-c. Nécessaire maintenant (pas différé) : les tests de conformité ABI (AIR_ABI_CONFORMANCE_REQUIRED=1 en CI) exigent la .so. Deux détails d’implémentation appris à la dure :

    • crate-type = ["cdylib", "rlib"] (pas cdylib seul) : un test d’intégration ne peut Rust-dépendre d’une crate cdylib-onlycargo test ne produirait pas la .so. Le rlib fait builder le lib (donc la .so, même invocation rustc) pour le test. (OK en release : ces crates sont std.)
    • [lib] name distinct (air_value_clibair_value_c.so, etc., pas air_value) : partager le nom de lib avec la crate de code (rlib) casse à la fois cargo test --workspace (la .so n’est plus émise) et cargo doc (collision de sortie). Comme air-libc-c (air_cair_libc_capi).
    • Les tests de conformité vivent dans la crate -c (pas la crate de code) : c’est là que cargo test produit 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 -c sont donc std (host-only) : std fournit l’allocateur système + le panic handler, la .so se lie en release. Le code (rlib) reste no_std buildable partout ; les crates -c ne sont jamais buildées pour *-linux-air.

  3. Corriger les commentaires erronés (air-value/air-base-capi : la cdylib n’hérite d’aucun runtime std via air-base-lib).

Conséquences

  • Positif : cargo build --release de toute la pile air-com/air-sshd/ air-object fonctionne (déblocage du démon air-sshd optimisé, ADR-100) ; discipline C-ABI uniforme (une seule façon : code rlib, .so en crate -c) ; cohérence avec la cible freestanding (le combiné cassait déjà on-target).
  • Aucune perte : les .so libair_value.so/libair_object.so/libair_base.so sont 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 feature capi-host) — recrée le couplage feature↔crate-type, diverge d’ADR-029 (air-libc-capi a 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).

Références

  • ADR-029 — séparation code rlib / artefact .so (pattern air-libc-capi/air-libc-c).
  • ADR-045panic = "abort" à l’ABI C (cause du non-support cdylib en release).
  • ADR-048#![no_std] couche livrée.
  • ADR-100 — déploiement ayant révélé le bug (premier binaire release de la pile).