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-054 — Scission d’air-base-lib : un cœur air-base-core sans i18n pour la fermeture runtime

Statut : Accepté (2026-06-30, ratifié par le BDFL). RFC de correction d’architecture (ADR-015). Companion d’ADR-052 (layering air-runtime couche 1) et d’ADR-050 (cible *-linux-air). Précise ADR-053 (la libc s’appuie sur icu4x) en traçant la frontière de compilation d’icu4x. Rectifie une affirmation erronée de l’en-tête d’air-base-lib (« icu4x … aucune feature std »).

Catégorie : Architecture (layering, fondateur). Toute évolution structurante = RFC.

Contexte

Le constat empirique (sonde de build, étape 6)

L’étape 6 du programme *-linux-air consiste à recâbler rt/ sur l’objet couche 1 air-runtime (ADR-052), donc à builder la fermeture d’air-runtime pour *-linux-air (-Z build-std core+alloc, no_std, nightly). Une sonde (2026-06-30, speedy, x86) a tranché empiriquement la faisabilité longtemps redoutée (couplage air-base-lib → icu4x) :

error[E0463]: can't find crate for `std`
  --> icu_provider-2.2.0/src/lib.rs:198:5
   |
198|     extern crate std;
   = note: the `x86_64-unknown-linux-air` target may not support the standard library

cargo tree -e features donne la chaîne exacte :

air-memory / air-thread / air-process
  → air-base-lib (feature "default")
      → icu_calendar (feature "default")
          → std

Deux faits en découlent :

  1. Ce n’est pas libm. Le build meurt sur la feature std d’icu4x avant d’atteindre la moindre crate à math flottante (calendrical_calculations). libm est donc hors du chemin critique de l’étape 6 ; il ne concerne que le futur port i18n de la libc (ADR-053).
  2. La cause est un couplage de layering : air-memory/air-thread/air-process ne consomment d’air-base-lib qu’AirError (et, pour deux d’entre eux, l’horloge monotone et AirPath) — mais en dépendant d’air-base-lib, ils compilent tout l’i18n (icu_normalizer/casemap/segmenter/ collator/calendar). La fondation runtime traîne la pile Unicode entière pour un type d’erreur.

La rectification d’un postulat erroné

L’en-tête d’air-base-lib/src/lib.rs affirme : « icu4x est no_std + alloc par conception (données compiled_data baked-in, aucune feature std) ». C’est faux : les deps icu du workspace (Cargo.toml, [workspace.dependencies]) n’ont pas default-features = false, donc la feature default d’icu — qui inclut std — est active. Cela passait inaperçu car air-base-lib n’a jamais été compilé que pour des cibles *-linux-gnustd existe (l’extern crate std d’icu résolvait sans bruit). La cible no_std *-linux-air révèle le postulat.

Cartographie du couplage icu (mesurée)

Module air-base-libicu ?Consommé par le runtime ?
error, id, path, encoding, log0error (partout), path (air-process)
timeAirInstant/AirDuration (monotone)0air-thread, air-process
timeAirDateTime/AirCalendar (calendaire)icu_calendarnon
stringAirString/AirLocaleicu_*air-process (API Command)

Le « cœur sans-icu » (error/id/path/time-monotone/encoding/log) est nettement séparable de l’i18n (string + calendaire). Seul accroc : air-process::Command prend arg(&AirString) / env(&AirString, &AirString), ce qui tire AirString (icu).

Décision

D1 — Créer air-base-core (couche 1, no_std, zéro icu)

Extraire d’air-base-lib un cœur sans i18n dans une nouvelle crate crates/air-base-core : error ([AirError], [AirErrorKind], [AirResult], ResultContextExt, absorption d’Errno), id, path ([AirPath], octets), time monotone ([AirInstant], [AirDuration]), encoding (base64/hex), log ([AirLog]). Aucune dépendance icu ; core + alloc + couche 0 (air-sys-types ; air-sys-syscall uniquement pour l’horloge monotone, comme aujourd’hui).

D2 — air-base-lib conserve l’i18n et ré-exporte le cœur (compatibilité)

air-base-lib garde les types i18n : string ([AirString]/[AirLocale] + segmentation/ normalisation), et la partie calendaire de time ([AirDateTime]/[AirCalendar]). Elle dépend de air-base-core + icu4x, et ré-exporte tout air-base-core (pub use air_base_core::…) de sorte que air_base_lib::AirError, air_base_lib::AirPath, etc. continuent de fonctionner à l’identique. Les 7 consommateurs non-runtime (air-config, air-device, air-filesystem, air-crypto, air-socket, air-config-compile, air-base-capi) ne changent pas ; air-base-capi (ABI C) accède toujours à AirError via air_base_lib:: → frontière ABI intacte (ADR-045).

D3 — Le runtime bascule sur air-base-core

