OVH Cloud OVH Cloud

[Gnu-Makefile ou shell windows] comment faire une boucle dans un Makefile ?

9 réponses
Avatar
Aurélien Barbier-Accary
Bonjour,

ayant surtout l'habitude de travailler sous Linux, j'utilise des choses de ce type:

all:
ifdef DIRECTORIES
@for dir in $(DIRECTORIES); do \
$(MAKE) -ks -C $$dir all; \
done
endif

Maintenant je dois travailler sous Windows et Msys pose des problèmes, aussi je
travaille sans émulateur de shell Linux. Globalement je m'en sors très bien sauf
pour refaire le cas précédent.
Sur http://www.gnu.org/software/make/manual/html_chapter/make_toc.html je n'ai
rien trouvé sur les boucles dans un Makefile :-(
D'après http://www.gnu.org/software/make/manual/html_chapter/make_4.html#SEC41
il serait possible de simuler cela avec des .PHONY targets mais je n'y arrive pas.

Existe-il un moyen (simple) de faire ce que je veux avec les commandes
Gnu-Makefile ?

Comment faire cela avec le shell windows ? (mon Makefile teste l'OS et pourrait
donc s'adapter)
A partir de http://www.horstmann.com/bigj/help/windows/advanced.html j'ai essayé
d'écrire:

all:
ifdef DIRECTORIES
for %d in (Rep1,Rep2) do $(MAKE) $(OPTIONS) -C %d all
endif

mais ça ne fonctionne pas (ni avec %%d au lieu de %d).
Si jamais cette dernière version fonctionnait, il resterait alors le problème de
construire une liste (Rep1,Rep2,Rep3) à partir de "Rep1 Rep2 Rep3". Pour cela,
comment faire avec patsubst ?
@echo $(patsubst % ,%,,$(DIRECTORIES)) ne donne pas le bon résultat.


Merci d'avance pour toutes les infos que vous pourrez me donner sur ces prblèmes.

Aurélien.

9 réponses

Avatar
Jean-Marc Bourguet
Aurélien Barbier-Accary writes:

Existe-il un moyen (simple) de faire ce que je veux avec les commandes
Gnu-Makefile ?


Peut-etre en utilisant la recursion. C'est simple, mais pas
necessairement efficace. Je chercherais plutot a faire fonctionner
Msys ou cygwin.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
kanze
Aurélien Barbier-Accary wrote:

[Ça sort vraiment du sujet C++, mais passons...]

ayant surtout l'habitude de travailler sous Linux, j'utilise
des choses de ce type:

all:
ifdef DIRECTORIES
@for dir in $(DIRECTORIES); do
$(MAKE) -ks -C $$dir all;
done
endif

Maintenant je dois travailler sous Windows et Msys pose des
problèmes, aussi je travaille sans émulateur de shell Linux.
Globalement je m'en sors très bien sauf pour refaire le cas
précédent.


La solution la plus simple, à mon avis, c'est d'installer
CygWin. Si ton makefile contient des ifdef etc., il est déjà un
GNUmakefile ; puisque tu imposes une partie des outils GNU,
pourquoi pas simplement exiger le tout.

Sur
http://www.gnu.org/software/make/manual/html_chapter/make_toc.html
je n'ai rien trouvé sur les boucles dans un Makefile :-(
D'après
http://www.gnu.org/software/make/manual/html_chapter/make_4.html#SEC41
il serait possible de simuler cela avec des .PHONY targets
mais je n'y arrive pas.


Pour ton exemple, ça doit marcher :

all : subdirs

.PHONY subdirs
subdirs : $(DIRECTORIES)

$(SUBDIRS):
$(MAKE) -C $@

Au moins selon le manual ; j'utilise en fait la même technique
que toi, ci-dessus (et j'exige la présence de CygWin sous
Windows -- mes makefiles font aussi utilisation de awk et de
sed).

Sinon, il existe une fonction $(foreach...) ; je crois que
quelque chose comme :

define subdirHandling
.PHONY $(1)
$(1) :
$(MAKE) -C $@
endef

all : $(DIRECTORIES)

$(foreach dir,$(DIRECTORIES),$(eval $(call subdirHandling,$(dir))))

ferait l'affaire. (Je m'en suis servi pour quelque chose de
semblable chez moi. Mais justement, c'était chez moi, et je me
trouve au travail à l'instant. Je ne peux donc pas vérifier la
syntaxe exacte.)

Enfin, il y a toujours la méthode brutale : tu as l'if, et les
fichiers de make sont des fonctions. Alors :

all : recurse

.PHONY recurse
recurse: $(DIRECTORIES)
ifneq ($(DIRECTORIES),)
$(MAKE) -C $<
$(MAKE) 'DIRECTORIES=$(wordlist 2,10000,$^)'
endif

Si le nombre de répertoires n'est pas trop grand, ça risque
d'être acceptable. (En fait, je crois que je mettrais la
récursion dans un fichier de make à part, avec :

all :
$(MAKE) -f makeInDirs.mk

dans le fichier principal.)

