[linux-l] Make und Ant (war: Warum gibt es keine einheitliche Dokumentation?)
Steffen Dettmer
steffen at dett.de
Fr Feb 9 00:21:30 CET 2007
* Volker Grabsch wrote on Sun, Feb 04, 2007 at 13:45 +0100:
> On Sun, Jan 21, 2007 at 04:46:04PM +0100, Steffen Dettmer wrote:
> > Das ist ja der Kernkonflikt. Macht man es unabhängig, wird Make
> > dezentral (rekursiv) und weniger effizient.
>
> Nein, das ist nicht der Kernkonflikt. Lange Bauzeiten sind ein kleiner
> Aspekt, der ebenfalls nerven kann.
Effizienz kann man doch nicht auf lange Bauzeiten reduzieren! Ausserdem
ist es gerade hier auch egal, da ist kein Unterschied (es sei denn, das
Makefile ist halt falsch), es wird in beiden Fällen gebaut, was
notwendig ist. Einmal weil make das weiss, einmal weil es automake ihm
sagen muss.
Ich meinte den Zielkonflikt zwischen zentral und orthogonal.
> Da du den Artikel offenbar noch nicht genauer angeschaut hast: Mach es
> mal. Er ist wirklich lesenswert.
> (zur Erinnerung:
> http://members.pcug.org.au/~millerp/rmch/recu-make-cons-harm.html
> ... erster Suchtreffer in Google mit den Suchbegriffen "recursive make")
Ich hab seit langem einen Ausdruck (jedenfalls gehe ich davon aus, dass
es der gleiche, fast schon "klassische" Artikel ist).
> Dann ist vielleicht folgender Ergänzungs-Artikel interessant für dich:
> http://www.xs4all.nl/~evbergen/nonrecursive-make.html
Komisch:
CC = ./build/ccd-gcc
COMP = $(CC) $(CF_ALL) $(CF_TGT) -o $@ -c $<
%.o: %.c
$(COMP)
da ist die builtin-rule aber besser... Ja, dependencies sind ein
komplexes Thema, kriegt auch automake nicht wirklich hin. Irgendwo hat
man dann immer eine Kombination, wo was falsch ist (meist Windows,
entweder sind \ drin oder \r oder irgendwas anderes).
Na ja, wie ich schon schrieb, find ich automake da noch am besten.
"GNU Autoconf [+automake] ist Mist, aber besser als alles, was ich sonst
kenne" (um mal einen Kollegen zu zitieren) :)
Weiss nicht, ob automake schon 2002 mit verzeichnisübergreifenden Regeln
korrekt umgehen konnte, vermute mal fast, das ging da schon. Na ja, hat
schon Gründe, warum automake Schwächen hat: es ist komplex und geht halt
nicht so einfach besser zu machen.
> Ein sehr schönes praktisches Beispiel:
> http://www.mindcontrol.org/~hplus/makesample.html
Ich finde es überhaupt nicht schön: es erkennt keine "verbotenen"
Abhängigkeiten:
steffen at link:~/makesample> head -1 lib2/file-in-lib2.cpp
#include "app1/app1.h"
steffen at link:~/makesample> make
Built libraries: bin/lib1.a bin/lib2.a
Build apps: bin/app1 bin/app2
Das muss aber knallen, weil lib nicht von app1 abhängen darf, nur
umgekehrt. Ganz schlimm sowas. Hatten paar Mannwochen Arbeit, weil sowas
in einem Projekt passierte: da waren alle header global sichtbar (geht
bei MS Dev Studio fix, wenn man aus Versehen oder mit Absicht alle
Projekte markiert, und dann includes einträgt). Führt dazu, dass man
lib1 nicht mehr bauen kann (wenn man app1 nicht hat). Kommt dann zum
bösen Erwachen, wenn man lib1 in app3 braucht. Tja, und dann hat man es
lange nicht gemerkt und hat viel Spass :)
Na ja, ist von 1997, zehn Jahre her. Bei kleinen Projekten (< 100.000
Zeilen Code oder so) geht das ja, solange man nichts wiederverwenden
will.
Um das zu umgehen, kann man folgenden Trick machen:
lib1/src/lib1/l1.h
lib2/src/lib2/l1.h
app1/src/app1/a1.h
(die drei würde ich app1_sys nennen)
alle includen <prefix/file.h>, also lib1/l1.h. INCLUDES enthalten immer
nur die "nach unten folgenden" Module im gerichten Graphen (also lib2
hat "-Ilib1/src -Ilib2/src" usw).
> > Ich persönlich bewerte den Vorteil "Unabhängigkeit" und "Teile und
> > Herrsche" (im Hinblick auf Refakturisierbarkeit, Wartbarkeit usw)
> > wesentlich höher als ein effizientes Make.
>
> Ich auch. Aber kann ein Modul, dass praktisch nur in deinen Apps
> eingesetzt wird, tatsächlich "unabhängig" sein? Könntest du jedes
> deiner "Module", auch wenn sie von mehreren Applikationen gemeinsam
> genutzt werden, auch als eigenständige RPM-Pakete vertreiben?
Die wichtigen servermodule können das; da gibts i.d.R. sogar RPMs, auch
wenn die nie installiert werden. Hilfreich ist, wenn man so jeder
(wichtigen) lib mindestens eine "minimal-Konfiguration" hat und bauen
kann, idealerweise in allen wichtigen Varianten (in C:
#define-Kombinationen bzw. mit und ohne libxyz) und den wichtigen
(aktiven) Branches (sind meist nur ein, zwei). Da hab ich ein Script,
dass versucht die zu bauen. Dann seh ich auf einer "webseite", welche
Varianten gehen und welche nicht (aha, "liba geht nicht mit libb, aber
mit libc und allein" oder sowas).
Der Schlüssel ist aber, dass (im Beispiel oben) lib1 so gebaut wird "wie
immer". ".." darf nicht benutzt werden und über die Konfiguration wird
app1 oder lib2 nicht "sichtbar gemacht" (also keine INCLUDES). Selbst
wenn man was brutales probiert (include ../../../app1/src/app1/a1.h),
knallt es einen Tag später, wenn lib1 allein gebaut werden soll. Aber
#include "../..." zählt schon zu "krimineller Energie"; gegen Gewalt
kann man nicht schützen, irgendwann ist Schluss klar. Wichtig ist halt,
Flüchtigkeits- und Faulheitsfehler zu vermeiden.
> Wenn ja, wäre das sinnvoll und macht ihr das auch?
Ich finde es sinnvoll; hat auch den Vorteil, dass man Änderungen in lib1
"im kleinen" testen kann. Ich kann mir ja ein "app1_sys" branch machen
(für neues Feature oder so) und mir ein "lib1_sys" nehmen/machen, was
lib1 aus dem app1_sys" branch verwendet. Dann kann ich da im kleinen
testen (also bis einschliesslich unittests und vielleicht einem Beispiel
von lib1). Ist übersichtlich, sauber schnell und einfach.
Integrations-testen kann ich dann in app1_sys (im Branch). Da würde ich
das auch mergen. Klingt jetzt vielleicht kompliziert, ist's aber gar
nicht (so wirklich). Man nimmt sich halt passende Systeme, wählt die
Branches, die man zum test nehmen möchte (bastelt sich also die SCM
Konfiguration zusammen) und kann immer mit dem Umfang arbeiten, den man
gerade braucht (also kein Ballast).
So eine (ziemlich) erfolgreiche Geschichte hatte ich gerade gestern.
Hatte ein app1_sys und app2_sys. Branch auf app1_sys. Da getestet, dann
app2_sys mit app1_sys-Branch, integriert. Integrationstest war
vorgestern, also gestern alle app1_sys und app2_sys Branches gemergt
(wir hatten dann noch ein paar andere Features/Utilities da gemacht,
weil die "ran waren" und wir die so benutzen konnten und gleich Zeit
sparten, war eh schon länger gewünsch-plant :)). app2_sys trunk gebaut,
geht. Weil Vorsicht und elterliche Verwandtschaft mütterlicherseits mit
Pozellankiste app2_sys release candidate aus älterem Branch und trunk in
Testsystem installiert.
Und läuft nicht.
lol. Am Ende war nur ein Startskript falsch, aber wurde nicht gemerkt.
Warum? Weil der alte Code so blöde reflection API benutzte, und so nicht
auffiel, dass bei *dieser* Konfiguration ein bestimmer
default-Construktor benutzt wurde, den es nicht mehr gab (weil andere
Construktor hinzugefügt und automatisch erzeugte damit ja nicht mehr
automatisch erzeugt wird) :-) Gegen reflection ist halt kein Kraut
gewachsen. Untestbar.
> Was ich beschrieben habe, geht auch mit Automake sehr gut. Das
> einzige, was mich daran hindert, ein Unterverzeichnis als
> eigenständige Lib zu compilieren und zu starten, ist ein billiges
> Makefile.am und eine sehr kleine configure.ac. Diese beiden Dateien
> hinzuzufügen und das Ding getrennt als Paket zu behandeln, das ist ne
> Sache von höchstens ner halben Stunde (inkl. Testen).
>
> Dieser Aufwand ist einmalig und kann jederzeit bei Bedarf erfolgen.
Ich habe heute ca. 2 Stunden an einem Detailproblem gesessen. Bei
englischen Windows gings nicht, weil "c:/program files/java/bin/tool"
ein Leerzeichen enthielt und ich das gerne mit Quotes ins $(MACRO) haben
wollte. Hab am Ende aufgegeben und doch fünf oder sechs Makefile.ams
angepasst.
GNU Make "kann" dass, wenn man make TOOL='"c:/program files/.."' schreibt.
Das hab ich so bei autoconf/automake nicht hinbekommen. Bestimmt ne
Kleinigkeit falsch gemacht. Hab dann einfach "TOOL" als Aufruf
geschrieben, so, geht auch :)
Ohh, und libtool ist so einfach auch nicht, falls man es braucht.
Genialmagisch, aber nicht einfach. Ich hab z.B. noch nichtmal
rausbekommen, wie man statisch *und* dynamische Libs immer beide baut.
Na ja, aber auch kaum mit beschäftigt.
> Macht man das zu früh, handelt man sich längere Build-Zeiten und
> aufwändigere Wartung ein. Das ist einer der Hintergründe dieser Python-
> Richtlinie: Solange das Modul nur in wenigen Applikationen verwendet
> wird, sollte es mit diesen jeweils zusammen vertrieben werden. Erst,
> wenn es *wirklich* eigenständig genutzt wird, auch von völlig anderen
> Personen und Projekten, dann ist es sinnvoll, das Ding als extra Paket
> zu vertreiben.
Ja, kann man aber nicht vergleichen. Ist so aber eine sehr sinnvolle
Regel. Bei uns sind libs halt so SCM Componenten. Dann machste Dir ein
system, schmeisst die libs rein, die Du brauchst (und die, die die libs
brauchen :)) und wenn Du Glück hast, baut das wenigstens auf einer
Platform und die ist ähnlich der, die Du brauchst ;)
Aber klar, für ein 10.000 oder gar nur 1.000 Zeilen Modul macht man
sowas nicht unbedingt. Bringt ja nix, mit 1000 Zeilen Building 100
Zeilen Codecopy zu sparen, klar!
Wir machen auch solche Kompromisse; da gibt's vielleicht schon ein
extra Unterverzeichnis, aber ist "mit in anderer SCM Komponente drin".
Kann man dann leicher refakturieren. Falls man das macht. Oft macht man
das auch gar nicht, weil man eh oft nur 20% der lib-Funktionen benutzt -
sind's so halt nur 15%, kostet ja bloss paar Compiler-Sekunden (oft
billiger, als Entwicklerstunden :))
> > > Moment. Mit scheint, du übersiehst die Möglichkeit, dass Automake auch
> > > selbst andere Automake-Dateien includen kann.
> >
> > Also, ich weiss, dass automake andere Makefiles includen kann. Dabei
> > included das automake die aber schon (also nicht make!),
>
> Finde ich gut. So muss es sein.
>
> > was einige Nachteile hat (aber unterm Strich die Reproduzierbarkeit
> > wohl ein bissel vereinfacht, daher schützt dieser "Bug" wohl
> > vielleicht ein bisschen vor Dingen, die man nicht machen sollte und
> > ist daher evlt. ein "Feature" :)).
>
> Versteh ich nicht. Wo ist das Problem?
Wenn man bedingt included (also if a: rules.a, if b: rules.b) wird dass
einmal von config.status geschrieben und ist dann "fest". Die
Alternative wird nichtmal syntaktisch geprüft :) automake schreibt vor
die nicht-aktiven Zeilen "#" und gut ist :)
Wenn man nun "-include helper.mak" schreibt und helper.mak vom Makefile
anlegt (was mit GNU Make geht! Hammer!), dann kommentiert automake das
-include aus (weil gibt's nicht), und GNU Make lädt helper.mak nie, weil
es es nicht kennt :)
Aber wie gesagt, "es schützt vor Dingen, die man nicht machen sollte".
Richtig ist hier natürlich, helper.mak vor automake (also vor oder von
configure) anzulegen. Dann geht da auch x_SOURCES (was ja statisch
ist!!! Erzeugt Regeln pro Eintrag! Muss man aufpassen!).
> Das eingebundene Makefile ist schließlich nicht dazu da, das Modul
> eigenständig in eine Shared-Library umzuwandeln, oder? Wenn dem so
> wäre, wär das Ding schon ein eigenständiges Paket und nicht mehr
> Bestandteil des Pakets "app1".
Praktisches Beispiel war hier, dass während des Bauens das Makefile
entstand (da war u.A. ne Dateiliste drin), die dann via Abhängigkeit
neugeladen wurde und benutzt werden sollte. Geht halt nicht. Jetzt wird
es vorher erzeugt und ist sauber. Geht durch "make distcheck" :)
Aber schon geil, dass bei automake ggf. das Makefile zuerst automatisch
aktualiert wird und dann die neuen Regeln gleich gehen :)
> Ich meine, das Makefile (bzw. Makefile.am.inc) muss doch lediglich
> dafür sorgen, dass es auch vom Haupt-Makefile.am der "app2/" und
> "app3" mit eingebunden werden kann.
Na nee, normalerweise darf app keine andere einbinden, dann wäre sie ja
abhängig (falls app eine Applikation und keine Library ist).
> > Wäre mir neu, wenn ein automake ein automake includen kann (und
> > natürlich am Ende was sinnvolles rauskommt!!!
(genauer: das geht, wenn man es vorher erzeugt)
> Das meinte ich auch nicht, und das finde ich auch nicht sinnvoll.
Na ja, mit vorher erzeugten schon: nämlich wenn man für automatisch
erzeugte Sourcen auch automatisch automake-Rules erzeugen möchte (also
x_SOURCES).
> Wer weiß, vielleicht müsste das Modul eh noch eine ganz andere API
> kriegen, wenn es für projekt12 wiederverwendbar sein soll, das mit
> euren bisherigen app1, app2 und app3 nichts gemeinsam hat.
Dann hat man aber was falsch gemacht :) Vermutlich will man es dann
lieber doch nicht wiederverwenden. Oder es ist falsch und man möchte es
auch für app1, app2 und app3 verbessern.
> Früher hätte ich mich auch auf jede globale Form der
> Wiederverwendbarkeit eingerichtet. Und es ist ja auch nichts falsch
> daran, gewisse einfache Grundsätze zu befolgen. Aber alles, was an
> Aufwand darüber hinausgeht, gleicht IMHO der Verallgemeinerung aus
> einem einzigen Beispiel.
ja klar, was nur einer benutzt, wird ja nicht wieder verwendet, klar.
> Modul1 wird nur in den eigenen App1, App2 und App3 eingesetzt, aber es
> wird ein Wartungs-Aufwand und Overhead im Build-System betrieben, als
> ob es weltweit in hunderten von Projekten eingesetzt wird. Das ist
> Wunschdenken. Es wäre eine großartige Leistung, wenn auch nur ein
> einziges dieser Module dermaßen berühmt wird.
berühmt ist ja egal, bloss wenn der Kollege app1 nichtmal starten kann
(weil er nur app2 kennt), möchte er bitte app1 auch nicht wegen modul1
lernen müssen :)
> Das stimmt. Da ist eine gewisse Inkonsistenz. Ich muss auch zugeben,
> dass For-Schleifen in Makefiles bei mir immer ein mulmiges Gefühl
> verursachen, obwohl ich damit noch keine schlechten Erfahrungen gemacht
> habe.
Das ist IMHO logisch sehr ähnlich zu "rekursive make". Man umgeht Make
damit.
> > > Das schon. Dennoch möchte ich alles, was gut gegangen ist,
> > > *übersichtlich* dargestellt bekommen. Das hat wirklich enorme
> > > Vorteile. Ich habe festgestellt, dass diesen Zusatzaufwand mit den
> > > Silent-Optionen und echo-Befehlen im Nachhinein richtig Zeit spart.
> > > Weil man eben viel, viel schneller sieht, was los ist.
> >
> > Find ich Quatsch. Was gut gegangen ist, interessiert mich eigentlich
> > überhaupt nicht. Also silent. Wenn aber ein Fehler auftrat, möchte ich
> > möglichst viele Details, z.B. auch, ob ein bestimmtes Submodul gebaut
> > wurde oder nicht und ob es Warnungen gab etc.
> >
> > Verstehe nicht, wo silent plus echo Zeit spart?
>
> Du siehst, an welcher Stelle es fehlgeschlagen ist bzw. wieviel schon
> korrekt gebaut wurde. Diese Zusatzinfo war für mich immer sehr
> praktisch. Nicht unbedingt beim Compilieren von C-Programmen, obwohl
> die die Ausgabe vom Kernel recht schick finde.
mmm... wenn make -s nicht geklappt hat, starte ich meist ein make im vim
und drücke dann F4. Dass springt dann zum ersten Fehler. Leider nur
zeigt es nur die erste Meldungszeile an.
> > Ich finde, entweder macht man ein "build" (erwartet Erfolg, will keine
> > Ausgaben etc) oder "entwickelt" (will Fehler genau sehen etc). Ersteres
> > hätte idealerweise einen Prozentbalken. Zweiteres erzeugt
> > menschenverständliche Ausgaben, die man mit einem Tool betrachtet (vim).
> > Find ich jedenfalls.
>
> Ein "Baue Modul X ..." fände ich sehr viel aussagekräftiger als einen
> Prozentbalken.
wenn man es wissen will (Aussagekraft möchte), ist automake prima, es
schreibt sogar den Dateinamen hin :) Wenn man es nicht wissen möchte,
wäre Prozentbalken cool, weil man meist nur wissen möchte, wie lange es
noch dauert. Gut, ETA muss noch dazu.
> Die sind eh recht ungenau, und so vieles ich auch manchmal beim
> Programmieren vermisse: Einen Build-Prozentbalken ganz sicher nicht.
> ;-)
ich schrieb ja:
> > Ich finde, entweder macht man ein "build" (erwartet Erfolg, will keine
> > Ausgaben etc) oder "entwickelt" (will Fehler genau sehen etc). Ersteres
> > hätte idealerweise einen Prozentbalken. Zweiteres erzeugt
> > menschenverständliche Ausgaben, die man mit einem Tool betrachtet (vim).
> > > Das ähnelt etwas der Diskussion über transparente Shells. Klar sieht
> > > das cooler aus. Genauso wie es cool aussieht, wenn ein make-Aufruf den
> > > ganzen Bildschirm voller Text malt.
> >
> > mmm... Ist es nicht cooler, wenn da steht CC file.c [OK] und in grün?
>
> Ja, das meine ich ja. :-)
>
> Aber irgendwie scheinen die meisten darauf zu stehen, von Make die
> Konsole vollgemüllt zu bekommen.
Ja klar, bei entwickeln sowieso: SHIFT-PgUp und man sieht gleich, was es
war :)
oki,
Steffen
--
Dieses Schreiben wurde maschinell erstellt,
es trägt daher weder Unterschrift noch Siegel.
Mehr Informationen über die Mailingliste linux-l