¿Cuál es tu experiencia con make no recursivo?

Hace unos años, leí el documento Recursive Make Considered Harmful e implementé la idea en mi propio proceso de comstackción. Recientemente, leí otro artículo con ideas sobre cómo implementar make no recursivo . Así que tengo algunos puntos de datos que no recursivos funcionan para al menos algunos proyectos.

Pero tengo curiosidad sobre las experiencias de otros. ¿Has probado la make no recursiva? ¿Mejoró o empeoró las cosas? ¿Valió la pena el tiempo?

Usamos un sistema GNU Make no recursivo en la empresa para la que trabajo. Se basa en el documento de Miller y, especialmente, en el enlace “Implementación de recurso no recursivo” que proporcionó. Hemos logrado refinar el código de Bergen en un sistema en el que no hay placas de calderas en los archivos make de los subdirectorios. En general, funciona bien, y es mucho mejor que nuestro sistema anterior (una cosa recursiva hecha con GNU Automake).

Admitimos todos los sistemas operativos “principales” (comercialmente): AIX, HP-UX, Linux, OS X, Solaris, Windows, incluso el mainframe AS / 400. Comstackmos el mismo código para todos estos sistemas, con las partes dependientes de la plataforma aisladas en bibliotecas.

Hay más de dos millones de líneas de código C en nuestro árbol en aproximadamente 2000 subdirectorios y 20000 archivos. Consideramos seriamente usar SCons, pero no pudimos hacerlo funcionar lo suficientemente rápido. En los sistemas más lentos, Python usaría un par de docenas de segundos para analizar en los archivos SCons donde GNU Make hizo lo mismo en aproximadamente un segundo. Esto fue hace unos tres años, así que las cosas pueden haber cambiado desde entonces. Tenga en cuenta que generalmente mantenemos el código fuente en un recurso compartido NFS / CIFS y comstackmos el mismo código en múltiples plataformas. Esto significa que es incluso más lento para que la herramienta de comstackción explore los cambios en el árbol fuente.

Nuestro sistema GNU Make no recursivo no está exento de problemas. Estos son algunos de los mayores obstáculos que puede encontrar:

  • Hacerlo portátil, especialmente para Windows, es mucho trabajo.
  • Mientras que GNU Make es casi un lenguaje de progtwigción funcional utilizable, no es adecuado para la progtwigción en general. En particular, no hay espacios de nombres, módulos ni nada por el estilo para ayudarlo a aislar piezas entre sí. Esto puede causar problemas, aunque no tanto como podría pensar.

Las principales ventajas sobre nuestro antiguo sistema recursivo de archivos MAKE son:

  • Es rápido Lleva unos dos segundos comprobar el árbol completo (directorios 2k, 20k) y decidir si está actualizado o comenzar a comstackr. La vieja cosa recursiva tomaría más de un minuto para no hacer nada.
  • Maneja las dependencias correctamente. Nuestro antiguo sistema se basó en el orden en que se construyeron los subdirectorios, etc. Tal como esperaría al leer el documento de Miller, tratar el árbol completo como una entidad única es realmente la forma correcta de abordar este problema.
  • Es portátil para todos nuestros sistemas compatibles, después de todo el trabajo duro que le hemos dedicado. Es genial.
  • El sistema de abstracción nos permite escribir makefiles muy concisos. Un subdirectorio típico que define solo una biblioteca es solo dos líneas. Una línea da el nombre de la biblioteca y la otra enumera las bibliotecas de las que depende.

En relación con el último elemento en la lista anterior. Terminamos implementando una especie de instalación de expansión macro dentro del sistema de comstackción. Los archivos make de subdirectorios incluyen progtwigs, subdirectorios, bibliotecas y otras cosas comunes en variables como PROGRAMAS, SUBDIROS, LIBRAS. Luego, cada uno de estos se expande a reglas de GNU Make “reales”. Esto nos permite evitar gran parte de los problemas del espacio de nombres. Por ejemplo, en nuestro sistema está bien tener múltiples archivos fuente con el mismo nombre, no hay problema allí.

En cualquier caso, esto terminó siendo mucho trabajo. Si puede obtener SCons o similar que funcione para su código, le aconsejaría que lo vea primero.