En tant que langage, si on considère les sous-make comme des
fonctions, GNU make est « Turing complete ». On peut donc faire
tout ce qu'on pourrait faire dans n'importe quel langage de
programmation. Mais en tant que langage, il est encore plus
tordu, et de loin, que les templates en C++.

Existe-il un moyen (simple) de faire ce que je veux avec les
commandes Gnu-Makefile ?


Si on laisse tomber le « simple », certainement.

Comment faire cela avec le shell windows ? (mon Makefile teste
l'OS et pourrait donc s'adapter)


Pourquoi utiliser le shell de Windows ? Tu exiges déjà le make
de GNU. Vas-y, exige aussi CygWin et bash. Tu rendras la vie
beaucoup plus facile.

A partir de
http://www.horstmann.com/bigj/help/windows/advanced.html j'ai
essayé d'écrire:

all:
ifdef DIRECTORIES
for %d in (Rep1,Rep2) do $(MAKE) $(OPTIONS) -C %d all
endif


Je ne connais pas bien Windows, mais il me semble que la syntaxe
pour une variable là est %nom%. Essayer donc avec %d% à la place
de %d.

mais ça ne fonctionne pas (ni avec %%d au lieu de %d). Si
jamais cette dernière version fonctionnait, il resterait alors
le problème de construire une liste (Rep1,Rep2,Rep3) à partir
de "Rep1 Rep2 Rep3". Pour cela, comment faire avec patsubst ?

@echo $(patsubst % ,%,,$(DIRECTORIES)) ne donne pas le bon résultat.


@echo $(foreach dir,$(dirs),$(dir),)

ajoute un , à la fin de chaque nom. Pour éviter le virgule à la
fin, quelque chose comme :
@echo $(firstword $(dirs)) $(foreach dir,$(wordlist
2,1000,$(dirs)),,$(dir))
fait l'affaire, mais si des espaces en rab pose un problème, je
ne connais pas de solution de tête.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
Fabien LE LEZ
On 26 Oct 2005 03:18:12 -0700, "kanze" :

La solution la plus simple, à mon avis, c'est d'installer
CygWin.


Ou MinGW ?

Y'a un truc qui me gonfle dans Cygwin : il ne semble pas qu'on puisse
avoir deux installations indépendantes sur la même machine, même si
les deux font des trucs totalement différents (par exemple, serveur
SSH pour des backups et programmation). Du coup, ça introduit un
couplage artificiel qui me déplait.

Je ne connais pas bien Windows, mais il me semble que la syntaxe
pour une variable là est %nom%.


Pas dans le cas du "for".

Essayer donc avec %d% à la place
de %d.


En ligne de commande directe, ce serait "%d". Mais dans un .bat, il
faut doubler le "%", et ça donne "%%d". Dans un Make, je ne sais pas :
ça dépend entièrement de la version de make utilisée.

[Make] Mais en tant que langage, il est encore plus
tordu, et de loin, que les templates en C++.


Les .bat de Windows sont moins puissants, mais au moins aussi tordus.

Avatar
Aurélien Barbier-Accary
On 26 Oct 2005 03:18:12 -0700, "kanze" :
La solution la plus simple, à mon avis, c'est d'installer
CygWin.


Ou MinGW ?

