Documentation polyglotte : réunir plusieurs générateurs
Découvrez comment réunir Doxygen, rustdoc et Sphinx dans un site cohérent : deux architectures, leurs compromis et une méthode concrète pour décider.

Réunir plusieurs générateurs de documentation sans tout casser
Un projet qui fournit des bibliothèques en C++, Rust et Python ne produit pas sa documentation avec un seul outil. Chaque langage a ses générateurs, ses habitudes et ses utilisateurs. Le vrai souci arrive quand on veut tout rassembler sur un même site : les pages ne suivent pas les mêmes conventions, la recherche ne couvre pas tout, et les liens d'une section à l'autre deviennent vite fragiles.
On pourrait se dire : « convertissons tout vers un générateur unique ». Sur le papier, c'est séduisant. En pratique, on finit souvent par perdre des fonctions utiles, ajouter une couche de « liaison » pénible à maintenir, et ralentir les builds. À l'inverse, publier chaque sortie telle quelle évite de casser les outils d'origine, mais donne parfois l'impression de naviguer entre plusieurs sites indépendants.
Il n'existe pas de solution universelle pour assembler ces contenus. Le « bon » choix dépend de ce que vous essayez d'unifier : le rendu, la navigation, la recherche, ou le contenu lui-même. Voici deux architectures possibles, leurs compromis, et une méthode pragmatique pour garder un site cohérent sans réécrire inutilement vos outils.
Pourquoi les sites de documentation polyglottes sont difficiles à unifier
Quand un projet publie des bibliothèques dans plusieurs langages, il utilise généralement plusieurs générateurs de références d'API. Doxygen est courant pour le C et le C++ ; rustdoc fait partie de l'écosystème Rust ; et les autres langages ont leurs outils. Le site peut aussi s'appuyer sur Sphinx pour les guides, les tutoriels et les pages « éditoriales ».
Ces outils ne produisent pas seulement du HTML différent. Ils embarquent aussi des choix de conception propres à leurs communautés : structure des pages, navigation, recherche, présentation des signatures, et liens vers les définitions dans le code. Une référence d'API n'est donc pas juste une collection de pages que l'on peut « reformater » sans conséquences.
Au fond, le conflit est simple : viser la cohérence du site tout en respectant des outils spécialisés. Pour le lecteur, l'attente est claire : retrouver des repères communs, une recherche unique, et pouvoir passer facilement d'un guide à une référence d'API. Pour l'équipe, c'est plus compliqué : chaque générateur évolue à sa façon, et ne sait pas forcément ce que font les autres.
On parle alors de site de documentation polyglotte : un site qui doit faire cohabiter des sorties de plusieurs générateurs qui, à la base, n'étaient pas pensés pour fonctionner ensemble. Le point clé, c'est de décider où se fait l'unification (ou si elle ne se fait pas du tout).
Trois besoins sont souvent mélangés :
- Uniformiser le contenu : convertir les références d'API dans le format du générateur « principal ».
- Uniformiser l'interface : harmoniser en-tête, couleurs, liens et repères de navigation.
- Uniformiser les services transverses : par exemple, une recherche qui couvre tout.
Ces objectifs ne mènent pas aux mêmes architectures. Vous pouvez centraliser la recherche sans transformer les références d'API. Vous pouvez harmoniser l'en-tête tout en gardant l'interface propre à chaque outil. Et vous pouvez convertir le contenu, mais il faut alors assumer un surcoût : maintenir cette conversion à chaque évolution des générateurs.
La première question utile est donc celle-ci : qu'est-ce qui gêne vraiment les utilisateurs ? S'ils ne retrouvent pas une page parce qu'elle est « perdue » entre sous-sites, une migration complète de contenu a de fortes chances d'être disproportionnée. S'ils bloquent sur un cas où une information ne peut tout simplement pas vivre dans un guide, la conversion peut se justifier - mais pour ce cas précis. Unifier n'est pas forcément fusionner.
Deux architectures, deux compromis
Il y a deux grandes manières de s'en sortir : soit transformer les sorties des générateurs, soit les garder « natives » et relier les sites entre eux. Aucune des deux n'est magique. La bonne architecture est celle dont les coûts restent visibles et contrôlables pour votre équipe.
Transformer les références dans un générateur principal
Ici, le pipeline lit la sortie d'un générateur spécialisé, puis la convertit pour qu'elle soit exploitable par le générateur principal. Celui-ci peut ensuite intégrer les pages d'API dans la même navigation que les guides et la documentation générale.
Un cas classique consiste à combiner Doxygen et Sphinx via l'extension Breathe. Doxygen sort des fichiers XML ; Breathe les interprète et permet d'insérer des références dans des fichiers reStructuredText (avec des directives, par exemple). Concrètement : vous pouvez placer une référence de classe au milieu d'un guide, sans renvoyer le lecteur vers un sous-site.
L'avantage est réel : un système pilote davantage de pages, les choix éditoriaux restent cohérents, et les liens entre contenu « explicatif » et API peuvent devenir plus naturels. C'est aussi particulièrement pertinent quand l'intégration du contenu fait partie du besoin, pas seulement une question de style.
Mais cette conversion crée une couche de traduction. Elle doit comprendre suffisamment la sortie d'origine pour ne pas perdre des informations importantes. Or rustdoc et Doxygen connaissent leurs langages mieux que toute transformation générique. En simplifiant, vous risquez de supprimer des détails utiles, de dégrader des liens vers le code, ou de réduire la qualité de la recherche.
Il y a aussi un coût opérationnel : une étape de conversion peut allonger la génération et ajoute des dépendances à surveiller. Dans un cas décrit sur Pigweed, l'intégration via Breathe a fait passer la durée de génération d'environ 60 à 90 secondes. Le chiffre varie selon les projets, mais le mécanisme reste le même : chaque passe en plus consomme du temps, et ça rend aussi le diagnostic des erreurs plus pénible.
Dernier point, et pas des moindres : la documentation peut ne jamais apparaître si le flux exige une action manuelle oubliée. Par exemple, un développeur peut annoter son code avec des commentaires Doxygen sans ajouter la directive nécessaire pour intégrer la classe dans Sphinx. Le code est documenté, mais la page n'est jamais publiée. Un build rouge est plus « bruyant » qu'un défaut silencieux : ce n'est pas le même niveau de risque.
Publier les sorties séparément et les relier
L'autre approche consiste à conserver les sorties natives de Doxygen, rustdoc et Sphinx, puis à les publier sous un même domaine ou une structure de site commune. Chaque outil garde son interface et ses forces. C'est, en pratique, l'architecture utilisée par Pigweed : plusieurs sous-sites générés séparément, puis rassemblés via une couche de post-traitement pour offrir une expérience plus unifiée.
Le bénéfice principal : vous ne refaites pas le travail qui existe déjà. Les utilisateurs Rust continuent de retrouver rustdoc ; les références C/C++ restent celles produites par Doxygen ; les guides restent gérés par Sphinx. Vous réduisez le risque de perdre des comportements propres à chaque outil et vous évitez de maintenir une conversion complète entre modèles de données.
Le revers, c'est la fragmentation. Sans travail supplémentaire, les sous-sites peuvent avoir des thèmes différents, pas les mêmes repères, et ne pas « se connaître ». Un lecteur qui arrive sur une page rustdoc peut ne pas trouver comment revenir à l'accueil du projet. La recherche Sphinx peut ignorer des pages Doxygen ou rustdoc. Les liens internes peuvent aussi reposer sur des chemins relatifs bricolés... donc sensibles aux changements d'arborescence.
Cette architecture ne supprime donc pas l'intégration : elle la déplace. Vous ne convertissez plus chaque page ; vous construisez une couche commune par-dessus les sorties HTML. Elle peut unifier navigation, recherche, thème et certaines règles de publication - sans se substituer à la génération de la référence elle-même.
Au final, le modèle paraît parfois moins « propre » sur le plan conceptuel. Mais il peut être plus robuste si les générateurs évoluent indépendamment et si votre équipe préfère préserver leurs comportements natifs. Ce n'est pas une affaire d'élégance : c'est une affaire de coût réel, et de probabilité d'incidents sur la durée.
Choisir une architecture qui reste maintenable
Le choix se fait mieux en regardant la maintenance plutôt que l'apparence du schéma. Dans un projet réel, les besoins changent, les générateurs évoluent, et plusieurs équipes contribuent à la documentation depuis différents dépôts. Une solution qui marche une fois, mais qui dépend d'étapes manuelles non documentées, n'est pas une « intégration fiable ».
Utiliser une grille de décision
Avant de trancher, posez-vous ces questions :
- Le contenu doit-il être imbriqué dans les guides ? Si oui, la transformation peut apporter une vraie valeur. Si les références peuvent rester séparées, garder les sorties natives est souvent plus simple.
- Quelles fonctions spécialisées sont indispensables ? Évaluez ce dont vous avez besoin, vraiment : recherche par signature, liens vers le code source, ancres, et informations propres au langage. Ne partez pas du principe qu'une conversion les préservera.
- Où risquent de se produire les erreurs ? Si un échec peut passer sans faire échouer le build, ajoutez des contrôles. Le contenu attendu doit être vérifiable automatiquement.
- Quel est le coût des mises à jour ? Une transformation doit suivre l'évolution du générateur source et du générateur cible. À l'inverse, une injection d'interface doit être tolérante aux changements de structure des pages.
- Qui maintient l'intégration ? Si une seule personne comprend le script de conversion ou le post-traitement, vous créez un point de fragilité. Documentez le flux et ajoutez des tests.
Une règle pragmatique : transformez seulement ce qui doit vraiment être intégré. Vous pouvez garder une référence d'API dans son générateur natif tout en convertissant quelques extraits précis pour un guide. L'architecture n'a pas besoin d'être identique sur toutes les pages.
Déployer par étapes et mesurer les effets
Commencez par un prototype, pas par « une page vide ». Prenez une page représentative : avec des liens, des signatures, des blocs de code, et une navigation assez profonde. Générez-la avec chaque outil, puis testez le rendu sur ordinateur et sur mobile.
Ensuite, mesurez avant de généraliser :
- durée totale des builds (pas uniquement celle du générateur principal) ;
- nombre d'étapes manuelles nécessaires pour publier ;
- pages manquantes ou liens cassés après chaque génération ;
- couverture de la recherche sur un jeu de requêtes préparé ;
- effort pour mettre à jour le thème ou les versions des outils.
Automatisez les contrôles qui détectent les erreurs « invisibles » : présence de l'en-tête, existence des destinations de navigation, nombre minimal de pages indexées, validation des liens internes. Si vous publiez sous plusieurs sous-chemins, testez aussi les URL dans leur contexte réel. Un lien relatif qui marche en local peut pointer au mauvais endroit une fois déployé.
Enfin, documentez le flux de génération comme une partie du produit. Les contributeurs doivent savoir où ajouter une documentation d'API, comment vérifier qu'elle est publiée, et à qui signaler une page absente. Si une directive est nécessaire pour intégrer une classe, rendez-la explicite dans les outils de contribution ou automatisez sa détection. Une interface commune, aussi « belle » soit-elle, ne compensera pas une chaîne éditoriale que personne ne comprend.
Conclusion
Un site de documentation polyglotte n'a pas besoin de choisir entre une expérience totalement fusionnée et plusieurs sous-sites sans aucun lien. Vous pouvez garder vos générateurs spécialisés, tout en unifiant ce qui compte pour le lecteur : navigation, repères visuels et recherche.
La transformation devient pertinente quand l'intégration du contenu apporte un bénéfice concret, par exemple pour placer une référence d'API au cœur d'un tutoriel. Sinon, publier en natif, avec une couche commune et des tests, évite souvent de réécrire une partie de la chaîne de documentation.
Commencez par l'irritant utilisateur. Choisissez ensuite la couche qui doit être commune, puis prototypez sur une page représentative. Mesurez le temps de build, les erreurs et la qualité de la recherche avant de généraliser. Le but n'est pas de rendre tous les outils identiques : c'est de construire un parcours compréhensible et fiable, malgré leurs différences.