Site logo

Triceraprog
La programmation depuis le Crétacé

  • La Maison dans la colline, partie 2 ()

    Suite de la série sur le développement du jeu La maison dans la colline sur VG5000µ. Après, la première partie, qui donnait le contexte de création, voici donc la deuxième partie, où j'aborde les outils de développement utilisés.

    Les bases

    Lorsque je développe un logiciel, je veux pouvoir me consacrer sur un temps contiguë maximum au cœur du projet. Même, et peut-être surtout, lorsque ce développement est un hobby, et que j'ai peu d'heures à y consacrer, ici et là en fonction de mon temps libre.

    Ainsi, une fois le projet en développement, lorsque j'ai une petite demi-heure par-ci ou une grosse heure par là, je veux pouvoir m'installer et me consacrer à la valeur de ce projet. À son développement concret.

    Pour moi, cela signifie que tout ce que je peux automatiser et qui me permettra de profiter de ces temps là au maximum doit l'être fait en amont. Si je dois cliquer sur des boutons, faire des manipulations, lancer ou configurer des logiciels à chaque fois que je fais un essai, voire même à chaque fois que je débute une session, c'est un temps précieux qui est inutilisé. Chaque manipulation est aussi une occasion de faire une erreur.

    Dans le passé sur ce site, j'ai déjà présenté quelques outils destinés à me faciliter la tâche. Et dès les essais sur l'EF9345 dont je parlais dans l'article précédent, j'ai remis en place un script qui permet de lancer l'émulateur automatiquement, configuré avec lancement du chargement.

    Comme je me doutais que le programme allait gagner en taille, j'ai ajouté au script une manière de passer l'émulateur en vitesse accélérée le temps du chargement.

    Ainsi, dès que je termine une compilation, je n'ai que quelques secondes à attendre avant de retrouver le jeu, prêt à être testé, dans l'émulateur.

    Le framework

    Afin de pouvoir itérer sur le jeu, particulièrement sur son gameplay, j'ai choisi de développer dans un mélange de C et d'assembleur. Le C permet des modifications simples de morceaux de code sans trop se poser de questions bas niveau. L'assembleur permet d’accélérer certaines parties une fois qu'elles sont fixes, ainsi qu'un accès rapide au matériel.

    Car en effet, si j'ai choisi de faire un jeu d'aventure graphique, je tiens à deux choses au niveau de l'affichage :

    • que l'affichage d'une pièce soit très rapide (si ce n'est immédiat),
    • que les mouvements à l'écran ne provoquent aucun clignotement

    Difficile voire impossible à faire cela en BASIC. Plus facile à faire en C, mais c'est l'assembleur qui me permettra de vraiment réaliser ces objectifs.

    De même, je voulais initialement que le jeu tienne sur une machine sans extension mémoire. Malheureusement, je ne réaliserai pas cet objectif. De peu...

    z88dk

    Quelques temps (années ?) auparavant, j'avais croisé le chemin de z88dk, un framework de programmation en C dédié aux machines Z80. Cela me semblait être une bonne occasion pour l'essayer, avoir l'espoir qu'il vienne avec des fonctionnalités intéressantes qui me feraient gagner du temps.

    Après tout, il y a des outils de création de programme assemblés dans une suite complète, une bibliothèque C pour Z80, un support VG5000µ et un fichier crt.s déjà configuré et configurable. Ça, se sont les avantages.

    Après usage pendant toute la durée du projet, voici les inconvénients :

    • le programme principal qui appelle les autres pour la compilation n'a pas une syntaxe d'option standard, et cela me posera des problèmes avec cmake,
    • le compilateur par défaut n'est pas le meilleur, je m'en rendrai compte beaucoup plus tard, vers la fin du projet. Il est possible d'utiliser un compilateur alternatif, qui optimise visiblement mieux. Malheureusement, les deux ne se comportent pas tout à fait de la même façon, et j'ai préférer ne pas changer en chemin. On verra pour un futur projet peut-être,
    • le support VG5000µ est axé sur une utilisation en mode texte, et passe par le système implémenté par la ROM, sans accès direct à l'EF9345. Ce n'est pas cohérent avec ce que je veux faire, et je n'utiliserai finalement aucune fonction spécifiquement dédiée au VG5000µ.

    Au final, je ne suis pas entièrement convaincu par z88dk, qui à l'air de prendre une orientation plutôt CP/M et machined un peu plus puissantes qu'un VG5000µ.

    Gestion de la construction

    Pour le gestionnaire de construction de version, je suis parti sur cmake. C'est très probablement surdimensionné, mais je connais son utilisation classique. Ici, je voulais pousser plus loin et aborder sa configuration de chaîne de compilation « exotique ».

    J'ai beaucoup appris sur cmake au passage ; ma solution finale n'est pas entièrement solide, je confirme à nouveau que Stack Overflow n'est d'aucune aide dès que vous avez un soucis qui n'est pas basique, que la documentation de cmake est toujours peu aidante... et qu'un Makefile tout simple aurait été probablement tout aussi efficace. Mais je m'emmêle toujours les pinceaux avec un Makefile dès que je quitte les règles de bases.

    cmake est probablement trop automatique sur certains points, et il n'est pas toujours facile de lui faire comprendre que la buildchain utilisé ne se comporte pas tout à fait comme ce à quoi il s'attend.

    Enfin maintenant c'est fait, et même si je dois corriger certaines petites choses au niveau des dépendances entre les .c, les .h et les .asm, ça marche globalement bien.

    Éditeur de code

    cmake était surdimensionné ? Que dire de l'éditeur de code que j'ai utilisé. CLion... vim ou visual studio code auraient suffit. Et il m'est arrivé d'utiliser vim pendant le développement pour quelques modifs rapides. Mais là encore, je connais bien CLion, ses outils, sa configuration, et l'habitude, ça compte.

    De toute façon, mes outils d'automatisation ne sont dépendants ni de cmake, ni de clion, ce sont donc des choix indépendants.

    Paré au départ !

    Ou presque. J'ai cité les outils de développement principaux. Mais je parlais en introduction d'automatiser la mise en place de l'environnement de développement. Pour cela, j'utilise le terminal kitty associé à un script. Lorsque je lance ce script, le terminal se met en place avec les bon terminaux ouverts dans les bons répertoire, et des outils textes déjà lancés (ranger pour la gestion de fichier, lazygit pour la gestion de version, nb pour la prise de notes, le script d'automatisation à l'écoute).

    Ainsi, démarrer une session depuis un ordinateur fraîchement allumé me prend deux clics pour avoir l'intégralité de l'environnement en place, et j'ai tout ce qu'il me faut pour la session. Cela aurait été un seul si je n'avais pas choisi Clion.

    Alors... clic, clic... et la suite au prochain épisde.

    Capture montrant l'explorateur textuel Ranger dans le répertoire du jeu.


  • Plouf ... in space ()

    En attendant de revenir sur le déroulé du développement de « La maison dans la colline », voici un article beaucoup plus court pour présenter ma contribution à la session la plus récente de « Retro Programmers United for Obscure Systems », consacrée à Exelvision.

    La découverte de la machine

    Exelvision, une entreprise française, a produit au milieu des années 80 une série de machines assez originales. Ce sont des machines de type familial, mais avec un look un peu pro, un choix audacieux mais probablement désastreux d'une connectique sans fil pour le clavier et les joysticks, de la synthèse vocale, et des capacités graphiques plutôt correctes.

    Pour ce challenge, je me suis orienté vers la première de ces machines, l'EXL 100, dont vous pouvez trouver une présentation détaillée sur ce site.

    C'est une machine que je ne connaissais pas vraiment. Ou tout du moins, je ne m'étais pas penché sur ces caractéristiques, son architecture ni même sur les périphériques disponibles. Et la surprise fut là.

    Côté processeur principal, je savais qu'il faisait dans l'original, un TMS 7020. Côté RAM, tout est côté vidéo, associée à un TMS 3556, non accessible directement par le TMS 7020. Ça promet. Toute cette place mémoire, 32 ko tout de même, hors de porté du bus principal. Et en RAM principale ? ... 2 ko. Deux tout petits kilo octets, déjà très occupés par le système.

    Je me tourne vers le BASIC pour découvrir la machine. Celui-ci est disponible sur cartouche. Et ses données sont bien en VRAM, le seul endroit où il y a de la place. Les performances sont en adéquation. C'est lent. Pas très lent, il y a pire, mais lent tout de même.

    La machine peut bien supporter des extensions mémoires, mais l'architecture laisse peu de place au doute : c'est une machine qui est faite pour lancer ses programmes sur cartouches en priorité, à moins de lui adjoindre l'extension disquettes, qui fait entrer la machine dans une toute autre dimension.

    Ces matériels ne sont pas très courant, et semblent complexifier la machine, je décide donc de cibler l'EXL100 de base, sans extension.

    Les deux premières pistes

    La première piste que j'explore est technique : celle de proposer le logiciel sur cartouche. L'avantage me semble être une rapidité d'exécution. L'inconvénient est que je n'ai aucune idée de comment fonctionne la ROM de la machine, de ce qu'il faut pour la faire reconnaître. Ça se trouve bien entendu, mais je ne connais pas non plus l'assembleur TMS 7020. Je laisse l'idée rapidement de côté, il y a trop d'inconnues pour moi sur cette machine.

    La seconde piste est logicielle : écrire une machine virtuelle qui tiendrait dans la petite RAM de la machine pour exécuter un byte code présent dans la VRAM. L'idée me plaît bien et j'y réfléchi un temps tout en consultant les documentations de la machine. Mais encore un fois, je m'aperçois des particularités de la machine. Je crains le hors sujet.

    Au final, faisons simple : un petit jeu en BASIC pour s'entraîner. Et si j'ai le temps, loger un peu d'assembleur pour accélérer certaines parties.

    Plouf

    Puisque j'ai besoin de creuser ma connaissance de la machine, je ne peux pas me lancer dans quelque chose de compliqué. Je vais donc chercher dans les classiques. Après quelques hésitations, je choisi la bataille navale. C'est un jeu qui peut se représenter avec des graphismes détaillé tout comme en caractères, c'est assez simple à programmer. Petit inconvénient, il me faudra une IA pour un jeu à une seule personne.

    Je cherche un petit twist tout de même pour ne pas aller dans le trop classique et je me dis que faire bouger les bateaux entre deux tours pourrait être sympa sans trop complexifier le jeu. Là encore, c'est débrayable, si je n'ai pas le temps, je ferai sans.

    Et donc c'est parti.

    Le BASIC

    La plupart des BASICs de l'époque se ressemblent, souvent parce qu'ils sont soient des adaptations du BASIC Microsoft, soit parce qu'ils ont été créés avec une syntaxe similaire, ou en tout cas en suivant cette branche d'évolution du BASIC.

    Sans être complètement différent, le BASIC Exelevision a des particularités. Entre autre, il a un concept de routines appelables sans tenir compte de son numéro de ligne. Pratique à première vue. De plus, dans ces routines, les variables sont locales. C'est plutôt attrayant.

    Rapidement, les routines semblent être une fausse bonne idée. En premier lieu, même si le numéro de ligne n'a pas d'importance, elles doivent absolument se trouver à la fin du programme, sous peine d'erreur (et les erreurs sont sous forme de nombres seulement, mieux vaut avoir le manuel sous les yeux). Et puis la localité des variables est tout ou rien. Et on ne peut pas passer de tableaux à ces routines.

    Au final, je ne garde qu'une paire de fonctionnalités sous forme de routines. Le reste sera fait avec ce bon vieux GOSUB et des numéros de ligne.

    Le BASIC permet aussi un traitement d'exception, avec de la récupération d'erreurs. C'est plutôt pas mal... mais je ne penserai à m'en servir que tard dans le développement. Ce BASIC semble cependant un peu capricieux, avec des commandes qui parfois ne veulent pas être sur la même ligne et parfois oui... je n'ai pas vraiment compris les raisons.

    Le démarrage d'un programme après un RUN est assez long, et de plus en plus long avec l'avancée du projet. J'acquière la certitude que RUN effectue une vérification du programme. Peut-être pas une compilation, quoi que je n'en sais rien, mais il est capable de détecter avant démarrage les erreurs de numéros de lignes dans les GOTO/GOSUB, et les mauvais appariement de boucles FOR/NEXT.

    C'est pratique pour le premier cas, parfois un peu obscure pour le second, car la ligne en erreur n'est pas toujours celle où se trouve vraiment l'erreur.

    Cependant, c'est une fonctionnalité notable, car peu présente, voire pas, sur d'autres BASIC de cette époque.

    Le jeu

    Je n'ai pas grand chose à dire sur le jeu lui-même. La principale difficulté est d'arriver à caser deux grilles à l'écran. L'algorithme de déplacement et collision des bateaux me pose aussi quelques soucis, et debugguer n'est pas des plus plaisants, même sur émulateur. Cette machine est peu utilisée, peu connue, et le manque d'outils se fait cruellement sentir.

    Mais je crois bien que ça sera le cas à chaque fois pour ces Game Jams, étant donné que le but est d'aller sur des machines sur lesquels il y a peu de développement.

    Mon principal problème est la vitesse. Le jeu est affreusement lent. Vraiment. Le fait que les bateaux bougent et collisionnent pour faire demi tour demande de longs calculs. Un temps, j'imagine couper les collisions et dire que après tout, ce sont des sous-marins et qu'ils peuvent passer l'un sur l'autre.

    Cependant, cela complexifie la compréhension du jeu, ainsi que la gestion des tirs, qui peuvent toucher plusieurs sous-marins en même temps. Je décide donc qu'il est temps de s'essayer à un peu d'assembleur.

    Optimisations assembleur

    Le TMS 7020, de la série TMS 7000, est un processeur connu, même si pas aussi utilisé dans les ordinateurs personnes que le Z80 ou le 6809. Il est donc supporté par des assembleurs et en particulier par celui-ci, que je connais déjà. Parfait.

    J'avais déjà commencé à lire des ressources sur la programmation de cette machine et ses 128 registres. Quel luxe... ou presque, car c'est dans cette mémoire interne que se loge la pile aussi. Ça reste quand même bien spacieux.

    Et au final, c'est un processeur plutôt agréable. Très simple à programmer. La plus grosse difficulté étant plutôt, à nouveau, de comprendre l'environnement de la machine, les registres à préférer et/ou à laisser tranquille. Comment discuter avec le processeur vidéo et accéder à la VRAM. Mais là encore, il y a de nombreux exemples dans de la documentation.

    La machine, bien que peu connue et peu dynamique, a eu déjà eu des retro-développeurs dans le passé proche. La route est pavée. Un peu laissée à l'abandon, mais pavée quand même, et c'est agréable. Merci à eux.

    Au final, transférer en assembleur mes routines les plus coûteuses se révèle extrêmement bénéfique. J'en aurais bien transféré plus, mais d'après la documentation, j'ai évalué à un peu moins de 500 octets la place disponible de manière « sûre ». On devrait pouvoir pousser un peu plus, mais je n'avais pas le temps de pousser les tests. Restons prudent.

    ... in Space

    Pendant le développement, j'ai souvent hésité. Est-ce que ce sont des bateaux ? Est-ce que ce sont des vaisseaux spaciaux ? Initialement, je comptais redéfinir des caractères afin d'avoir un affichage un peu plus joli. Le temps manquant, je suis resté sur le jeu de caractères disponibles dans le BASIC, qui heureusement à quelques caractères permettant de tracer des lignes en mode basse résolution.

    Pas non plus le temps d'enjoliver en utilisant un peu de mode haute résolution, qui peut se partager l'écran sur cette machine avec le mode texte. L'essentiel du développement se fait pendant mes vacances de fin d'années, et donc juste à la fin de la période de la Game Jam.

    Donc bateau ou vaisseau... cela n'a pas vraiment d'importance en soi.

    Oui mais, dans cette Game Jam, même si on n'en est qu'à la deuxième session, il est de « tradition » de pousser un peu la qualité en ayant une couverture de jeu, une illustration, un manuel, ou un peu de tout ça. Et les jeux de l'époque misaient beaucoup sur ce packaging pour faire travailler l'imagination.

    Donc je dois choisir. Et je choisi l'espace... même si le fond de l'écran pendant le jeu reste bleu foncé. Les vaisseaux ne sont pas coulés après avoir été touchés, mais désactivés, ce qui me donne une justification au RADAR, qui réagi aux morceaux de vaisseaux touchés.

    Il me reste une IA à bricoler. Je fais simple : si le RADAR ne donne pas d'information, le coup prochain se fera au hasard. S'il donne une information, le tir se fera d'autant de cases que la distance indiquée par le RADAR, réparties en horizontal et en vertical. Cela ne prend pas en compte le déplacement des vaisseaux, ni les touches précédentes, mais après quelques parties, je juge que c'est suffisant pour mettre une petite pression à l'humain.

    Conclusion

    Une machine originale, qui demande vraiment à y revenir. Un jeu qui ne vole pas très haut, mais qui a été intéressant à optimiser et comme toujours, à terminer et présenter.

    Si vous aussi vous voulez jouer avec des torpilles photoniques, c'est par ici.

    Écran de jeu de Plouf... in Space


  • Inifinite Turtles, un jeu avec des tortues jusqu'en bas. ()

    Le jeu « Inifinite Turtles », de Charlie Brej, est un casse-tête de programmation. De manière classique dans ce type de jeu, vous êtes exposés à des problèmes de plus en plus complexe à résoudre, en ayant à votre disposition de nouvelles briques du système.

    En ce sens, il se rapproche des jeux de Zachtronics comme Shenzhen I/O, de Human Resource Machine ou bien d'autres, à un niveau de réalisation moins poussé tout en étant tout à fait acceptable.

    Dans « Infinite Turtles », votre terrain est une grille de positions avec quatre « portes » aux points cardinaux. Ces portes sont des entrées ou des sorties par lesquels vont transiter des jetons numérotés.

    À votre charge d'acheminer les jetons grâce à des tapis roulants à travers la grille.

    Chaque niveau vous indique l'objectif à atteindre et vous donne, pour les premiers niveaux, des indications sur le fonctionnement du jeu, ainsi qu'une nouvelle brique fonctionnelle.

    Ces briques fonctionnelles occupent un espace sur la grille. Elles réalisent une fonction simple, comme par exemple un aiguillage, un clonage du jeton ou encore un filtre.

    De plus, chaque niveau réalisé devient une nouvelle brique fonctionnelle que vous pouvez utiliser pour résoudre d'autres niveaux. Et bien entendu, le jeu est construit de manière à ce que les nouvelles fonctions servent dans les problèmes suivants.

    Enfin, pour vous aider à la réalisation du niveau, un batterie de tests est disponible, que vous pouvez exécuter plus ou moins rapidement, afin d'aider à mettre au point la solution.

    Et enfin, un score vous indique l'efficacité en temps et en briques utilisées et vous pouvez le comparer aux autres joueurs.

    Des tortues jusqu'en bas

    Le titre du jeu ne fait pas référence à une tortue de type de celle utilisée dans le langage Logo, mais à la cosmogonie qui dit que la Terre repose sur une tortue, elle même reposant sur une tortue,... et tout ceci jusqu'en bas.

    Le thème est introduit dès le début dans les explications par les dialogues des personnages qui animent la narration, qui sont toujours par couple identique mais de tailles différentes.

    Ainsi, le jeu introduit l'utilisation de briques fonctionnelles qui utilisent elles-mêmes d'autres briques fonctionnelles qui utilisent, etc.

    Aperçu du jeu Infinite Turtles

    Tout dans la synchro

    La difficulté des problèmes n'est pas tant de les résoudre fonctionnellement, mais de les placer sur une grille restreinte. En effet, rapidement, la place se fait rare, et l'utilisation des briques déjà faites est nécessaire.

    Cependant, le système que crée le jeu est très dépendant des synchronisations. En effet, les jetons arrivent avec un tempo déterminé par les tests et leur temps de trajet sur les tapis roulants est entièrement dépendant de la longueur de ces trajets, y compris des trajets dans les briques internes.

    Dès les premiers niveaux, les défis proposés sont clairs : il va falloir trouver des moyens de synchroniser tous ces jetons pour qu'ils arrivent face au briques fonctionnelles au moment voulu. Le jeu guide vers des systèmes de synchronisation que l'on découvre et que l'on pourra appliquer plus tard.

    Mais ces systèmes prennent de la place, beaucoup de place, car basé sur de l'envoi de messages par jetons. Ils nécessitent donc des tapis roulants encombrants.

    Et donc ?

    Je n'ai pas terminé tous les niveaux du jeu. Je suis allé assez loin, mais au bout d'un moment, j'avais l'impression que le problème à résoudre était essentiellement du routage de jeton et comment faire tenir le tout dans la grille.

    Les fonctions d'édition avancées de la grille, qui ne sont pas présentée dès le début, ne sont pas toujours très agréable non plus. Quand on veut essayer différents type de routages afin de voir celui qui prend le moins d'espace, le comportement des tapis roulant est rapidement agaçant.

    Un autre défaut, mais moindre, est la progression des niveaux, qui mériterait plus de fluidité. Les problèmes par lesquels ont passe ont un objectif, on le sent bien, mais on passe parfois du coq à l'âne, sur des voies que l'on sent se rejoindre au loin, mais qui produisent une sorte de « task switching » peu agréable.

    Malgré ces petits soucis, et sur la vingtaine de niveaux que j'ai pu résoudre, j'ai trouvé le jeu amusant et plutôt original. Le modèle d'automation et le type de programmation qu'il induit sont plutôt originaux, avec une petite histoire métaphysique pour enrober le tout.

    C'est un jeu qui se défend tout à fait dans sa catégorie, et si vous êtes curieux et aimez les jeux d'énigmes logiques, cela pourra vous plaire.


  • La Maison dans la colline, partie 1 ()

    En juin 2022, sur Facebook, Olipix lance un groupe avec l'objectif de donner un peu d'activité à des ordinateurs qui n'en n'ont pas beaucoup. En effet, si quelques anciennes machines bénéficient de nombreux nouveaux logiciels homebrew toujours de nos jours, certaines autres ont une base d'utilisateurs beaucoup plus restreinte. Et souvent une ludothèque maigrichonne.

    Initialement, je n'ai pas trop prêté attention à l'initiative. J'étais justement en train de nettoyer mon compte Facebook avec l'idée de ne plus y mettre les pieds, tout en gardant un accès minimale « au cas où ». Mais quand on m'avertie qu'un vote a désigné le VG5000µ comme première machine, je dresse l'oreille.

    Ça tombe bien, je m'étais remis à l'étude du VG5000µ après le hiatus de l'étude du Micral N. C'est une excellente occasion pour relier les deux activités : continuer l'exploration de la machine, tout en développant un jeu.

    Reste à trouver l'idée. J'aimerais quelque chose qui pousse un peu les capacités d'affichage de la machine. En attendant, je commence à mettre en place une chaîne d'outils de développements avec comme premier objectif le développement d'un programme de tests des différentes possibilités graphiques de l'EF9345.

    L'idée derrière la batterie de tests est de documenter l'utilisation de ce processeur graphique en publiant les sources, de démontrer ses différents modes (ce qui a déjà été fait par le passé), et de proposer des tests pour pouvoir travailler les détails des timings et les commandes non implémentées actuellement dans les deux émulateurs du VG5000µ (dcvg5k et MAME).

    Je n'ai pas encore terminé cette batterie de tests, puisque je suis passé entre temps sur le développement du jeu. Je publierai ça sous peu, même si pas terminé, accompagné d'articles.

    En attendant, en voici une copie d'écran.

    Écran de jeu de la Maison dans la colline


  • Utilisation de z88dk pour le VG5000 ()

    Encore ?! Oui... encore. Une nouvelle manière de gérer la construction d'un programme VG5000µ. Après la version Sublime Text et z80asm en 2018, puis la version Visual Studio Code et sjasmplus en 2020, je voulais essayer autre chose.

    J'avais laissé de côté Sublime Text et z80asm pour deux raisons : le changement de license de Sublime Text que je n'avais pas apprécié, et le côté très simpliste de z80asm, dont je touchais des limites.

    Pour un nouveau projet, je voulais utiliser z88dk, un kit de développement pour machines Z80, avec du support C et ASM, ainsi que des bibliothèques standards. Je voulais aussi approfondir ma connaissance du support de toolchains avec CMake.

    Alors oui, cmake pour un tout petit projet pour des machines des années 80, ça fait un peu surdimensionné... Je le concède. Et ça n'enlève en rien mon envie de fouiller de ce côté.

    Un exemple, qui peut servir de base, est disponibles sur GitLab et GitHub. Il y a quelques limitations quand à l'environnement supporté, car je n'ai testé que sur mon ordinateur de développement.

    Mais l'essentiel est là, et peut éviter des heures de recherches entre la documentation de CMake et StackOverflow, la première était connue pour son aridité et le second pour, en ce qui concerne CMake, ses 99% de réponses fausses, ou tout du moins obsolètes.

    Par la suite, je vais décrire les différents éléments qui forment le projet CMake.

    CMakeLists.txt

    Tout projet cmake a pour point d'entrée un fichier CMakeLists.txt.

    cmake_minimum_required(VERSION 3.20)
    

    Tout d'abord, on indique la version minimale de cmake à utiliser. Il est toujours préférable de mettre celle sur laquelle on a validé le fonctionnement, car cmake évolue souvent et tenter d'en mettre une plus ancienne est acrobatique.

    list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
    

    Je vais avoir besoin d'ajouter des scripts de description de machine et de compilateur localement, car non reconnus nativement, du moins aujourd'hui, par cmake. J'indique donc que certains scripts se trouvent dans le répertoire locale cmake/, en modifiant la variable CMAKE_MODULE_PATH.

    project(vg_tests C ASM)
    

    Je déclare ensuite le nom du projet vg_tests, ainsi que les langages de programmation supportés.

    # If need for cmake debug, the next line can help
    # set(CMAKE_VERBOSE_MAKEFILE 1)
    

    Comme indiqué, mettre la variable CMAKE_VERBOSE_MAKEFILE à 1 permet d'avoir des informations sur les actions qui sont faites lors de la génération du projet. Très pratique pour comprendre quelles sont les lignes de commandes générées, avec leurs options. Essentiel pour mettre au point quand on tâtonne.

    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ../output)
    

    La variable CMAKE_RUNTIME_OUTPUT_DIRECTORY indique où sont les artefacts de sortie. Dans notre cas, c'est là que se trouveront les .k7 utilisables pour le vg5000µ (ou les .wav, si vous le désirez).

    set(SOURCE_FILES src/main.c src/auxiliary.asm)
    

    SOURCE_FILES est une variable interne à ce script. C'est une habitude que de passer par une variable intermédiaire pour spécifier la liste des fichiers sources. Dans certains cas, on peut avoir besoin de la réutiliser.

    Ici, je vais compiler et assembler un fichier C et un fichier assembleur.

    add_executable(${PROJECT_NAME} ${SOURCE_FILES})
    target_compile_options(${PROJECT_NAME} PRIVATE -I$ENV{Z88DK_HOME}/include -Isrc/ -vn -m)
    target_link_options(${PROJECT_NAME} PRIVATE -m -create-app -subtype=default)
    

    La déclaration de l'exécutable utilise la variable PROJECT_NAME, qui prend le nom spécifié dans project() au tout début, et y associe la liste des fichiers sources.

    Puis sont définies les options de compilations et les options pour l'éditeur de liens. La nature des options ne sont pas du domaine de cet article. Elles seront transmises à zcc, qui est la commande générique pour toutes les opérations de construction dans z88dk.

    # Fixes the k7 format for old z88dk versions.
    set(INPUT_FOR_FIX ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}.k7)
    set(ZERO_FILE ${CMAKE_SOURCE_DIR}/zero-file)
    set(OUTPUT_FOR_FIX ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}.fix.k7)
    
    add_custom_command(OUTPUT k7_fix
            DEPENDS ${PROJECT_NAME}
            COMMAND ${CMAKE_COMMAND} -E cat ${INPUT_FOR_FIX} ${ZERO_FILE} > ${OUTPUT_FOR_FIX}
            )
    
    add_custom_target(${PROJECT_NAME}-fix ALL DEPENDS k7_fix)
    

    La version actuelle de z88dk a un bug au niveau de la génération des données du VG5000µ. Il manque des octets à la fin, qui sont attendues par la ROM pour valider la fin du fichier.

    J'ai soumis un fix, qui a été accepté, mais le temps que cela soit déployé partout, j'ajoute cette custom_target, qui utilise une custom_command. Le fichier zero-file est le fichier nécessaire à l'ajustement, et ne contient que des 0.

    Compilation croisée

    Lancer cmake tel quel ne va pas fonctionner, car par défaut, cela utilisera les outils de compilation de la machine hôte. Il faut donc spécifier une environnement de compilation croisée, c'est-à-dire les outils pour compiler pour une machine cible, et non la machine hôte.

    Pour cela, cmake a un mécanisme de déclaration de compilation croisée. Lors de l'initialisation de cmake, il faut spécifier la variable CMAKE_TOOLCHAIN_FILE. Ici, cmake -DCMAKE_TOOLCHAIN_FILE=z88dk-vg5000.cmake.

    Ici, on entre un peu dans le tâtonnement. Ce que j'expliquer fonctionne, mais est-ce que c'est carré ? C'est une bonne question.

    Voici l'explication du fichier z88dk-vg5000.cmake.

    set(CMAKE_SYSTEM_NAME vg5000)
    set(CMAKE_SYSTEM_PROCESSOR Z80)
    set(CMAKE_C_COMPILER_ID z88dk)
    set(CMAKE_ASM_COMPILER_ID z88dk)
    

    En premier lieu, on fourni des valeurs à des variables internes de cmake indiquant le nom du système, le processeur, et des identifiants du compilateur et de l'assembleur. Ces variables seront utilisées par cmake pour déterminer quels fichiers de description il doit chercher.

    Il n'est pas toujours très clair de savoir quelle variable influe sur quoi. Il faut se fier aux messages d'erreurs lorsqu'un fichier n'est pas trouvé...

    set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
    

    Cette variable indique à cmake si, pour valider le fonctionnement de la chaîne de compilation, un essai se fera sur une bibliothèque ou sur un exécutable. L'exécutable, pour être validé, doit être lancé. Comme je ne définie pas de moyen de lancer l'exécutable, je demande à ne faire l'essai de compilation que sur une bibliothèque, ce qui est en fait le défaut lors d'une compilation croisée.

    set(TOOLCHAIN_PREFIX z88dk)
    set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}.zcc)
    set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}.zcc)
    

    Ici, j'indique le nom des compilateurs et assembleur. Pour cmake, tout ce qui prend une source et sort un fichier objet est un compiler. Pour z88dk, je passe par le frontend zcc plutôt que les exécutable eux-mêmes, ce qui semble être préféré dans la documentation (et dans la façon dont est construit le paquetage).

    set(CMAKE_DEPENDS_USE_COMPILER True)
    

    J'indique aussi à cmake d'utiliser le compilateur pour trouver les dépendances entre les fichiers.

    set(CMAKE_C_COMPILE_OBJECT  "<CMAKE_C_COMPILER> +vg5k <DEFINES> <INCLUDES> <FLAGS> -o <OBJECT> -c <SOURCE>")
    set(CMAKE_C_LINK_EXECUTABLE "<CMAKE_C_COMPILER> +vg5k <FLAGS> <OBJECTS> -o <TARGET> <CMAKE_C_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES>")
    
    set(CMAKE_ASM_COMPILE_OBJECT  "<CMAKE_C_COMPILER> +vg5k <DEFINES> <INCLUDES> <FLAGS> -o <OBJECT> -c <SOURCE>")
    

    On y est presque. Dans un cas classique où le compilateur se comporte de manière classique (disons comme un gcc, un clang ou autre), on pourrait se passer de ces lignes. Mais zcc à besoin comme premier paramètre de la plateforme cible (+vg5k ici).

    J'indique donc à cmake comment générer la ligne de commande pour les fichiers C et ASM, avec une syntaxe de template.

    set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
    set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER)
    set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
    

    Et enfin, je demande à cmake de ne pas chercher à résoudre les commandes find_library, que je n'utiliserai pas.

    Et ce n'est pas fini !

    Le fichier de compilation croisée indique que l'on compile pour VG5000µ. Mais cmake ne connait pas cette plateforme, et va donc chercher un script qui lui en dirait plus.

    Ce que j'ai fait n'est probablement pas entièrement correct, car j'associe la plateforme avec la chaîne de compilation. Et je fais ça dans le fichier cmake/Platform/vg5000.cmake.

    set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS FALSE)
    

    z88dk ne supporte pas un systmème de bibliothèque dynamiques (style DLL, so ou dynlib).

    set(CMAKE_C_OUTPUT_EXTENSION .o)
    

    z88dk ne reconnaît que l'extension .o comme fichiers objets. On indique donc à cmake de produire des fichiers objets avec cette extension.

    set(CMAKE_SYSTEM_INCLUDE_PATH $ENV{Z88DK_HOME}/include)
    set(CMAKE_SYSTEM_LIBRARY_PATH $ENV{Z88DK_HOME}/lib)
    set(CMAKE_SYSTEM_PROGRAM_PATH $ENV{Z88DK_HOME}/bin)
    

    Ces variables ont l'air d'être les pendants des CMAKE_FIND_ROOT_PATH_MODE_* indiqués dans le fichier de compilation croisée. Le fonctionnement n'est pas hyper clair.

    Voilà, à présent cmake sait compiler un fichier C avec z88dk pour VG5000µ.

    Et l'assembleur ?

    Pour une raison que je n'ai pas creusé, mais probablement concernant la séparation des plateformes des compilateurs, le support d'un assembleur nécessite un autre fichier, différent du précédent vg5000.cmake.

    C'est dans cmake/Compiler/z88dk-ASM.cmake que cmake va chercher l'outil pour traiter l'ASM pour z88dk. Ce qui se tient. Pourquoi est-ce qu'il ne va pas chercher z88dk-C.cmake au même endroit, cela m'échappe...

    Le contenu est strictement identique à celui du fichier vg5000.cmake, puisque l'on s'adresse au même outil zcc.

    Et la cerise optionnelle

    Le fichier z88dk-clion.yaml est un fichier qui ajoute un support de z88dk (assez succin) à Clion. Par défaut, lors de la génération de cmake, l'IDE va essayer de trouver un certain nombre d'information en interrogeant le compilateur. Les #define par exemple, ou les répertoires d'inclusion par défaut.

    Mais zcc ne réagissant par bien à la question, Clion affiche un warning. Il est cependant possible de lui indiquer manuellement les informations recherchées, et c'est le but de ce fichier. Cependant, sa description tombe hors du sujet de cet article.

    Conclusion

    Ça a été une aventure, comme à chaque fois que l'on sort des sentiers battus avec cmake. J'y ai appris un peu plus de choses, ce qui était probablement l'objectif initial. Et j'ai une façon de générer un programme VG5000µ à partir de tout outil qui utilise cmake, ce qui couvre aussi Visual Studio Code.