Y'a un truc qui me gonfle dans Cygwin : il ne semble pas qu'on puisse
avoir deux installations indépendantes sur la même machine, même si
les deux font des trucs totalement différents (par exemple, serveur
SSH pour des backups et programmation). Du coup, ça introduit un
couplage artificiel qui me déplait.



J'utilise mingw (il me semblait que cygwin était trop lourd) mais quand j'active
msys (dabs le %PATH%) j'ai des soucis avec certains répertoires du %PATH% qui
contiennent des comme délimiteurs et donc Msys les interprète comme des
caractères d'échappement :-(
D'un autre côté je ne connaissais pas encore les fonctions patsubst et subst
donc il faudrait que je recreuse l'idée.

Pour le moment je vais essayer l'idée de James avec un $(foreach ,,$(eval ...)).
Je ne connaissais pas eval mais il semble que ça pourrait me suffir.

Merci à vous deux, je vais tester ça cet am dès que j'aurai un moment de libre
et je vous tiens au courant.


Avatar
Aurélien Barbier-Accary
Aurélien Barbier-Accary writes:


Existe-il un moyen (simple) de faire ce que je veux avec les commandes
Gnu-Makefile ?



Peut-etre en utilisant la recursion. C'est simple, mais pas
necessairement efficace. Je chercherais plutot a faire fonctionner
Msys ou cygwin.

A+



oui mais c'est parce que j'avais un problème de ce côté là que j'ai commencé
d'essayer de faire sans Msys. Et puis je me suis dit que ce serait moins
exigeant pour l'utilisateur (qui doit recompiler).

Merci.


Avatar
Aurélien Barbier-Accary
[Ça sort vraiment du sujet C++, mais passons...]


Oui désolé mais je ne savais pas où demander de l'aide pour ça :-o


Pour ton exemple, ça doit marcher :

all : subdirs

.PHONY subdirs
subdirs : $(DIRECTORIES)

$(SUBDIRS):
$(MAKE) -C $@


je n'y suis pas parvenu car je ne vois pas comment gérer des $@ différents (pour
les règles all, clean et cleanall par exemple).
Du coup j'ai essayé de fouiller du côté de ta proposition suivante.


Sinon, il existe une fonction $(foreach...) ; je crois que
quelque chose comme :

define subdirHandling
.PHONY $(1)
$(1) :
$(MAKE) -C $@
endef

all : $(DIRECTORIES)

$(foreach dir,$(DIRECTORIES),$(eval $(call subdirHandling,$(dir))))

ferait l'affaire. (Je m'en suis servi pour quelque chose de
semblable chez moi. Mais justement, c'était chez moi, et je me
trouve au travail à l'instant. Je ne peux donc pas vérifier la
syntaxe exacte.)


chez moi j'avais des messages d'erreur avec la fonction "eval" (non définie
semble-t-il) et j'ai donc une demi-solution avec la fonction "shell" et un appel
à la commande shell "call":

DIRECTORIES := $(patsubst %,$(MAKE)_-C_%_$$@,$(DIRECTORIES))

.PHONY: truc
truc:
ifdef DIRECTORIES
@echo $(foreach cmd, $(DIRECTORIES),
$(shell call $(subst $$@,$@,$(subst _, ,$(cmd)))))
else
@echo FEUILLE du Makefile
endif

L'inconvénient de "foreach" c'est qu'elle renvoie une liste en remplaçant les
sauts de ligne par des espaces et du coup mon affichage est tout moche :-(


Enfin, il y a toujours la méthode brutale : tu as l'if, et les
fichiers de make sont des fonctions. Alors :

all : recurse

.PHONY recurse
recurse: $(DIRECTORIES)
ifneq ($(DIRECTORIES),)
$(MAKE) -C $<
$(MAKE) 'DIRECTORIES=$(wordlist 2,10000,$^)'
endif

Si le nombre de répertoires n'est pas trop grand, ça risque
d'être acceptable. (En fait, je crois que je mettrais la
récursion dans un fichier de make à part, avec :

all :
$(MAKE) -f makeInDirs.mk

dans le fichier principal.)


ça je ne l'ai pas encore bien compris ni testé...


En tant que langage, si on considère les sous-make comme des
fonctions, GNU make est « Turing complete ». On peut donc faire
tout ce qu'on pourrait faire dans n'importe quel langage de
programmation. Mais en tant que langage, il est encore plus
tordu, et de loin, que les templates en C++.


