Hardening consulting

Sortie d'accendino 0.5.9

J'ai fais quelque petites mises à jour sur mon script Accendino, jusqu'à en faire un petit programme à part entière avec des fonctionnalités intéressantes. Cette version 0.5.9 vient ajouter plein de choses sympatiques par rapport à la version précédente. Avec le temps, je vois plus Accendino comme un programme permettant de construire un logiciel complexe à partir de plusieurs sources logicielles, et sur plusieurs plate-formes. Pour l'instant mon cas d'école est FreeRDP, j'essaye d'avoir des fichiers accendino qui permettent de construire FreeRDP à partir de zéro sur le plus de plate-formes possibles (linux, mac, windows, mingw, ...)


Historique

Originellement accendino n'était qu'un petit script permettant de jouer les instructions d'installation d'Ogon. Il était néanmoins un peu plus complexe car on pouvait spécifier les emplacements git à descendre. Par exemple, pour utiliser les repos de Forgiare à la place des repos officiels d'Ogon. Avec la version 0.5.0, j'ai pas mal étendu ses fonctionnalités:

  • la possibilité d'inclure des fichiers accendino pour réutiliser des définitions existantes;
  • les sources des programmes ont été beaucoup étendues et ne viennent plus nécessairement de git. On peut avoir des sources locales, ou bien de git. Beaucoup d'options git sont désormais accessibles;
  • dans un fichier accendino on a désormais accès a tout un tas de fonctions et de variables, ce qui permet de faire des scripts de build plus complexes;
  • la gestion du plan de construction a été revue, ce qui a permis d'avoir une fonctionnalité de reprise du build à partir d'un certain point;
  • il arrive que les distributions changent le nom ou la disponibilité de leurs packages, et donc des objets ont été introduit pour aider à représenter ça dans les dépendances au packages locaux. Par exemple DepsAdjuster('< Ubuntu 18.04', add=['libxfont-dev']), permet d'ajouter en dépendance le paquetage libxfont-dev quand on build pour sur une Ubuntu avant la 18.04.
  • on dispose d'une page de manuel listant tout ce qui est disponible à l'intérieur des scripts accendino
  • cette version est publié sur pypy

Les nouveautés dans la version 0.5.9

Cross compilation

Cette version commence à introduire la compilation croisée, celà prend en compte l'architecture ainsi que la plateforme. Ainsi la compilation pour windows à partir de Linux a été pas mal testée en utilisant mingw. Certaines plateformes comme Fedora fournissent des binaires déjà compilés de librairies usuelles pour mingw, ainsi sur une Fedora 41 on dispose déjà de zlib, d'uriparser, etc. Celà permet de faire des fichiers accendino assez complexes où par exemple pour fournir zlib on peut:

  • si on compile en natif utiliser des packages disponibles comme zlib1-dev ou zlib-ng-devel;
  • si on compile en compilation croisée pour Windows avec mingw et qu'on est sur Fedora, utiliser les paquetages ad-hoc comme mingw64-zlib. Même Ubuntu fournit zlib pour mingw;
  • et enfin si on n'est dans aucun de ces cas là, on compile zlib à partir des sources

Le fichier zlib.accendino correspondant est assez élégant je trouve:

zlib_pkgDeps = {
    UBUNTU_LIKE: ['zlib1g-dev'],
    REDHAT_LIKE: ['zlib-ng-devel'],
}

zlib_fromSources = False

if targetDistribId == 'mingw':
    # Checks for some existing distribution packets
    if checkDistrib('>= Fedora 40') or (checkDistrib('>= Ubuntu 22.04') and targetArch == 'x86_64'):
        zlib_pkgDeps.update({
            'Fedora->mingw@i686': ['mingw32-zlib'],
            'Fedora->mingw@x86_64': ['mingw64-zlib'],
            'Ubuntu->mingw@x86_64': ['libz-mingw-w64-dev'],
        })
    else:
        zlib_fromSources = True

if targetDistribId == 'Windows':
    zlib_fromSources = True