« (précédent) Page 7 / 24 (suivant) »

Tous les tags

3d (15), 6809 (1), 8bits (1), Affichage (24), AgonLight (2), Altaïr (1), Amstrad CPC (1), Apple (1), Aquarius (2), ASM (30), Atari (1), Atari 800 (1), Atari ST (2), Automatisation (4), BASIC (31), BASIC-80 (4), C (3), Calculs (1), CDC (1), Clion (1), cmake (1), Commodore (1), Commodore PET (1), Compression (2), CPU (1), Debug (5), Dithering (2), Divers (1), EF9345 (1), Émulation (7), Famicom (2), Forth (3), Game Jam (1), Hector (3), Histoire (1), Hooks (4), i8008 (1), Image (17), Jeu (15), Jeu Vidéo (4), Livre (1), Logo (2), LZ (1), Machine virtuelle (2), Magazine (1), MAME (1), Matra Alice (3), MDLC (7), Micral (2), Motorola (1), MSX (1), Musée (2), Nintendo Switch (1), Nombres (3), Optimisation (1), Outils (3), Pascaline (1), Peertube (1), PHC-25 (2), Photo (2), Programmation (5), Python (1), RLE (1), ROM (15), RPUfOS (6), Salon (1), SC-3000 (1), Schéma (5), Synthèse (15), Tortue (1), Triceraprog (1), VG5000 (62), VIC-20 (1), Vidéo (1), Z80 (21), z88dk (1)

Les derniers articles

Compression de données, référencer les répétitions
Briques Stellaires, jeu pour PHC-25
Compression de données, compter les répétitions
PHC-25, et Z80 en IM 2
Récréation Famicom
Family BASIC, le BASIC sur Famicom
Instance Peertube pour Triceraprog
Environnement de développement pour Picthorix
Un jeu en Forth pour Hector HRX : Picthorix
Yeno SC-3000 et condensateurs

Atom Feed

Réseaux