2011-09-30 10 views
6

Sto cercando un sistema/strumento di creazione di alto livello che possa aiutare a organizzare il mio progetto C incorporato in "moduli" e "componenti". Si noti che questi due termini sono altamente soggettivi, quindi le mie definizioni sono fornite di seguito.Costruisci il sistema per un progetto C/C++ integrato

  • Un modulo è una raccolta coesa di file c e h ma con un solo file h pubblico visibile ad altri moduli.
  • Un componente (o un livello) d'altra parte è una raccolta di moduli (ad esempio livello applicazione, livello libreria, livello driver, livello RTOS ecc.).

Il sistema di compilazione/strumento dovrebbe -

  • Prevenire le dipendenze cicliche tra i componenti e moduli (dipendenze cicliche all'interno dei moduli va bene)
  • impedire l'accesso a barriere di natura privata di un modulo. Se altri moduli cercano di includere un file di intestazione privato in un modulo, il sistema di build deve generare un errore. Tuttavia, i file all'interno di una barriera privata devono essere in grado di includere altri file all'interno di tale barriera.
  • costruzione supporto e l'esecuzione di test di unità automaticamente (circuito di retroazione veloce per TDD) sull'host
  • test di unità di supporto da eseguire sul simulatore porta
  • supporto analisi statica codice
  • generazione di codice di supporto codice
  • supporto rilevamento duplicazione (applicare principio DRY)
  • codice
  • supporto abbellimento
  • generazione supporto di unità metriche copertura di codice di test
  • generazione il supporto di metriche di qualità del codice
  • essere indipendente dalla piattaforma

ho potuto scrivere il mio strumento di compilazione e di spendere un sacco di tempo su di esso. Tuttavia, questa non è la mia area di competenza e preferirei non reinventare la ruota se qualcuno ha già creato uno strumento del genere.

risposta

4

Il modo convenzionale per ottenere ciò sarebbe quello di posizionare il codice sorgente per ciascun modulo in una directory separata. Ogni directory può contenere tutti i file di origine e di intestazione per il modulo.

L'intestazione pubblica per ogni modulo può essere inserita in una directory comune separata di intestazioni. Probabilmente userò un link simbolico dalla directory comune alla directory del modulo pertinente per ogni intestazione.

Le regole di compilazione indicano semplicemente che nessun modulo può includere intestazioni da altri moduli ad eccezione delle intestazioni nella directory comune. Ciò consente di ottenere che nessun modulo possa includere intestazioni da un altro modulo, ad eccezione dell'intestazione pubblica (rafforzando così le barriere private).

Prevenire automaticamente le dipendenze cicliche non è banale. Il problema è che puoi solo stabilire che c'è una dipendenza ciclica guardando diversi file sorgente alla volta, e il compilatore guarda solo uno alla volta.

Considerare una coppia di moduli, ModuloA e ModuloB, e un programma, Programma1, che utilizza entrambi i moduli.

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 

Quando si compila Program1.c, è perfettamente legittimo per poter includere sia ModuleA.h e ModuleB.h se fa uso dei servizi di entrambi i moduli. Quindi, ModuleA.h non può lamentarsi se ModuleB.h è incluso nella stessa unità di traduzione (TU), e nessuno dei due moduli può lamentarsi se ModuleA.h è incluso nella stessa TU.

Supponiamo che sia legittimo per ModuleA utilizzare le strutture di ModuleB. Pertanto, durante la compilazione di ModuleA1.c o ModuleA2.c, non ci possono essere problemi con l'inclusione di ModuleA.h e ModuleB.h.

Tuttavia, per evitare le dipendenze cicliche, è necessario poter impedire al codice in ModuleB1.c e ModuleB2.c di utilizzare ModuleA.h.

Per quanto posso vedere, l'unico modo per farlo è una tecnica che richiede un'intestazione privata per ModuleB che dice "ModuleA è già incluso" anche se non lo è, e questo è incluso prima di ModuleA.h è mai incluso

Lo scheletro di ModuleA.h sarà il formato standard (e ModuleB.h sarà simile):

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

Ora, se il codice in 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... 

Questo è tutt'altro che automatico.

È possibile eseguire un'analisi dei file inclusi e richiedere l'esistenza di un ordinamento topologico senza loop delle dipendenze. Un tempo esisteva un programma tsort su sistemi UNIX (e un programma associato,) che insieme fornivano i servizi necessari per creare una libreria statica (.a) che contenesse i file oggetto in un ordine che non richiedesse la scansione del archivio. Il programma ranlib e, eventualmente, ar e ld hanno assunto i compiti di gestione della riscansione di una singola libreria, rendendo in tal modo ridondante lorder. Ma tsort ha usi più generali; è disponibile su alcuni sistemi (MacOS X, ad esempio, RHEL 5 Linux).

Quindi, utilizzando il rilevamento delle dipendenze da GCC plus tsort, si dovrebbe essere in grado di verificare se ci sono cicli tra i moduli. Ma questo dovrebbe essere gestito con un po 'di attenzione.

Potrebbe esserci qualche IDE o un altro set di strumenti che gestisce questa roba automaticamente. Ma normalmente i programmatori possono essere abbastanza disciplinati per evitare problemi, a patto che i requisiti e le dipendenze tra moduli siano accuratamente documentati.

+0

+1 Ottima risposta, in particolare l'ultimo paragrafo. – mouviciel

+0

Jonathan, quale sarebbe lo strumento di scelta per implementare la soluzione suggerita? Crea, CMake, Rake o qualcos'altro interamente? – thegreendroid

+1

Non ho opinioni forti sui sistemi più moderni; Ho visto decantato CMake, ma probabilmente potresti usarne qualcuno. Potrebbe dipendere in parte da quali lingue è più familiare. Se usi Ruby, allora Rake ha senso, per esempio. Tendo ancora ad usare la semplice vecchia Make, ma l'ho fatto per ... piuttosto a lungo ... quindi mi sento a mio agio nel farlo. È una soluzione con un minimo comune denominatore, con problemi di portabilità. A volte uso Autoconf per gestire i problemi di portabilità, ma questo ha le sue complesse complessità (e aumenta drasticamente la dimensione dei piccoli progetti). –

2

Per una soluzione generale, consiglio vivamente di andare con la soluzione di Jonathan Leffler. Tuttavia, se hai assolutamente bisogno di un test automatico se i tuoi moduli sono autonomi e isolati, potresti provare il sistema di generazione di Debian.

Inserisci ciascun modulo in un pacchetto Debian (che viene eseguito molto rapidamente quando è già autoconfigurato), dichiara Build-Depends correttamente e crea i pacchetti all'interno di un ambiente pbuilder. Ciò garantisce che solo le intestazioni pubbliche di ciascun modulo siano disponibili (perché solo quelle sono nei pacchetti .deb che sono stati installati da pbuilder per costruire gli altri pacchetti) e ci sono strumenti eccellenti per esaminare gli alberi di pacchetti Debian e assicurarsi che siano ciclici. gratuito.

Tuttavia, questo è probabilmente over-kill. Basta affermarlo per completezza e il caso è sicuramente necessario una soluzione automatizzata.

Problemi correlati