Después de leer el documento de RMCH, me puse en camino con el objective de escribir un Makefile no recursivo apropiado para un pequeño proyecto en el que estaba trabajando en ese momento. Después de terminar, me di cuenta de que debería ser posible crear un “framework” genérico Makefile que se pueda usar para decir de manera muy simple y concisa qué objectives finales te gustaría construir, qué tipo de objectives son (por ejemplo, bibliotecas o ejecutables). ) y qué archivos fuente deben comstackrse para hacerlos.

Después de algunas iteraciones finalmente creé eso: un único Makefile repetitivo de unas 150 líneas de GNU Make syntax que nunca necesita ninguna modificación, solo funciona para cualquier tipo de proyecto que me interese usar, y es lo suficientemente flexible como para construir objectives múltiples de diversos tipos con suficiente granularidad para especificar indicadores de comstackción exactos para cada archivo de origen (si lo deseo) y marcadores precisos del enlazador para cada ejecutable. Para cada proyecto, todo lo que necesito hacer es proporcionarle pequeños Makefiles separados que contengan bits similares a esto:

 TARGET := foo TGT_LDLIBS := -lbar SOURCES := foo.c baz.cpp SRC_CFLAGS := -std=c99 SRC_CXXFLAGS := -fstrict-aliasing SRC_INCDIRS := inc /usr/local/include/bar 

Un archivo Makefile como el anterior haría exactamente lo que cabría esperar: comstackr un ejecutable llamado “foo”, comstackr foo.c (con CFLAGS = -std = c99) y baz.cpp (con CXXFLAGS = -fstrict-aliasing) y agregar “./inc” y “/ usr / local / include / bar” a la ruta de búsqueda #include , con un enlace final que incluye la biblioteca “libbar”. También se daría cuenta de que hay un archivo fuente de C ++ y se sabe que debe usar el enlazador C ++ en lugar del enlazador C. El marco me permite especificar mucho más de lo que se muestra aquí en este sencillo ejemplo.

El Makefile estándar hace todo el desarrollo de reglas y la generación automática de dependencias necesarias para construir los objectives especificados. Todos los archivos generados se ubican en una jerarquía de directorios de salida separada, por lo que no se entremezclan con los archivos fuente (y esto se hace sin el uso de VPATH, por lo que no hay problema con tener varios archivos fuente que tienen el mismo nombre).

Ahora (re) he usado este mismo Makefile en al menos dos docenas de proyectos diferentes en los que he trabajado. Algunas de las cosas que más me gustan de este sistema (aparte de lo fácil que es crear un Makefile adecuado para cualquier proyecto nuevo) son:

  • Es rápido Puede decir casi instantáneamente si algo está desactualizado.
  • Dependencias 100% confiables. No hay posibilidad de que las comstackciones paralelas se rompan misteriosamente, y siempre construye exactamente el mínimo requerido para que todo vuelva a estar actualizado.
  • Nunca más necesitaré reescribir un archivo MAKE completo: D

Finalmente, solo mencionaría que, con los problemas inherentes a la marca recursiva, no creo que hubiera sido posible para mí lograr esto. Probablemente habría estado condenado a reescribir makefiles defectuosos una y otra vez, tratando en vano de crear uno que realmente funcionara correctamente.

Permítanme enfatizar un argumento del documento de Miller: cuando comienza a resolver manualmente las relaciones de dependencia entre los diferentes módulos y le resulta difícil garantizar el orden de comstackción, en realidad está reimplantando la lógica que el sistema de comstackción ha tenido que resolver en primer lugar. Construir sistemas recursivos fiables para construir es muy difícil. Los proyectos de la vida real tienen muchas partes interdependientes cuyo orden de construcción no es trivial para descubrir y, por lo tanto, esta tarea debe dejarse al sistema de comstackción. Sin embargo, solo puede resolver ese problema si tiene conocimiento global del sistema.