if zlib_fromSources:
    ARTIFACTS += [
        CMakeBuildArtifact('zlib', [], GitSource('https://github.com/madler/zlib.git', 'v1.3.1'),
            cmakeOpts=['-DZLIB_BUILD_EXAMPLES=OFF'], provides=['zlib-artifact']
        ),
        CMakeBuildArtifact('zlib-static', [], GitSource('https://github.com/madler/zlib.git', 'v1.3.1'),
            cmakeOpts=['-DZLIB_BUILD_EXAMPLES=OFF', '-DBUILD_SHARED_LIBS=OFF']
        ),
    ]

else:
    ARTIFACTS += [
        DepsBuildArtifact('zlib-artifact', [], pkgs=zlib_pkgDeps)
    ]

Un artefact nécessitant zlib pour se construire n'aura qu'à mettre zlib-artifact dans ses dépendances et accendino fera le reste.

Comme on peut le voir dans cet exemple, le format pour exprimer un environnement cross compilation est <build distrib>-><target distrib>@<arch>, par exemple Fedora->mingw@x86_64 signifie "quand on build sous Fedora pour mingw64".

Accendino essaye de fournir ce qu'il faut à cmake ou meson pour que la compilation croisée fonctionne correctement sans faire la moindre configuration.

Par exemple pour avoir freerdp et ses dépendances pour windows construit avec mingw, on lance dans les sources d'accendino:

# accendino --targets=freerdp3 --targetDistrib=mingw64 --prefix=/tmp/freerdp3-win32 --project=freerdp3-build src/accendino/pocket/freerdp.accendino

Et dans /tmp/freerdp3-win32 on aura tout ce qu'il faut. On aura un répertoire freerdp3-build qui contiendra les sources et les compilés.

Artefacts de build

Les artefacts de build ont été modifié pour utiliser des commandes de build et d'install qui marchent sur toutes les plateformes (cmake --build ou meson compile), de cette manière on peut laisser ces outils générer pour le backend le plus adapté à une plateforme, et construire et installer sans se soucier de quel outil est vraiment utilisé derrière (msbuild pour windows et ninja ou cmake sur les unix).

Désormais les artefacts de build et les sources de code amènent aussi leurs dépendances en terme de packages: un artefact construit avec cmake dont le code vient de git demandera à installer les packages git et cmake. On essaye aussi de faire au mieux quand on compile pour mingw.

La classe BuildArtifact a été beaucoup retravaillée pour bien avoir l'étape de préparation du code source et du répertoire de build. Et la phase de compilation/installation. La phase de préparation est quelques fois assez longue avec des sources utilisant les autotools ou des outils maison (oui on parle de vous openssl et ffmpeg), accendino essaye donc de faire un cache de cette étape en référençant les variables qui servent à cette étape, et si rien n'a changé on ne la relance pas.

Comme Accendino lance des commandes avec tout un environnement qu'il établi lui même à partir de la configuration, quand on veut débugger un build qui ne fonctionne pas, c'est compliqué d'être exactement dans la même configuration. Pour aider au débuggage, quand on lance accendino avec l'option --debug, il créé des fichiers setEnv.sh et prepare.sh qui permettent de facilement avoir le même environnement et les mêmes commandes.

Chemin de style Unix ou windows

Quand un même outil est disponible à la fois sous les unix et sous windows, on se retrouve assez fréquement à se demander comment exprimer des chemins de fichiers: faut-il utiliser des / ou bien des \ ? Certains outils gèrent les deux et pour d'autres on doit donner un chemin dans le mode de l'Os. C'est pour celà qu'accendino a désormais une class NativePath qui permet de s'adapter à cette contraite et de proposer un chemin avec le saparateur qui va bien pour un OS donné. Ainsi NativePath('{srcdir}', 'toto', 'bin', prefix='--mypath=', suffix='-after') génèrera la chaîne --mypath={srcdir}/toto/bin-after sous les unix et --mypath={srcdir}\toto\bin-after sous windows. Avec srcdir qui sera substitué par son équivalent natif (on peut aussi utiliser {srcdir_posix} si on veut forcer la représentation posix avec des /).

Petit exemple d'utilisation tiré du fichier openssl.accendino:

prepareCmd = ['perl', NativePath('{srcdir}', 'Configure')]

Conclusion

Bref, cette nouvelle version d'accendino est vraiment une étape importante, essayez la !