air-memory, air-thread, air-process dépendent désormais d’air-base-core (et non plus d’air-base-lib). Leur fermeture ne compile plus aucun code icu ⇒ build *-linux-air débloqué, sans libm pour l’étape 6.

D4 — air-process::Command : args/env en octets (AirPath), plus AirString

Command::arg/args/env prennent désormais &AirPath (octets) au lieu de &AirString. Ce n’est pas un simple contournement du couplage icu : c’est plus correct au sens du Principe 3 (« zéro présomption d’encodage »). Sur Unix, execve(2) prend char *const argv[] / envp[] : des chaînes d’octets terminées NUL, non garanties UTF-8. Représenter args/env en octets est fidèle à la sémantique kernel (doctrine « kernel = bible »). Changement d’API publique d’air-process, au rayon d’impact nul (aucun appelant externe d’air-process::Command à ce jour). Les appelants futurs passeront des octets (AirPath/&[u8]) ; l’i18n ne descend plus dans la couche process.

D5 — time.rs scindé selon la frontière icu

La partie monotone (AirInstant/AirDuration, zéro icu) part dans air-base-core::time ; la partie calendaire (AirDateTime/AirCalendar, icu_calendar) reste dans air-base-lib::time. Les deux familles sont déjà indépendantes (horloge monotone vs mur).

D6 — Frontière de compilation d’icu4x : hors périmètre de cet ADR

Rendre icu4x lui-même compilable en no_std pour *-linux-air (default-features = false sur les deps icu du workspace + sélection des features no_std, puis la question libm) est nécessaire pour la libc (ADR-053) mais pas pour l’étape 6. Cet ADR déplace ce travail dans le chantier libc/ i18n (futur), où il sera traité avec la décision libm (réécriture vs vendoring, ADR-024). En attendant, l’en-tête erroné d’air-base-lib est rectifié (le default d’icu tire std ; le build no_std d’icu reste à faire pour la libc).

Conséquences

  • Étape 6 débloquée : la fermeture air-runtime compile pour *-linux-air sans icu ni libm (à reprouver par la sonde après le split — confirmation empirique attendue).
  • Compatibilité : ré-export D2 ⇒ zéro changement pour les 7 consommateurs non-runtime et l’ABI C.
  • Architecture plus saine : la fondation système (erreur/temps/chemin/id) ne dépend plus de l’i18n ; tout consommateur qui n’a pas besoin d’Unicode cesse de compiler icu (bénéfice au-delà du runtime — temps de build, surface).
  • libm clarifié : confirmé hors du chemin de l’étape 6 ; reste un maillon du port icu4x futur (calendrier + LSTM), tranché plus tard (ADR-024).
  • Couverture : air-base-core est couche 1 ⇒ 100 % lignes/branches (Principe 1), comme air-base-lib aujourd’hui. Les fichiers déplacés gardent leurs tests (unit + property + doctests + fuzz le cas échéant).
  • Dette rectifiée : le postulat « icu sans std » est corrigé dans la doc.

Alternatives rejetées

  • Option B — forcer icu en no_std au niveau workspace (default-features = false + features no_std sur les deps icu). Plus large (affecte le build stable de tous les consommateurs), et recule seulement le mur : on retomberait sur libm (jamais atteint par la sonde). Nécessaire un jour pour la libc, mais disproportionné pour débloquer l’étape 6. Déféré (D6).
  • Isoler le cœur d’AirString (conteneur UTF-8 sans-icu + extension icu pour normalisation/ segmentation), pour garder l’API Command en « chaîne ». Refonte d’AirString plus invasive, et moins correcte que des octets pour args/env (Principe 3). Rejeté au profit de D4.
  • Déplacer l’i18n hors d’air-base-lib (renommer le cœur en air-base-lib, l’i18n en air-base-text). Plus « propre » sur les noms, mais casse tous les consommateurs d’AirString (air_base_lib::AirString disparaîtrait). Rejeté : D1+D2 (extraire le cœur, ré-exporter) obtient le découplage sans breakage.

Mise en œuvre (esquisse, code headless après ratification)

  1. Créer crates/air-base-core ; y déplacer error.rs, id.rs, path.rs, encoding.rs, log.rs, et la partie monotone de time.rs. Tests déplacés avec.
  2. air-base-lib : retirer ces modules, dépendre d’air-base-core, pub use air_base_core::… (ré-export), garder string.rs + partie calendaire de time.rs.
  3. air-memory/air-thread/air-process : dépendance air-base-libair-base-core (imports air_base_lib::air_base_core::).
  4. air-process::Command : AirStringAirPath (args/env) ; adapter tests/doctests.
  5. Couverture 100 % air-base-core + air-base-lib ; barrière 2-arches verte ; DEPENDENCIES.md, docs/SUMMARY.md, registre ADR, INDEX à jour ; mdbook build OK.
  6. Reprouver la sonde : fermeture air-runtime compile pour *-linux-air (carbon x86 + raspi/CI).