Además, los sistemas recursivos make build son propensos a derrumbarse cuando se construyen simultáneamente en múltiples procesadores / núcleos. Si bien estos sistemas de comstackción parecen funcionar de manera confiable en un solo procesador, muchas dependencias faltantes pasan desapercibidas hasta que comiences a construir tu proyecto en paralelo. He trabajado con un sistema recursivo make build que funcionó en hasta cuatro procesadores, pero de repente se estrelló en una máquina con dos cuatros núcleos. Luego me encontré con otro problema: estos problemas de concurrencia son casi imposibles de depurar y terminé dibujando un diagtwig de flujo de todo el sistema para descubrir qué salió mal.

Para volver a su pregunta, me resulta difícil pensar en buenas razones por las que uno quiere usar la marca recursiva. El rendimiento en tiempo de ejecución de los sistemas de comstackción GNU Make no recursivos es difícil de superar y, por el contrario, muchos sistemas recursivos tienen graves problemas de rendimiento (el soporte de comstackción paralela débil es una vez más parte del problema). Hay un documento en el que evalué un sistema de comstackción Make específico (recursivo) y lo comparé con un puerto SCons. Los resultados de rendimiento no son representativos porque el sistema de comstackción no era muy estándar, pero en este caso particular, el puerto SCons era realmente más rápido.

En pocas palabras: si realmente quiere usar Make para controlar sus comstackciones de software, opte por Make no recursivo porque hace que su vida sea mucho más fácil a largo plazo. Personalmente, prefiero usar SCons por motivos de usabilidad (o Rake, básicamente cualquier sistema de comstackción que use un lenguaje de scripting moderno y que tenga soporte de dependencia implícito).

Hice un bash a medias en mi trabajo anterior al hacer que el sistema de comstackción (basado en la versión de GNU) fuera completamente no recursivo, pero me encontré con una serie de problemas:

  • Los artefactos (es decir, las bibliotecas y los ejecutables creados) tenían sus fonts distribuidas en varios directorios, confiando en vpath para encontrarlos
  • Varios archivos fuente con el mismo nombre existían en diferentes directorios
  • Se compartieron varios archivos fuente entre artefactos, a menudo comstackdos con diferentes indicadores de comstackción
  • Diferentes artefactos a menudo tenían diferentes indicadores de comstackción, configuraciones de optimización, etc.

Una característica de GNU make que simplifica el uso no recursivo son los valores de las variables específicas del objective :

 foo: FOO=banana bar: FOO=orange 

Esto significa que al construir el objective “foo”, $ (FOO) se expandirá a “banana”, pero al construir “barra” de destino, $ (FOO) se expandirá a “naranja”.

Una limitación de esto es que no es posible tener definiciones de VPATH específicas de un objective, es decir, no hay forma de definir de manera única a VPATH de forma individual para cada objective. Esto fue necesario en nuestro caso para encontrar los archivos fuente correctos.

La principal característica que falta de GNU es necesaria para admitir la no recursividad es que carece de espacios de nombres . Las variables específicas de un objective se pueden usar de manera limitada para “simular” espacios de nombres, pero lo que realmente se necesita es poder incluir un Makefile en un subdirectorio utilizando un ámbito local.

EDITAR: Otra característica muy útil (y con frecuencia infrautilizada) de GNU make en este contexto son las instalaciones de macro expansión (ver la función eval , por ejemplo). Esto es muy útil cuando tiene varios objectives que tienen reglas / objectives similares, pero difieren en formas que no se pueden express usando la syntax regular de GNU make.

Estoy de acuerdo con las declaraciones en el artículo referido, pero tardé mucho tiempo en encontrar una buena plantilla que haga todo esto y que sea fácil de usar.

Actualmente estoy trabajando en un pequeño proyecto de investigación, donde estoy experimentando con la integración continua; compruebe automáticamente la unidad en la PC, y luego ejecute una prueba del sistema en un objective (incrustado). Esto no es trivial en make, y he buscado una buena solución. Encontrar esa marca sigue siendo una buena opción para las comstackciones multiplataforma portátiles. Finalmente encontré un buen punto de partida en http://code.google.com/p/nonrec-make

Esto fue un verdadero alivio. Ahora mis makefiles son

  • muy simple de modificar (incluso con conocimientos limitados de hacer)
  • rápido de comstackr
  • verificando completamente (.h) las dependencias sin ningún esfuerzo

