Erstellen mehrerer ausführbarer Dateien mit ähnlichen Regeln

Ich schreibe so etwas wie ein interaktives Tutorial für C ++. Das Tutorial besteht aus zwei Teilen: Einer ist in eine Bibliothek kompiliert (ich benutze Scons, um das zu erstellen), und der andere (die Lektionen) wird mit dem Tutorial geliefert, das vom Endbenutzer kompiliert werden muss. Ich bin derzeit auf der Suche nach einem guten, einfachen Weg, um diese Lektionen zu bauen.

Im Grunde ist der zweite Teil ein Verzeichnis mit allen Lektionen darin, jedes in einem eigenen Verzeichnis. Jede Lektion hat mindestens eine Datei ” lesson.cpp und eine Datei ” lesson.cpp Es kann auch andere Dateien geben, deren Existenz ich erst nach dem Versand weiß – der Endbenutzer erstellt diese. Es sieht ungefähr so ​​aus:

 all_lessons/ helloworld/ lesson.cpp main.cpp even_or_odd/ lesson.cpp main.cpp calculator/ lesson.cpp main.cpp user_created_add.cpp 

Jeder von diesen muss nach fast den gleichen Regeln kompiliert werden, und der Befehl zum Kompilieren sollte möglich sein, von einem der Unterrichtsverzeichnisse ( helloworld/ , etc.) ausgeführt zu werden.

Da der Rest des Projekts mit Scons erstellt wird, wäre es sinnvoll, es auch für diesen Teil zu verwenden. Scons sucht SConstruct in dem Verzeichnis, aus dem es ausgeführt wird, nach der SConstruct Datei: SConstruct es akzeptabel, eine SConstruct Datei in jedes Unterrichtsverzeichnis SConscript , plus ein SConscript im Verzeichnis all_lessons/ , das die allgemeinen Regeln enthält? Dies scheint gegen den typischen Weg zu gehen, auf dem Scons Projekte zu organisieren erwartet: Was sind die möglichen Fallstricke dieses Ansatzes? Könnte ich eine SConstruct-Datei anstelle der SConscript-Datei einfügen und es dadurch ermöglichen, aus jedem Verzeichnis zu erstellen (mit Exporten, um endlose Rekursion zu vermeiden, vermute ich)?

Außerdem möchte ich irgendwann die lesson.cpp durch eine lesson.py ersetzen, die die notwendigen Dateien erzeugt; Werden Scons mir erlauben, dies leicht mit Bauarbeitern zu tun, oder gibt es einen Rahmen, der bequemer wäre?

Am Ende möchte ich mit dem folgenden (oder gleichwertigen mit verschiedenen Build-Systemen) enden:

 all_lessons/ SConstruct helloworld/ SConstruct lesson.cpp main.cpp even_or_odd/ SConstruct lesson.py main.cpp calculator/ SConstruct lesson.cpp main.cpp user_created_add.cpp 

Das Ausführen von scons all im all_lessons Verzeichnis würde all_lessons erfordern:

  • Führen Sie even_or_odd/lesson.py , um even_or_odd/lesson.cpp zu generieren.
  • Erkenne, dass user_created_add.cpp ebenfalls kompiliert werden muss.
  • Produce eine ausführbare Datei für jede Lektion.

Das Ausführen von scons in even_or_odd/ oder von scons even_or_odd in all_lessons/ sollte eine ausführbare Datei erzeugen, die der obigen identisch ist (gleiche Kompilier-Flags).

Zusammenfassung:

  1. Ist Scons dafür geeignet / fähig?
  2. functioniert Scons gut, wenn SConscript Dateien über SConstruct Dateien liegen?
  3. functioniert Scons mit mehreren SConstrcut Dateien für ein Projekt, SConscripting gegenseitig?
  4. Ist das Scons-Builder-System für die Verwendung von Python-Skripten zum Generieren von C ++ – Dateien geeignet?
  5. Gibt es einen Vorteil, ein anderes Build-System zu verwenden / mein eigenes Build-Framework zu schreiben, das ich vermisse?