je ne suis pas encore convaincu qu'on puisse tout faire ;-p mais ce qui est sûr
c'est que ce n'est pas très intuitif :-)

Merci pour toutes ces propositions détaillées.

Avatar
kanze
Aurélien Barbier-Accary wrote:
[Ça sort vraiment du sujet C++, mais passons...]


Oui désolé mais je ne savais pas où demander de l'aide pour ça
:-o


Peut-être des groupes d'Unix ? (Make a ses origines dans Unix,
et au moins le groupe francophone semble considère Linux et les
outils GNU « Unix ».) Mais je crois qu'on pourrait tolérer aussi
un petit écart ici, pourvu qu'il ne se réproduit pas trop
souvent.

Pour le ramener un tout petit peu vers C++, le Gabi-lib utilise
des techniques semblable -- des « programmes » make à la place
de autoconf ou de jam (utilisé par Boost). Je ne sais pas si
c'est réelement la meilleur solution, mais c'est la voie que
j'ai choisi il y a longtemps, et dans la mesure où ça marche, je
n'ai pas pris le temps jusqu'ici à considérer les alternatifs.

Si tu vas à gabisoft.free.fr, en fouillant un peu, tu dois
pouvoir trouver une version de mes fichiers de make. Mais je te
préviens que la site n'est pas encore réelement prète à
accueillir le public, et que la version des makefiles là est
assez ancienne. Et assez bricolé -- quand je les ai commencé, je
ne m'y connaissais pas trop, et surtout, je n'avais pas pensé
d'en faire autant ; c'est un exemple d'un programme qui a grandi
par accrétion plutôt que par conception.

Pour ton exemple, ça doit marcher :

all : subdirs

.PHONY subdirs
subdirs : $(DIRECTORIES)

$(SUBDIRS):
$(MAKE) -C $@


je n'y suis pas parvenu car je ne vois pas comment gérer des
$@ différents (pour les règles all, clean et cleanall par
exemple). Du coup j'ai essayé de fouiller du côté de ta
proposition suivante.


C'est un exemple que j'ai calculé de la doc. Je me suis jamais
servi ; je ne peux donc pas dire plus.

Sinon, il existe une fonction $(foreach...) ; je crois que
quelque chose comme :

define subdirHandling
.PHONY $(1)
$(1) :
$(MAKE) -C $@
endef

all : $(DIRECTORIES)

$(foreach dir,$(DIRECTORIES),$(eval $(call subdirHandling,$(dir))))

ferait l'affaire. (Je m'en suis servi pour quelque chose de
semblable chez moi. Mais justement, c'était chez moi, et je
me trouve au travail à l'instant. Je ne peux donc pas
vérifier la syntaxe exacte.)


chez moi j'avais des messages d'erreur avec la fonction "eval"
(non définie semble-t-il) et j'ai donc une demi-solution avec
la fonction "shell" et un appel à la commande shell "call":


La fonction s'appelle bien eval, et je m'en sers. C'est
peut-être une question de la version de make, mais j'imagine que
tout make un peu récent doit l'avoir. (Pourvu, évidemment, que
ce soit un make de GNU.)

Je suis un peu pris de temps cette semaine, mais j'essaierai de
régarder ce week-end, pour voir exactement ce que j'ai fait.

DIRECTORIES := $(patsubst %,$(MAKE)_-C_%_$$@,$(DIRECTORIES))

.PHONY: truc
truc:
ifdef DIRECTORIES
@echo $(foreach cmd, $(DIRECTORIES),
$(shell call $(subst $$@,$@,$(subst _, ,$(cmd)))))
else
@echo FEUILLE du Makefile
endif

L'inconvénient de "foreach" c'est qu'elle renvoie une liste en
remplaçant les sauts de ligne par des espaces et du coup mon
affichage est tout moche :-(


Ce n'est pas le foreach, c'est « shell » qui fait ça.

Enfin, il y a toujours la méthode brutale : tu as l'if, et les
fichiers de make sont des fonctions. Alors :

all : recurse

.PHONY recurse
recurse: $(DIRECTORIES)
ifneq ($(DIRECTORIES),)
$(MAKE) -C $<
$(MAKE) 'DIRECTORIES=$(wordlist 2,10000,$^)'
endif

Si le nombre de répertoires n'est pas trop grand, ça risque
d'être acceptable. (En fait, je crois que je mettrais la
récursion dans un fichier de make à part, avec :

all :
$(MAKE) -f makeInDirs.mk

dans le fichier principal.)