Ciertamente también lo usaré para el próximo (gran) proyecto (asumiendo C / C ++)

Conozco al menos un proyecto a gran escala ( ROOT ), que anuncia el uso de [enlace de PowerPoint] el mecanismo descrito en Recursive Make Considered Harmful. El marco supera el millón de líneas de código y se comstack de manera bastante inteligente.


Y, por supuesto, todos los grandes proyectos con los que trabajo que usan recursive make son tremendamente lentos de comstackr. ::suspiro::

He desarrollado un sistema de make no recursivo para un proyecto de tamaño medio de C ++, que está destinado para su uso en sistemas tipo Unix (incluidos los Mac). El código en este proyecto está todo en un árbol de directorio enraizado en un directorio src /. Quería escribir un sistema no recursivo en el que se pudiera escribir “make all” desde cualquier subdirectorio del directorio src / de nivel superior para comstackr todos los archivos fuente en el árbol de directorio enraizado en el directorio de trabajo, como en un sistema recursivo de hacer. Debido a que mi solución parece ser ligeramente diferente de otras que he visto, me gustaría describirla aquí y ver si obtengo alguna reacción.

Los principales elementos de mi solución fueron los siguientes:

1) Cada directorio en src / tree tiene un archivo llamado sources.mk. Cada archivo define una variable de archivo MAKE que enumera todos los archivos fuente en el árbol enraizado en el directorio. El nombre de esta variable es de la forma [directorio] _SRCS, en la que [directorio] representa una forma canonicalizada de la ruta desde el directorio src / del nivel superior a ese directorio, con barras invertidas reemplazadas por guiones bajos. Por ejemplo, el archivo src / util / param / sources.mk define una variable llamada util_param_SRCS que contiene una lista de todos los archivos fuente en src / util / param y sus subdirectorios, si los hay. Cada archivo sources.mk también define una variable llamada [directorio] _OBJS que contiene una lista de los objetos correspondientes del archivo de objeto * .o. En cada directorio que contiene subdirectorios, sources.mk incluye el archivo sources.mk de cada uno de los subdirectorios y concatena las variables [subdirectorio] _SRCS para crear su propia variable [directorio] _SRCS.

2) Todas las rutas se expresan en archivos sources.mk como rutas absolutas en las que el directorio src / está representado por una variable $ (SRC_DIR). Por ejemplo, en el archivo src / util / param / sources.mk, el archivo src / util / param / Componenent.cpp se listará como $ (SRC_DIR) /util/param/Component.cpp. El valor de $ (SRC_DIR) no se establece en ningún archivo sources.mk.

3) Cada directorio también contiene un Makefile. Cada Makefile incluye un archivo de configuración global que establece el valor de la variable $ (SRC_DIR) en la ruta absoluta al directorio root src /. Elegí usar una forma simbólica de rutas absolutas porque esta parecía ser la forma más fácil de crear múltiples archivos make en múltiples directorios que interpretarían las rutas para las dependencias y los objectives de la misma manera, al tiempo que permitían mover todo el árbol fuente si lo deseaba , cambiando el valor de $ (SRC_DIR) en un archivo. Este valor se establece automáticamente mediante una secuencia de comandos simple que el usuario debe ejecutar cuando el paquete se carga o se clona desde el repository git o cuando se mueve todo el árbol fuente.

4) El archivo MAKE en cada directorio incluye el archivo sources.mk para ese directorio. El objective “todos” para cada archivo Makefile enumera el archivo [directorio] _OBJS para ese directorio como una dependencia, por lo que requiere la comstackción de todos los archivos fuente en ese directorio y sus subdirectorios.

5) La regla para comstackr archivos * .cpp crea un archivo de dependencia para cada archivo fuente, con un sufijo * .d, como efecto secundario de la comstackción, como se describe aquí: http://mad-scientist.net/make/ autodep.html . Elegí usar el comstackdor gcc para la generación de dependencias, usando la opción -M. Utilizo gcc para la generación de dependencias incluso cuando uso otro comstackdor para comstackr los archivos fuente, porque gcc casi siempre está disponible en sistemas unix y porque esto ayuda a estandarizar esta parte del sistema de comstackción. Se puede usar un comstackdor diferente para comstackr realmente los archivos fuente.