Weitere Kommentare sind natürlich willkommen.

Vielen Dank.

Sie können dies mit ein paar Zeilen GNU Make tun.

Im Folgenden sind zwei Makefiles aufgeführt, die das all_lessons und Säubern von all_lessons Verzeichnissen und einzelnen Projektverzeichnissen ermöglichen. Es setzt voraus, dass alle C ++ – Quellen in diesem Verzeichnis eine ausführbare Datei enthalten, die nach ihrem Verzeichnis benannt wird. Beim all_lessons und Bereinigen aus dem Quellverzeichnis der obersten Ebene ( all_lessons ) werden alle Projekte erstellt und bereinigt. Beim Erstellen und Bereinigen von einem Projektverzeichnis werden nur die Binärdateien des Projekts erstellt und bereinigt.

Diese Makefiles erzeugen automatisch Abhängigkeiten und sind vollständig parallelisierbar ( make -j friendly).

Für das folgende Beispiel habe ich dieselbe Quelldateistruktur verwendet wie Sie:

 $ find all_lessons all_lessons all_lessons/even_or_odd all_lessons/even_or_odd/main.cpp all_lessons/Makefile all_lessons/helloworld all_lessons/helloworld/lesson.cpp all_lessons/helloworld/main.cpp all_lessons/project.mk all_lessons/calculator all_lessons/calculator/lesson.cpp all_lessons/calculator/user_created_add.cpp all_lessons/calculator/main.cpp 

Um aus individuellen Projektverzeichnissen erstellen zu können, muss project.mk zuerst als project/Makefile symbolisiert werden

 [all_lessons]$ cd all_lessons/calculator/ [calculator]$ ln -s ../project.mk Makefile [helloworld]$ cd ../helloworld/ [helloworld]$ ln -s ../project.mk Makefile [even_or_odd]$ cd ../even_or_odd/ [even_or_odd]$ ln -s ../project.mk Makefile 

Lassen Sie uns ein Projekt erstellen:

 [even_or_odd]$ make make -C .. project_dirs=even_or_odd all make[1]: Entering directory `/home/max/src/all_lessons' g++ -c -o even_or_odd/main.o -Wall -Wextra -MD -MP -MF even_or_odd/main.d even_or_odd/main.cpp g++ -o even_or_odd/even_or_odd even_or_odd/main.o make[1]: Leaving directory `/home/max/src/all_lessons' [even_or_odd]$ ./even_or_odd hello, even_or_odd 

Jetzt baue alle Projekte:

 [even_or_odd]$ cd .. [all_lessons]$ make g++ -c -o calculator/lesson.o -Wall -Wextra -MD -MP -MF calculator/lesson.d calculator/lesson.cpp g++ -c -o calculator/user_created_add.o -Wall -Wextra -MD -MP -MF calculator/user_created_add.d calculator/user_created_add.cpp g++ -c -o calculator/main.o -Wall -Wextra -MD -MP -MF calculator/main.d calculator/main.cpp g++ -o calculator/calculator calculator/lesson.o calculator/user_created_add.o calculator/main.o g++ -c -o helloworld/lesson.o -Wall -Wextra -MD -MP -MF helloworld/lesson.d helloworld/lesson.cpp g++ -c -o helloworld/main.o -Wall -Wextra -MD -MP -MF helloworld/main.d helloworld/main.cpp g++ -o helloworld/helloworld helloworld/lesson.o helloworld/main.o [all_lessons]$ calculator/calculator hello, calculator [all_lessons]$ helloworld/helloworld hello, world 

Säubere ein Projekt:

 [all_lessons]$ cd helloworld/ [helloworld]$ make clean make -C .. project_dirs=helloworld clean make[1]: Entering directory `/home/max/src/all_lessons' rm -f helloworld/lesson.o helloworld/main.o helloworld/main.d helloworld/lesson.d helloworld/helloworld make[1]: Leaving directory `/home/max/src/all_lessons' 

