Construyendo múltiples ejecutables con reglas similares

Estoy escribiendo algo así como un tutorial interactivo para C ++. El tutorial constará de dos partes: una se comstack en una biblioteca (estoy usando Scons para comstackrla) y la otra (las lecciones) se envía con el tutorial para ser comstackdo por el usuario final. Actualmente estoy buscando una manera buena y fácil para que las personas construyan estas lecciones.

Básicamente, la segunda parte es un directorio con todas las lecciones, cada una en su propio directorio. Cada lección tendrá al menos un lesson.cpp y un archivo main.cpp , también puede haber otros archivos, cuya existencia no sabré hasta después de que se hayan enviado; el usuario final los creará. Se verá algo como esto:

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

Cada uno de estos tendrá que ser comstackdo de acuerdo con casi las mismas reglas, y el comando para comstackr debería poder ejecutarse desde uno de los directorios de lecciones ( helloworld/ , etc.).

Viendo que el rest del proyecto está construido con Scons, tendría sentido usarlo también para esta parte. Sin embargo, Scons busca el archivo SConstruct en el directorio desde el que se ejecuta: ¿sería aceptable colocar un archivo SConstruct en cada directorio de lección, más un SConscript en el directorio all_lessons/ que da las reglas generales? Esto parece ir en contra de la forma típica en que Scons espera que los proyectos se organicen: ¿cuáles son las posibles dificultades de este enfoque? ¿Podría poner un archivo SConstruct en lugar de uno de SConscript y, por lo tanto, permitir la comstackción desde cualquiera de los directorios (utilizando exportaciones para evitar recursiones interminables, supongo)?

Además, es posible que en algún momento quiera reemplazar el lesson.cpp con un lesson.py que genere los archivos necesarios; ¿Scons me permitirá hacer esto fácilmente con los constructores, o hay un marco que sería más conveniente?

Al final, quiero terminar con lo siguiente (o equivalente con diferentes sistemas de comstackción):

 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 

Ejecutando scons all en el directorio all_lessons necesitaría:

  • Ejecute even_or_odd/lesson.py para generar even_or_odd/lesson.cpp .
  • user_created_add.cpp cuenta que user_created_add.cpp también necesita comstackrse.
  • Producir un ejecutable para cada lección.

Ejecutar scons en even_or_odd/ , o scons even_or_odd en all_lessons/ debería producir un ejecutable idéntico al anterior (los mismos indicadores de comstackción).

Resumen:

  1. ¿Es Scons adecuado / capaz de esto?
  2. ¿Los Scons funcionan bien cuando los archivos SConscript están por encima de los archivos SConstruct ?
  3. ¿Scons funciona bien con múltiples archivos SConstrcut para un proyecto, SConscripting entre sí?
  4. ¿El sistema de construcción Scons es adecuado para usar scripts Python para generar archivos C ++?
  5. ¿Hay alguna ventaja de usar un sistema de comstackción diferente / escribir mi propio framework de comstackción que me estoy perdiendo?

Cualquier otro comentario es, por supuesto, bienvenido.

Gracias.

En realidad puedes hacer esto con unas pocas líneas de GNU Make.

A continuación hay dos all_lessons make que permiten all_lessons y limpiar el directorio all_lessons y los directorios de proyectos individuales. Supone que todas las fonts de C ++ en ese directorio comprenden un archivo ejecutable que recibe el nombre de su directorio. Al construir y limpiar desde el directorio de origen de nivel superior ( all_lessons ) construye y limpia todos los proyectos. Al construir y limpiar desde el directorio de un proyecto, solo crea y limpia los binarios del proyecto.

Estos makefiles también generan automáticamente dependencias y son completamente paralelizables ( make -j amigable).

Para el siguiente ejemplo, utilicé la misma estructura de archivos fuente que tiene:

 $ 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 

Para poder comstackr desde directorios de proyectos individuales, project.mk debe vincularse simbólicamente como project/Makefile primero

 [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 

Construyamos un proyecto:

 [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 

Ahora crea todos los proyectos:

 [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 

Limpiar un proyecto:

 [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' 

Limpiar todos los proyectos:

 [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 

Los 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)) 

Como ejercicio de aprendizaje, he intentado responder a tu pregunta. Desafortunadamente, no soy un experto, así que no puedo decirte cuál es la mejor / manera ideal, pero aquí hay una manera que funciona.

  1. Scons es adecuado / capaz de esto. (Esto es exactamente para lo que son las herramientas de comstackción).
  2. No aplica. (No lo sé.)
  3. Scons parece funcionar bien con múltiples archivos SConstrcut para un proyecto, SConscripting entre sí.
  4. El sistema constructor Scons puede usar scripts Python para generar archivos C ++.
  5. Un sistema de construcción diferente? A cada uno lo suyo.

Usando la jerarquía que definió, hay un archivo SConstruct en cada carpeta. Puede ejecutar scons en una subcarpeta para comstackr ese proyecto o en el nivel superior para comstackr todos los proyectos (no estoy seguro de cómo alias “todo” a la comstackción predeterminada). Puede ejecutar scons -c para limpiar el proyecto y scons averigua automáticamente qué archivos creó y los limpia (incluida la lección.cpp generada).

Sin embargo, si desea que los indicadores del comstackdor se propaguen desde el archivo de nivel superior, creo que es mejor utilizar los archivos SCONScript, excepto que no estoy seguro de hacer esta comstackción por sí mismos.

./SConstruct

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

./calculator/SConstruct y ./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')) 

Usar SConscripts

Convierto las SConstructs de la subcarpeta en SConscripts y puedo extraer los detalles de creación de código de las subcarpetas, pero luego debe ejecutar scons -u para comstackr una subcarpeta (para buscar hacia arriba la raíz SConstruct).

./SConstruct

 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, etc …

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

¿Es esencial que el comando para comstackr se ejecute desde el directorio de la lección? Si no, yo personalmente crearía all_lessons / makefile con los siguientes contenidos:

 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 $@ 

Todas las lecciones podrían construirse con “make” o “make all” en el directorio all_lessons, o una lección específica con, por ejemplo, “make helloworld / main”.

Hasta donde he llegado, esta es la mejor solución disponible:

El directorio está estructurado de la misma manera, pero en lugar de tener múltiples archivos SConstruct , las lecciones tienen un archivo SConscript cada uno, donde los valores predeterminados se anulan según sea necesario. Los archivos SConstruct son generados por un script externo según sea necesario, y se invoca SCons.

Una visión general:

 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 

Con Glob , el archivo SConscript puede SConscript todos los archivos con la extensión cpp . También puede usar un generador (ya sea uno que invoque un comando simple o uno completo) que generará la lección, lo que significa que es posible incluso almacenar la lección como metadatos y generarla en el momento.

Entonces, para responder las preguntas:

  1. Sí.
  2. No lo sé, pero no es necesario para el propósito de esto.
  3. Por lo que he visto, no (problemas con las rutas relativas a SConstruct , entre otras cosas).
  4. Sí, con varias opciones disponibles.
  5. No lo sé.

Desventajas del enfoque sugerido: esto requiere hacer un sistema meta-build por separado. La cantidad de archivos en los que se pueden especificar opciones es mayor, y los archivos SConscript dan mucho margen para el error.

Aquí está mi camino.

 # 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')) 

Ahora tu tienes :

  • env – core Entorno que contiene reglas y parámetros comunes para todas las lecciones
  • lessonEnv – clon de núcleo env, pero si tiene lesson.scons en el directorio de lección específico, puede configurar adicionalmente ese entorno o reescribir algunos parámetros.