Langue: 日本語 | Español | Français | Português | 中文 | English
Précédent | Suivant | Sommaire | Version originale anglaise (gnu.org)

12 Extensions de GNU make

GNU make offre de nombreuses fonctionnalités avancées, parmi lesquelles de nombreuses fonctions utiles. Il ne contient toutefois pas un langage de programmation complet et présente donc des limites. Ces limites peuvent parfois être contournées en recourant à la fonction shell pour invoquer un programme distinct, mais cette méthode est souvent peu efficace.

Lorsque les capacités intégrées de GNU make ne suffisent pas à vos besoins, il existe deux façons d'étendre make. La première, sur les systèmes où il est disponible, consiste à utiliser GNU Guile comme langage de script embarqué (voir Intégration de GNU Guile). La seconde, sur les systèmes qui prennent en charge les objets chargeables dynamiquement, consiste à écrire votre propre extension dans n'importe quel langage (pouvant être compilé en un tel objet) et à la charger pour fournir des capacités étendues (voir Chargement d'objets dynamiques).

12.1 Intégration de GNU Guile

GNU make peut être compilé avec la prise en charge de GNU Guile comme langage d'extension embarqué. Guile est une implémentation du langage Scheme. Une présentation de GNU Guile, du langage Scheme et de leurs fonctionnalités dépasse le cadre de ce manuel : reportez-vous à la documentation de GNU Guile et de Scheme.

Vous pouvez déterminer si votre make prend en charge Guile en examinant la variable .FEATURES ; elle contiendra le mot guile si la prise en charge de Guile est disponible.

L'intégration de Guile ajoute une nouvelle fonction make : la fonction guile. La fonction guile prend un seul argument, qui est d'abord développé par make de la manière habituelle, puis transmis à l'évaluateur de GNU Guile. Le résultat de l'évaluateur est converti en chaîne et utilisé comme développement de la fonction guile dans le makefile.

De plus, GNU make expose des procédures Guile destinées à être utilisées dans des scripts Guile.

12.1.1 Conversion des types Guile

Il n'existe qu'un seul « type de données » dans make : la chaîne de caractères. GNU Guile, en revanche, propose une grande variété de types de données différents. Un aspect important de l'interface entre make et GNU Guile est la conversion des types de données Guile en chaînes make.

Cette conversion intervient à deux endroits. Le premier : lorsqu'un makefile invoque la fonction guile pour évaluer une expression Guile, le résultat de cette évaluation doit être converti en une chaîne make afin de pouvoir être évalué plus avant par make. Le second : lorsqu'un script Guile invoque l'une des procédures exportées par make, l'argument fourni à la procédure doit être converti en chaîne.

La conversion des types Guile en chaînes make est la suivante :

#f

Faux (False) est converti en chaîne vide : dans les conditions de make, la chaîne vide est considérée comme fausse.

#t

Vrai (True) est converti en la chaîne « #t » : dans les conditions de make, toute chaîne non vide est considérée comme vraie.

symbol
number

Un symbole ou un nombre est converti en la représentation sous forme de chaîne de ce symbole ou de ce nombre.

character

Un caractère imprimable est converti en le même caractère.

string

Une chaîne ne contenant que des caractères imprimables est convertie en la même chaîne.

list

Une liste est convertie récursivement selon les règles ci-dessus. Cela implique que toute liste structurée sera aplatie (autrement dit, un résultat « '(a b (c d) e) » sera converti en la chaîne make « a b c d e »).

other

Tout autre type Guile provoque une erreur. Dans de futures versions de make, d'autres types Guile pourront être convertis.

La traduction de « #f » (en chaîne vide) et de « #t » (en la chaîne non vide « #t ») est conçue pour vous permettre d'utiliser directement les résultats booléens de Guile comme conditions booléennes de make. Par exemple :

$(if $(guile (access? "myfile" R_OK)),$(info myfile exists))

En conséquence de ces règles de conversion, vous devez tenir compte du résultat de votre script Guile, car ce résultat sera converti en chaîne et analysé par make. S'il n'y a pas de résultat naturel pour le script (c'est-à-dire si le script n'existe que pour ses effets de bord), vous devriez ajouter « #f » comme dernière expression afin d'éviter des erreurs de syntaxe dans votre makefile.

12.1.2 Interfaces de Guile vers make

Outre la fonction guile disponible dans les makefiles, make expose quelques procédures destinées à être utilisées dans vos scripts Guile. Au démarrage, make crée un nouveau module Guile, gnu make, et exporte ces procédures comme interfaces publiques de ce module :

gmk-expand

Cette procédure prend un seul argument, qui est converti en chaîne. La chaîne est développée par make selon les règles de développement habituelles de make. Le résultat du développement est converti en chaîne Guile et fourni comme résultat de la procédure.

gmk-eval

Cette procédure prend un seul argument, qui est converti en chaîne. La chaîne est évaluée par make comme s'il s'agissait d'un makefile. C'est la même capacité que celle offerte par la fonction eval (voir La fonction eval). Le résultat de la procédure gmk-eval est toujours la chaîne vide.

Notez que gmk-eval n'est pas tout à fait équivalent à l'utilisation de gmk-expand avec la fonction eval : dans ce dernier cas, la chaîne évaluée sera développée deux fois ; d'abord par gmk-expand, puis de nouveau par la fonction eval.

12.1.3 Exemple d'utilisation de Guile dans make

Voici un exemple très simple d'utilisation de GNU Guile pour gérer l'écriture dans un fichier. Ces procédures Guile se contentent d'ouvrir un fichier, d'autoriser l'écriture dans ce fichier (une chaîne par ligne), puis de fermer le fichier. Notez que, comme nous ne pouvons pas stocker dans les variables make des valeurs complexes telles que les ports Guile, nous conserverons ici le port comme variable globale dans l'interpréteur Guile.

Vous pouvez créer facilement des fonctions Guile en utilisant define/endef pour constituer un script Guile, puis en utilisant la fonction guile pour l'internaliser :

define GUILEIO
;; A simple Guile IO library for GNU Make

(define MKPORT #f)

(define (mkopen name mode)
  (set! MKPORT (open-file name mode))
  #f)

(define (mkwrite s)
  (display s MKPORT)
  (newline MKPORT)
  #f)

(define (mkclose)
  (close-port MKPORT)
  #f)

#f
endef

# Internalize the Guile IO functions
$(guile $(GUILEIO))

Si vous disposez d'une quantité importante de code de support Guile, vous pourriez envisager de le conserver dans un fichier séparé (par exemple guileio.scm), puis de le charger dans votre makefile à l'aide de la fonction guile :

$(guile (load "guileio.scm"))

L'avantage de cette méthode est que, lors de l'édition de guileio.scm, votre éditeur comprendra que ce fichier contient de la syntaxe Scheme et non de la syntaxe makefile.

Vous pouvez désormais utiliser ces fonctions Guile pour créer des fichiers. Supposons que vous deviez manipuler une très grande liste, qui ne peut pas tenir sur la ligne de commande, mais que l'utilitaire que vous employez accepte aussi la liste en entrée :

prog: $(PREREQS)
        @$(guile (mkopen "tmp.out" "w")) \
         $(foreach X,$^,$(guile (mkwrite "$(X)"))) \
         $(guile (mkclose))
        $(LINK) < tmp.out

Il est bien sûr possible de constituer un ensemble plus complet de procédures de manipulation de fichiers. Vous pourriez, par exemple, gérer plusieurs fichiers de sortie en même temps en choisissant un symbole pour chacun et en l'utilisant comme clé d'une table de hachage, dont la valeur serait un port, puis en renvoyant le symbole pour le stocker dans une variable make.

12.2 Chargement d'objets dynamiques

Avertissement : La directive load et la capacité d'extension sont considérées comme une « préversion technologique » (technology preview) dans cette version de GNU Make. Nous vous encourageons à expérimenter cette fonctionnalité et nous apprécierons tout retour à son sujet. Nous ne pouvons toutefois pas garantir le maintien de la compatibilité ascendante dans la prochaine version. Envisagez plutôt d'utiliser GNU Guile pour étendre GNU Make (voir La fonction guile).

De nombreux systèmes d'exploitation fournissent un mécanisme de chargement dynamique d'objets compilés. Si votre système offre ce mécanisme, GNU make peut l'utiliser pour charger des objets dynamiques à l'exécution, fournissant ainsi de nouvelles capacités qui peuvent ensuite être invoquées par votre makefile.

La directive load sert à charger un objet dynamique. Une fois l'objet chargé, une fonction de « configuration » (setup) est invoquée pour permettre à l'objet de s'initialiser et d'enregistrer de nouvelles fonctionnalités auprès de GNU make. Un objet dynamique pourrait par exemple inclure de nouvelles fonctions make, et la fonction de « configuration » les enregistrerait auprès du système de gestion des fonctions de GNU make.

12.2.1 La directive load

Les objets sont chargés dans GNU make en plaçant la directive load dans votre makefile. La syntaxe de la directive load est la suivante :

load object-file

ou :

load object-file(symbol-name) …

Le fichier object-file est chargé dynamiquement par GNU make. Si object-file ne comporte pas de chemin de répertoire, il est d'abord recherché dans le répertoire courant. S'il n'y est pas trouvé, ou si un chemin de répertoire est inclus, les chemins propres au système sont alors parcourus. Si le chargement échoue pour une raison quelconque, make affiche un message et se termine.

Si le chargement réussit, make invoque une fonction d'initialisation.

Si symbol-name est fourni, il est utilisé comme nom de la fonction d'initialisation.

Si aucun symbol-name n'est fourni, le nom de la fonction d'initialisation est construit en prenant le nom de base du fichier object-file, jusqu'au premier caractère qui n'est pas un caractère valide pour un nom de symbole (les caractères alphanumériques et le tiret bas sont des caractères valides pour un nom de symbole). À ce préfixe est ajouté le suffixe _gmk_setup.

Plusieurs fichiers objets peuvent être chargés par une seule directive load, et les deux formes d'arguments de load peuvent être employées dans la même directive.

La fonction d'initialisation reçoit le nom de fichier et le numéro de ligne de l'invocation de l'opération load. Elle doit renvoyer une valeur de type int, qui doit valoir 0 en cas d'échec et une valeur non 0 en cas de succès. Si la valeur de retour est -1, GNU Make ne tentera pas de reconstruire le fichier objet (voir Comment les objets chargés sont régénérés).

Par exemple :

load ../mk_funcs.so

chargera l'objet dynamique ../mk_funcs.so. Une fois l'objet chargé, make invoquera la fonction mk_funcs_gmk_setup (supposée définie par l'objet partagé).

En revanche :

load ../mk_funcs.so(init_mk_func)

chargera l'objet dynamique ../mk_funcs.so. Une fois l'objet chargé, make invoquera la fonction init_mk_func.

Quel que soit le nombre de fois où un fichier objet apparaît dans une directive load, il ne sera chargé (et sa fonction de configuration ne sera invoquée) qu'une seule fois.

Après qu'un objet a été chargé avec succès, son nom de fichier est ajouté à la variable .LOADED.

Si vous préférez que l'échec du chargement d'un objet dynamique ne soit pas signalé comme une erreur, vous pouvez utiliser la directive -load à la place de load. GNU make n'échouera pas et aucun message ne sera produit si un objet ne parvient pas à se charger. L'objet dont le chargement a échoué n'est pas ajouté à la variable .LOADED, que l'on peut alors consulter pour déterminer si le chargement a réussi.

12.2.2 Comment les objets chargés sont régénérés

Les objets chargés suivent la même procédure de régénération que les makefiles (voir Comment les makefiles sont régénérés). Si l'un des objets chargés est recréé, make repart de zéro, relit tous les makefiles et recharge les fichiers objets. Il n'est pas nécessaire que l'objet chargé fasse quoi que ce soit de particulier pour prendre cela en charge.

C'est à l'auteur du makefile de fournir les règles nécessaires à la reconstruction de l'objet chargé.

12.2.3 Interface des objets chargés

Avertissement : Pour que cette fonctionnalité soit utile, vos extensions devront invoquer diverses fonctions internes à GNU make. Les interfaces de programmation fournies dans cette version ne doivent pas être considérées comme stables : des fonctions peuvent être ajoutées, supprimées, ou voir leur signature d'appel ou leur implémentation modifiées dans de futures versions de GNU make.

Pour être utiles, les objets chargés doivent pouvoir interagir avec GNU make. Cette interaction comprend à la fois les interfaces que l'objet chargé fournit aux makefiles et les interfaces que make fournit à l'objet chargé pour manipuler le fonctionnement de make.

L'interface entre les objets chargés et make est définie par le fichier d'en-tête C gnumake.h. Tout objet chargé écrit en C devrait inclure ce fichier d'en-tête. Tout objet chargé non écrit en C devra implémenter l'interface définie dans ce fichier d'en-tête.

En général, un objet chargé enregistre une ou plusieurs nouvelles fonctions GNU make à l'aide de la routine gmk_add_function depuis sa fonction de configuration. Les implémentations de ces fonctions make peuvent faire appel aux routines gmk_expand et gmk_eval pour accomplir leur tâche, puis renvoyer éventuellement une chaîne comme résultat du développement de la fonction.

Licence des objets chargés

Toute extension dynamique devrait définir le symbole global plugin_is_GPL_compatible pour attester qu'elle est distribuée sous une licence compatible avec la GPL. Si ce symbole n'existe pas, make émet une erreur fatale et se termine lorsqu'il tente de charger votre extension.

Le type déclaré du symbole devrait être int. Il n'a toutefois pas besoin de figurer dans une section particulière. Le code se contente d'attester que le symbole existe dans la portée globale. Quelque chose comme ceci suffit :

int plugin_is_GPL_compatible;

Structures de données

gmk_floc

Cette structure représente un couple nom de fichier/emplacement (location). Elle est fournie lors de la définition d'éléments, afin que GNU make puisse, si nécessaire, informer ultérieurement l'utilisateur de l'endroit où la définition a eu lieu.

Enregistrement des fonctions

Il n'existe actuellement qu'une seule façon pour les makefiles d'invoquer les opérations fournies par l'objet chargé : via l'interface d'appel de fonction de make. Un objet chargé peut enregistrer une ou plusieurs nouvelles fonctions, qui peuvent ensuite être invoquées depuis le makefile de la même manière que n'importe quelle autre fonction.

Utilisez gmk_add_function pour créer une nouvelle fonction make. Ses arguments sont les suivants :

name

Le nom de la fonction. C'est ce que le makefile doit utiliser pour invoquer la fonction. Le nom doit comporter entre 1 et 255 caractères et ne peut contenir que des caractères alphanumériques, le point (« . »), le tiret (« - ») et le tiret bas (« _ »). Il ne peut pas commencer par un point.

func_ptr

Un pointeur vers une fonction que make invoquera lorsqu'il développera la fonction dans un makefile. Cette fonction doit être définie par l'objet chargé.

min_args

Le nombre minimal d'arguments que la fonction acceptera. Doit être compris entre 0 et 255. GNU make le vérifie et échoue avant d'invoquer func_ptr si la fonction a été invoquée avec trop peu d'arguments.

max_args

Le nombre maximal d'arguments que la fonction acceptera. Doit être compris entre 0 et 255. GNU make le vérifie et échoue avant d'invoquer func_ptr si la fonction a été invoquée avec trop d'arguments. Si la valeur est 0, n'importe quel nombre d'arguments est accepté. Si la valeur est supérieure à 0, elle doit être supérieure ou égale à min_args.

flags

Des drapeaux qui précisent le fonctionnement de cette fonction ; les drapeaux souhaités doivent être combinés par OU (OR). Si le drapeau GMK_FUNC_NOEXPAND est donné, les arguments de la fonction ne seront pas développés avant l'appel de la fonction ; sinon ils seront développés au préalable.

Interface des fonctions enregistrées

Une fonction enregistrée auprès de make doit correspondre au type gmk_func_ptr. Elle est invoquée avec trois paramètres : name (le nom de la fonction), argc (le nombre d'arguments de la fonction) et argv (un tableau de pointeurs vers les arguments de la fonction). Le dernier pointeur (c'est-à-dire argv[argc]) sera nul (0).

La valeur de retour de la fonction est le résultat du développement de la fonction. Si la fonction ne se développe en rien, la valeur de retour peut être nulle. Sinon, ce doit être un pointeur vers une chaîne créée avec gmk_alloc. Une fois que la fonction a retourné, cette chaîne appartient à make, qui la libérera au moment opportun ; elle ne peut plus être accédée par l'objet chargé.

Fonctionnalités fournies par GNU make

GNU make exporte quelques fonctionnalités destinées à être utilisées par les objets chargés. Elles sont en général exécutées depuis la fonction de configuration et/ou les fonctions enregistrées via gmk_add_function, pour récupérer ou modifier les données avec lesquelles make travaille.

gmk_expand

Cette fonction prend une chaîne et la développe selon les règles de développement de make. Le résultat du développement est renvoyé dans une mémoire tampon de chaîne terminée par un caractère nul. Il incombe à l'appelant d'appeler gmk_free avec un pointeur vers la mémoire tampon renvoyée une fois terminé.

gmk_eval

Cette fonction prend une mémoire tampon et l'évalue comme un fragment de syntaxe makefile. Elle peut servir à définir de nouvelles variables, de nouvelles règles, etc. Elle est équivalente à l'utilisation de la fonction eval de make.

Notez qu'il existe une différence entre gmk_eval et l'appel de gmk_expand sur une chaîne avec la fonction eval : dans ce dernier cas, la chaîne sera développée deux fois ; une fois par gmk_expand, puis de nouveau par la fonction eval. Avec gmk_eval, la mémoire tampon n'est développée qu'au plus une seule fois (au moment où elle est lue par l'analyseur de make).

Gestion de la mémoire

Certains systèmes autorisent différents schémas de gestion de la mémoire. C'est pourquoi vous ne devez jamais transmettre à une fonction make de la mémoire que vous avez allouée directement, ni tenter de libérer directement de la mémoire renvoyée par une fonction make. Utilisez plutôt les fonctions gmk_alloc et gmk_free.

En particulier, la chaîne renvoyée à make par une fonction enregistrée à l'aide de gmk_add_function doit être allouée avec gmk_alloc, et la chaîne renvoyée par la fonction gmk_expand de make doit être libérée (lorsqu'elle n'est plus nécessaire) à l'aide de gmk_free.

gmk_alloc

Renvoie un pointeur vers une mémoire tampon nouvellement allouée. Cette fonction renvoie toujours un pointeur valide ; si la mémoire est insuffisante, make se termine. gmk_alloc n'initialise pas la mémoire allouée.

gmk_free

Libère une mémoire tampon qui vous a été renvoyée par make. Une fois que la fonction gmk_free a retourné, la chaîne n'est plus valide. Si NULL est passé à gmk_free, aucune opération n'est effectuée.

12.2.4 Exemple d'objet chargé

Supposons que nous voulions écrire une nouvelle fonction GNU make qui crée un fichier temporaire et renvoie son nom. Nous souhaiterions que notre fonction prenne un préfixe comme argument. Nous pouvons d'abord écrire la fonction dans un fichier mk_temp.c :

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include <gnumake.h>

int plugin_is_GPL_compatible;

char *
gen_tmpfile(const char *nm, int argc, char **argv)
{
  int fd;

  /* Compute the size of the filename and allocate space for it.  */
  int len = strlen (argv[0]) + 6 + 1;
  char *buf = gmk_alloc (len);

  strcpy (buf, argv[0]);
  strcat (buf, "XXXXXX");

  fd = mkstemp(buf);
  if (fd >= 0)
    {
      /* Don't leak the file descriptor.  */
      close (fd);
      return buf;
    }

  /* Failure.  */
  fprintf (stderr, "mkstemp(%s) failed: %s\n", buf, strerror (errno));
  gmk_free (buf);
  return NULL;
}

int
mk_temp_gmk_setup (const gmk_floc *floc)
{
  printf ("mk_temp plugin loaded from %s:%lu\n", floc->filenm, floc->lineno);
  /* Register the function with make name "mk-temp".  */
  gmk_add_function ("mk-temp", gen_tmpfile, 1, 1, 1);
  return 1;
}

Ensuite, nous écrirons un Makefile capable de construire cet objet partagé, de le charger et de l'utiliser :

all:
        @echo Temporary file: $(mk-temp tmpfile.)

load mk_temp.so

mk_temp.so: mk_temp.c
        $(CC) -shared -fPIC -o $@ $<

Sous MS-Windows, en raison des particularités de la production des objets partagés, le compilateur doit examiner la bibliothèque d'importation (import library) produite lors de la construction de make, généralement nommée libgnumake-version.dll.a, où version est la version de l'API des objets chargés. La recette qui produit un objet partagé se présentera donc sous Windows comme suit (en supposant que la version de l'API est 1) :

mk_temp.dll: mk_temp.c
        $(CC) -shared -o $@ $< -lgnumake-1

Désormais, lorsque vous exécutez make, vous verrez quelque chose comme :

$ make
mk_temp plugin loaded from Makefile:4
cc -shared -fPIC -o mk_temp.so mk_temp.c
Temporary filename: tmpfile.A7JEwd

Précédent | Suivant | Sommaire | Version originale anglaise (gnu.org)