Säubere alle Projekte:

 [helloworld]$ cd .. [all_lessons]$ make clean rm -f calculator/lesson.o calculator/user_created_add.o calculator/main.o even_or_odd/main.o helloworld/lesson.o helloworld/main.o calculator/user_created_add.d calculator/main.d calculator/lesson.d even_or_odd/main.d calculator/calculator even_or_odd/even_or_odd helloworld/helloworld 

Die Makefiles:

 [all_lessons]$ cat project.mk all : % : forward_ # build any target by forwarding to the main makefile $(MAKE) -C .. project_dirs=$(notdir ${CURDIR}) $@ .PHONY : forward_ [all_lessons]$ cat Makefile # one directory per project, one executable per directory project_dirs := $(shell find * -maxdepth 0 -type d ) # executables are named after its directory and go into the same directory exes := $(foreach dir,${project_dirs},${dir}/${dir}) all : ${exes} # the rules .SECONDEXPANSION: objects = $(patsubst %.cpp,%.o,$(wildcard $(dir ${1})*.cpp)) # link ${exes} : % : $$(call objects,$$*) Makefile g++ -o $@ $(filter-out Makefile,$^) ${LDFLAGS} ${LDLIBS} # compile .o and generate dependencies %.o : %.cpp Makefile g++ -c -o $@ -Wall -Wextra ${CPPFLAGS} ${CXXFLAGS} -MD -MP -MF ${@:.o=.d} $< .PHONY: clean clean : rm -f $(foreach exe,${exes},$(call objects,${exe})) $(foreach dir,${project_dirs},$(wildcard ${dir}/*.d)) ${exes} # include auto-generated dependency files -include $(foreach dir,${project_dirs},$(wildcard ${dir}/*.d)) 

Als Übung zum Lernen von Scons habe ich versucht, deine Frage zu beantworten. Leider bin ich kein Experte, also kann ich dir nicht sagen, was der beste / ideale Weg ist, aber hier ist ein Weg, der funktioniert.

  1. Scons ist dafür geeignet / fähig. (Genau dafür stehen Build-Tools.)
  2. Unzutreffend. (Ich weiß es nicht.)
  3. Scons scheint gut mit mehreren SConstrcut-Dateien für ein Projekt zu funktionieren, die sich gegenseitig sconscripten.
  4. Das Scons-Builder-System kann Python-Skripts zum Generieren von C ++ – Dateien verwenden.
  5. Ein anderes Build-System? Jedem sein eigenes.

Unter Verwendung der von Ihnen definierten Hierarchie befindet sich in jedem Ordner eine SConstruct-Datei. Sie können scons in einem Unterordner scons , um dieses Projekt zu erstellen, oder auf der obersten Ebene, um alle Projekte zu erstellen (Sie sind sich nicht sicher, wie Sie ” scons ” zum Standard-Build scons würden). Sie können scons -c um das Projekt zu bereinigen, und scons ermittelt automatisch, welche Dateien erstellt wurden, und bereinigt diese (einschließlich der generierten lection.cpp).

Wenn Sie jedoch möchten, dass Compiler-Flags von der Top-Level-Datei ausgegeben werden, ist es besser, SConscript-Dateien zu verwenden – außer dass ich nicht sicher bin, ob Sie diese Compiles selbst erstellen.

./Strukturieren

 env = Environment() env.SConscript(dirs=['calculator', 'even_or_odd', 'helloworld'], name='SConstruct') 

./calculator/Struktur und ./calculator/helloworld

 env = Environment() env.Program('program', Glob('*.cpp')) 

./even_or_odd/SConstruct

 env = Environment() def add_compiler_builder(env): # filename transformation suffix = '.cpp' src_suffix = '.py' # define the build method rule = 'python $SOURCE $TARGET' bld = Builder(action = rule, suffix = suffix, src_suffix = src_suffix) env.Append(BUILDERS = {'Lesson' : bld}) return env add_compiler_builder(env) env.Lesson('lesson.py') env.Program('program', Glob('*.cpp')) 

SConscripts verwenden

Ich konvertiere die SConstructs des Unterordners in SConscripts und kann die Code-Build-Details aus den Unterordnern herausheben, aber dann muss man scons -u , um in einen Unterordner zu bauen (um nach dem Stamm SConstruct zu suchen).