ça je ne l'ai pas encore bien compris ni testé...


C'est de la récursion, pûr et simple. L'action pour le cible
recurse fait deux chose : il invoque make -C sur le premier mot
dans $(DIRECTORIES), et ensuite, il invoque make en forçant la
variable $(DIRECTORIES) à contenir tous les mots sauf le
premier.

En gros, dans GNU make, $(firstword $(list)) correspond à (car
list) et $(words 2,10000,$(list)) à (cdr list). C'est du Lisp
caché. (Je suppose ici qu'il n'a jamais de list avec plus de
10000 éléments. Si c'est le cas, de toute façon, ça risque de
poser des problèmes, parce que chaque récursion crée un nouveau
processus. Dans ton cas, je ne crois pas que ça soit un
problème. Si tu as plus de 10000 sous-répertoires directs, tu
vas avoir des problèmes déjà au niveau du système.)

En tant que langage, si on considère les sous-make comme des
fonctions, GNU make est « Turing complete ». On peut donc
faire tout ce qu'on pourrait faire dans n'importe quel
langage de programmation. Mais en tant que langage, il est
encore plus tordu, et de loin, que les templates en C++.


je ne suis pas encore convaincu qu'on puisse tout faire ;-p


C'est Turing complet. Donc, si on peut le faire en C++, tu peux
le faire en GNU make. En théorie, au moins ; je n'ai pas de
problème d'écrire une boucle de plusieurs millions de fois en
C++, tandis que si j'utilise la récursion en make, plusieurs
millions de processus va planter le système. La solution make
exige beaucoup plus de résourses que la solution C++.

En fait, au delà d'un certain niveau de complexité, je crois que
je ferais plutôt quelque chose du genre :

all:
makemake.sh | make -f -

avec un script de shell (probablement principalement de l'AWK)
pour générer le fichier make sur mesure.

(Note que c'est le genre de chose que tu veux développer très
doucement et itérativement, parce que la mise au point ne doit
pas être des plus facile.)

mais ce qui est sûr c'est que ce n'est pas très intuitif :-)


C'est le moindre des choses qu'on peut dire.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Aurélien Barbier-Accary
Encore merci pour toutes ces infos, je vais aller voir les Makefile de ta librairie.
Avatar
James Kanze
Aurélien Barbier-Accary wrote:

Encore merci pour toutes ces infos, je vais aller voir les
Makefile de ta librairie.


Qui ne font pas encore tout ce que tu cherches.

J'ai régardé chez moi. J"utilise $(eval $(call ...)) dans deux
fichiers de make. Le plus simple, c'est :

testInputs = $(patsubst %,testData/%.in,$(testSets))
testOutputs = $(patsubst %,testData/%.out,$(testSets))
testData = $(testInputs) $(testOutputs)

runtest : $(testData)

define genTestData
testData/$(1).in testData/$(1).out : $(workingDir)/gen$(1)TestData

$(encodingSourcesDir)/UnicodeData.txt
mkdir -p testData
$(beforeRun) ; $(workingDir)/gen$(1)TestData -D
$(encodingSourcesDir)
endef

$(foreach test,$(testSets),$(eval $(call genTestData,$(test))))

$(workingDir)/gen%TestData : $(workingDir)/gen%TestData.o
$(MAKE) -f $(makefilesDir)/makeBinary.mk target=$@
main=$< "usedComponents=$(usedComponents)"

$(workingDir)/gen%TestData.o : gen%TestData.cc
$(compile)

Cette partie se situe après l'include standard pour un
composant, qui définit déjà runtest comme dépendant de
$(workingDir)/test, ainsi que l'action qui lui
correspond (c-à-d executer les tests). La règle pour runtest ici
ne fait qu'ajouter des dépendances des fichiers des données.

--
James Kanze mailto:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34