6) El uso de rutas absolutas para todos los archivos en las variables _OBJS y _SRCS requiere que escriba un script para editar los archivos de dependencia generados por gcc, que crea archivos con rutas relativas. Escribí un script de python para este propósito, pero otra persona podría haber usado sed. Las rutas para las dependencias en los archivos de dependencia resultantes son rutas absolutas literales. Esto está bien en este contexto porque los archivos de dependencia (a diferencia de los archivos sources.mk) se generan localmente y no se distribuyen como parte del paquete.

7) El Makefile en cada director incluye el archivo sources.mk del mismo directorio, y contiene una línea “-include $ ([directory] _OBJS: .o = .d)” que intenta incluir un archivo de dependencia para cada archivo fuente en el directorio y sus subdirectorios, como se describe en la URL dada anteriormente.

La principal diferencia entre este y otros esquemas que he visto que permiten invocar “make all” desde cualquier directorio es el uso de rutas absolutas para permitir que las mismas rutas se interpreten de manera consistente cuando se invoca Make desde diferentes directorios. Siempre que estas rutas se expresen utilizando una variable para representar el directorio de origen de nivel superior, esto no impide que uno mueva el árbol de origen, y es más simple que algunos métodos alternativos para lograr el mismo objective.

Actualmente, mi sistema para este proyecto siempre realiza una comstackción “in situ”: el archivo objeto producido al comstackr cada archivo fuente se coloca en el mismo directorio que el archivo fuente. Sería sencillo habilitar las comstackciones fuera de lugar cambiando la secuencia de comandos que edita los archivos de dependencia de gcc para reemplazar la ruta absoluta a src / dirctory por una variable $ (BUILD_DIR) que represente el directorio de comstackción en la expresión para el objective de archivo de objeto en la regla para cada archivo de objeto.

Hasta ahora, he encontrado que este sistema es fácil de usar y mantener. Los fragmentos necesarios de los archivos MAKE son cortos y comparativamente fáciles de entender para los colaboradores.

El proyecto para el que desarrollé este sistema está escrito en ANSI C ++ completamente autónomo sin dependencias externas. Creo que este tipo de sistema de makefile no recursivo hecho en casa es una opción razonable para el código autónomo y altamente portátil. Consideraría un sistema de comstackción más potente como CMake o gnu autotools, sin embargo, para cualquier proyecto que tenga dependencias no triviales en progtwigs o bibliotecas externas o en características no estándar del sistema operativo.

Escribí un sistema de comstackción no recursivo no muy bueno, y desde entonces un sistema de comstackción recursivo modular muy limpio para un proyecto llamado Pd-extended . Es básicamente como un lenguaje de scripting con varias bibliotecas incluidas. Ahora también estoy trabajando en el sistema no recursivo de Android, así que ese es el contexto de mis pensamientos sobre este tema.

Realmente no puedo decir mucho sobre las diferencias de rendimiento entre los dos, realmente no he prestado atención, ya que las comstackciones completas solo se hacen en el servidor de comstackción. Por lo general, estoy trabajando ya sea en el lenguaje central o en una biblioteca en particular, por lo que solo estoy interesado en crear ese subconjunto del paquete completo. La técnica recursive make tiene la gran ventaja de hacer que el sistema de comstackción sea independiente y se integre en un todo más grande. Esto es importante para nosotros ya que queremos utilizar un sistema de comstackción para todas las bibliotecas, ya sea que estén integradas o escritas por un autor externo.

Ahora estoy trabajando en la construcción de una versión personalizada de las partes internas de Android, por ejemplo, una versión de las clases SQLite de Android que están basadas en la sqlite cifrada de SQLCipher. Así que tengo que escribir archivos Android.mk no recursivos que están envolviendo todo tipo de sistemas de comstackción raros, como los de sqlite. No puedo imaginar cómo hacer que Android.mk ejecute un script arbitrario, mientras que esto sería fácil en un sistema recursivo tradicional, según mi experiencia.