./Strukturieren

 def default_build(env): env.Program('program', Glob('*.cpp')) env = Environment() env.default_build = default_build Export('env') env.SConscript(dirs=['calculator', 'even_or_odd', 'helloworld']) 

./helloworld/SConscript usw.

 Import('env') env.default_build(env) 

Muss der Befehl zum Kompilieren aus dem Unterrichtsverzeichnis ausgeführt werden? Wenn nicht, dann würde ich all_lessons / makefile mit folgendem Inhalt erstellen:

 lessons = helloworld even_or_odd calculator all: $(lessons) # for each $lesson, the target is $lesson/main built from $lesson/main.cpp and $lesson/lesson.cpp # NB: the leading space on the second line *must* be a tab character $(lessons:%=%/main): %/main: %/main.cpp %/lesson.cpp g++ -W -Wall $+ -o $@ 

Alle Lektionen könnten dann mit “make” oder “make all” im Verzeichnis all_lessons oder einer bestimmten Lektion mit zB “make helloworld / main” erstellt werden.

Soweit ich gefunden habe, ist dies die beste verfügbare Lösung:

Das Verzeichnis ist auf die gleiche Weise strukturiert, aber anstatt mehrere SConstruct Dateien zu haben, haben die Lektionen jeweils eine SConscript Datei, in der die SConscript bei Bedarf überschrieben werden. Die SConstruct Dateien werden bei Bedarf von einem externen Skript generiert, und SCons wird aufgerufen.

Ein Überblick:

 all_lessons/ helloworld/ SConscript lesson.cpp main.cpp even_or_odd/ SConscript lesson.py main.cpp calculator/ SConscript lesson.cpp main.cpp user_created_add.cpp 

Mit Glob kann die SConscript Datei alle Dateien mit der Erweiterung cpp kompilieren. Es kann auch einen Builder verwenden (entweder einen, der einen einfachen oder einen vollwertigen Befehl aufruft), der die Lektion generiert, was bedeutet, dass es möglich ist, die Lektion auch nur als Metadaten zu speichern und sie vor Ort generieren zu lassen.

Also, um die Fragen zu beantworten:

  1. Ja.
  2. Ich weiß es nicht, aber es ist für den Zweck nicht erforderlich.
  3. Soweit ich gesehen habe, nein (Probleme mit Wegen, die mit SConstruct , unter anderem).
  4. Ja, mit mehreren verfügbaren Optionen.
  5. Ich weiß es nicht.

Nachteile des vorgeschlagenen Ansatzes: Dies erfordert ein separates Meta-Build-System. Die Anzahl der Dateien, für die Optionen angegeben werden können, ist höher, und die SConscript Dateien SConscript viel Platz für Fehler.

Hier ist mein Weg.

 # SConstruct or SConscript def getSubdirs(dir) : lst = [ name for name in os.listdir(dir) if os.path.isdir(os.path.join(dir, name)) and name[0] != '.' ] return lst env = Environment() path_to_lessons = '' # path to lessons # configure your environment, set common rules and parameters for all lessons for lesson in getSubdirs(path_to_lessons) : lessonEnv = env.Clone() # configure specific lesson, for example i'h ve checked SConscript file in lesson dir # and if it exist, execute it with lessonEnv and append env specific settings if File(os.path.join(path_to_lessons, lesson, 'lesson.scons')).exists() : SConscript(os.path.join(lesson, 'lesson.scons', export = ['lessonEnv']) # add lesson directory to include path lessonEnv.Append(CPPPATH = os.path.join(path_to_lessons, lesson)); lessonEnv.Program(lesson, Glob(os.path.join(path_to_lessons, lesson, '*.cpp')) 

Jetzt hast du :

  • env – core Umgebung, die allgemeine Regeln und Parameter für alle Lektionen enthält
  • lectionEnv – clone von core env, aber wenn du lection.cons in einem bestimmten lektionsverzeichnis hast, kannst du diese umgebung zusätzlich konfigurieren oder einige parameter neu schreiben.