2011-09-30 14 views
6

Estoy buscando un sistema/herramienta de compilación de alto nivel que pueda ayudar a organizar mi proyecto C incrustado en "módulos" y "componentes". Tenga en cuenta que estos dos términos son muy subjetivos, por lo que mis definiciones se dan a continuación.Sistema de compilación para un proyecto incrustado de C/C++

  • Un módulo es una colección cohesiva de archivos c y h, pero con un solo archivo h público que es visible para otros módulos.
  • Un componente (o una capa) por otro lado es una colección de módulos (por ejemplo, capa de aplicación, capa de biblioteca, capa de controlador, capa de RTOS, etc.).

El sistema de construcción/herramientas sufren -

  • Prevenir dependencias cíclicas entre componentes y módulos (dependencias cíclicas dentro de los módulos está bien)
  • impiden el acceso a las barreras privadas de un módulo. Si otros módulos intentan incluir un archivo de cabecera que sea privado para un módulo, el sistema de compilación debe arrojar un error. Sin embargo, los archivos dentro de una barrera privada deben poder incluir otros archivos dentro de esa barrera.
  • pruebas de unidad de soporte
  • edificio de apoyo y ejecución de pruebas de unidad de forma automática (rápido bucle de retroalimentación para TDD) en el host que se ejecutan en el simulador objetivo
  • apoyo análisis estático código
  • generación de código de soporte código
  • apoyo detección de duplicación (cumplir principio DRY)
  • código
  • apoyo embellecimiento
  • generación soporte de unidad de métricas de cobertura de código de prueba
  • generación soporte de métricas de calidad de código
  • sea independiente de la plataforma

podría escribir mi propia herramienta de construcción y pasar mucho tiempo en ella. Sin embargo, esa no es mi área de especialización y prefiero no reinventar la rueda si alguien ya ha creado una herramienta de este tipo.

Respuesta

4

La forma convencional de lograr eso sería colocar el código fuente de cada módulo en un directorio separado. Cada directorio puede contener todos los archivos de origen y encabezado para el módulo.

El encabezado público para cada módulo se puede colocar en un directorio de encabezados común y separado. Probablemente usaría un enlace simbólico desde el directorio común al directorio de módulos correspondiente para cada encabezado.

Las reglas de compilación simplemente indican que ningún módulo puede incluir encabezados de otros módulos a excepción de los encabezados en el directorio común. Esto logra el resultado de que ningún módulo puede incluir encabezados de otro módulo, a excepción del encabezado público (reforzando así las barreras privadas).

La prevención de dependencias cíclicas automáticamente no es trivial. El problema es que solo puede establecer que existe una dependencia cíclica al examinar varios archivos de origen a la vez, y el compilador solo mira uno a la vez.

Considere un par de módulos, ModuleA y ModuleB, y un programa, Program1, que utiliza ambos módulos.

base/include 
     ModuleA.h 
     ModuleB.h 
base/ModuleA 
     ModuleA.h 
     ModuleA1.c 
     ModuleA2.c 
base/ModuleB 
     ModuleB.h 
     ModuleB1.c 
     ModuleB2.c 
base/Program1 
     Program1.c 

Al compilar Program1.c, es perfectamente legítimo que incluyen tanto ModuleA.h y ModuleB.h si se hace uso de los servicios de ambos módulos. Por lo tanto, ModuleA.h no puede presentar una queja si el MóduloB.h está incluido en la misma unidad de traducción (TU), y tampoco el MóduloB.h puede presentar una queja si ModuleA.h está incluido en la misma TU.

Supongamos que es legítimo que ModuleA use las instalaciones de ModuleB. Por lo tanto, al compilar ModuleA1.c o ModuleA2.c, no puede haber ningún problema al tener ambos ModuleA.h y ModuleB.h incluidos.

Sin embargo, para evitar dependencias cíclicas, debe poder prohibir que el código en ModuleB1.c y ModuleB2.c use ModuleA.h.

Por lo que puedo ver, la única manera de hacerlo es mediante una técnica que requiere un encabezado privado para el Módulo B que dice "ModuleA ya está incluido" aunque no lo esté, y esto está incluido antes de ModuleA.h está siempre incluido.

El esqueleto de ModuleA.h será el formato estándar (y ModuleB.h habrá similar):

#ifndef MODULEA_H_INCLUDED 
#define MODULEA_H_INCLUDED 
...contents of ModuleA.h... 
#endif 

Ahora, si el código en ModuleB1.c contiene:

#define MODULEA_H_INCLUDED 
#include "ModuleB.h" 
...if ModuleA.h is also included, it will declare nothing... 
...so anything that depends on its contents will fail to compile... 

Esto está lejos de ser automático.

Puede hacer un análisis de los archivos incluidos y exigir que exista un tipo topológico sin bucles de las dependencias. Solía ​​haber un programa tsort en sistemas UNIX (y un programa complementario, lorder) que juntos proporcionaban los servicios necesarios para que se pudiera crear una biblioteca estática (.a) que contuviera los archivos del objeto en un orden que no requiriera volver a escanear el archivo. El programa ranlib y, finalmente, ar y ld asumieron las tareas de gestión del reexploración de una sola biblioteca, por lo que el lorder es especialmente redundante. Pero tsort tiene usos más generales; está disponible en algunos sistemas (MacOS X, por ejemplo, RHEL 5 Linux también).

Por lo tanto, utilizando el seguimiento de dependencia de GCC más tsort, debe poder verificar si hay ciclos entre módulos. Pero eso debería manejarse con cuidado.

Puede haber algún IDE u otro conjunto de herramientas que maneje esto automáticamente. Pero normalmente los programadores pueden ser lo suficientemente disciplinados como para evitar problemas, siempre y cuando los requisitos y las dependencias entre módulos estén cuidadosamente documentados.

+0

+1 Gran respuesta, en particular, el último párrafo. – mouviciel

+0

Jonathan, ¿cuál sería la herramienta de elección para implementar su solución sugerida? Hacer, CMake, Rake u otra cosa por completo? – thegreendroid

+1

No tengo puntos de vista fuertes sobre los sistemas más modernos; He visto exaltar a CMake, pero probablemente puedas usar cualquiera de ellos. Puede depender en parte de los idiomas con los que esté más familiarizado. Si usas Ruby, Rake tiene sentido, por ejemplo. Todavía tiendo a utilizar el viejo y sencillo Make, pero he estado haciendo eso durante ... bastante tiempo ... así que me siento cómodo haciéndolo. Es algo así como una solución de mínimo denominador común, con problemas de portabilidad. A veces uso Autoconf para manejar los problemas de portabilidad, pero eso tiene su propio conjunto de complejidades (y aumenta dramáticamente el tamaño de los pequeños proyectos). –

2

Para una solución general, recomiendo totalmente ir con la solución de Jonathan Leffler. Sin embargo, si necesita absolutamente una prueba automatizada de si sus módulos son autónomos y están aislados, podría probar el sistema de compilación de Debian.

Empaque cada módulo en un paquete Debian (que se realiza muy rápidamente cuando ya está configurado automáticamente), declare Build-Depende correctamente y cree los paquetes dentro de un entorno pbuilder. Esto garantiza que solo estén disponibles los encabezados públicos de cada módulo (porque solo están en los paquetes .deb que instala pbuilder para compilar los demás) y hay excelentes herramientas para examinar los árboles de paquetes de Debian y asegurarse de que sean cíclicamente gratis.

Sin embargo, esto es probablemente una muerte excesiva. Simplemente indicando que está completo y el caso definitivamente necesita una solución automatizada.

Cuestiones relacionadas