Il preprocessore C può essere utilizzato per sapere se esiste un file?
-
02-07-2019 - |
Domanda
Ho una base di codice molto grande (leggi: migliaia di moduli) con codice condiviso tra numerosi progetti che funzionano tutti su sistemi operativi diversi con compilatori C ++ diversi. Inutile dirlo, mantenere il processo di compilazione può essere piuttosto un lavoro ingrato.
Esistono diversi punti nella base di codice in cui si potrebbe ripulire sostanzialmente il codice se solo ci fosse un modo per fare in modo che il pre-processore ignori alcuni #includes
se il file non esistesse nel cartella corrente. Qualcuno conosce un modo per raggiungere questo obiettivo?
Attualmente, usiamo un #ifdef
attorno al #include
nel file condiviso, con un secondo file specifico del progetto che #definisce se #include
esiste nel progetto. Funziona, ma è brutto. Le persone spesso dimenticano di aggiornare correttamente le definizioni quando aggiungono o rimuovono file dal progetto. Ho pensato di scrivere uno strumento pre-build per mantenere aggiornato questo file, ma se esiste un modo indipendente dalla piattaforma per farlo con il preprocessore, preferirei piuttosto farlo in questo modo. Qualche idea?
Soluzione
Generalmente questo viene fatto usando uno script che tenta di eseguire il preprocessore nel tentativo di includere il file. A seconda che il preprocessore restituisca un errore, lo script aggiorna un file .h generato con un #define (o #undef) appropriato. In bash, la sceneggiatura potrebbe apparire vagamente così:
cat > .test.h <<'EOM'
#include <asdf.h>
EOM
if gcc -E .test.h
then
echo '#define HAVE_ASDF_H 1' >> config.h
else
echo '#ifdef HAVE_ASDF_H' >> config.h
echo '# undef HAVE_ASDF_H' >> config.h
echo '#endif' >> config.h
fi
Un framework abbastanza completo per lavorare in modo portabile con controlli di portabilità come questo (e migliaia di altri) è autoconf .
Altri suggerimenti
Piccolo aggiornamento
Alcuni compilatori potrebbero supportare __has_include (header-name)
.
L'estensione è stata aggiunta allo C ++ 17 standard ( P0061R1 ).
Supporto compilatore
- Clang
- GCC da 5.X
- Visual Studio da VS2015 Update 2 (?)
Esempio (dal sito web di clang):
// Note the two possible file name string formats.
#if __has_include("myinclude.h") && __has_include(<stdint.h>)
# include "myinclude.h"
#endif
Fonti
Crea una cartella speciale per le intestazioni mancanti e crea l'ultima cartella da cercare
(che è specifico del compilatore - l'ultimo elemento nella variabile d'ambiente " INCLUDE " qualcosa del genere)
Quindi, se manca un header1.h, crea in quella cartella uno stub
header1.h:
#define header1_is_missing
Ora puoi sempre scrivere
#include <header1.h>
#ifdef header1_is_missing
// there is no header1.h
#endif
Il preprocessore stesso non è in grado di identificare l'esistenza di file, ma è certamente possibile utilizzare l'ambiente di compilazione per farlo. Ho principalmente familiarità con make, che ti permetterebbe di fare qualcosa del genere nel tuo makefile:
ifdef $(test -f filename && echo "present")
DEFINE=-DFILENAME_PRESENT
endif
Naturalmente, dovresti trovare un analogo a questo in altri ambienti di build come VisualStudio, ma sono sicuro che esistono.
È possibile eseguire una fase di pre-build che genera un file include che contiene un elenco di #define che rappresentano i nomi dei file esistenti nella directory corrente:
#define EXISTS_FILE1_C
#define EXISTS_FILE1_H
#define EXISTS_FILE2_C
Quindi, includi quel file dal tuo codice sorgente, e quindi il tuo sorgente può testare il EXISTS_ *
per vedere se esiste un file o meno.
Per quanto ne so cpp non ha una direttiva sull'esistenza di un file.
Potresti riuscire a farlo con un po 'di aiuto dal Makefile, se stai usando la stessa marca su più piattaforme. Puoi rilevare la presenza di un file nel Makefile:
foo.o: foo.c
if [ -f header1.h ]; then CFLAGS+=-DHEADER1_INC
Come menziona @Greg Hewgill, puoi quindi rendere condizionati i tuoi #includi:
#ifdef HEADER1_INC
#include <header1.h>
#endif
Un'altra possibilità: popolare una directory da qualche parte con versioni di lunghezza zero di tutte le intestazioni che si desidera includere facoltativamente. Passa un argomento -I a questa directory come ultima tale opzione.
GCC cpp cerca le sue directory include in ordine, se trova un file header in una directory precedente lo utilizzerà. Altrimenti, alla fine troverà il file di lunghezza zero e sarà felice.
Presumo che anche altre implementazioni cpp cerchino le loro directory include nell'ordine specificato.
Ho dovuto fare qualcosa di simile per il sistema operativo Symbian. Ecco come l'ho fatto: supponiamo che tu voglia verificare se il file " file_strange.h " esiste e si desidera includere alcune intestazioni o collegamenti ad alcune librerie a seconda dell'esistenza di quel file.
prima crea un piccolo file batch per verificare l'esistenza di quel file.
autoconf è buono, ma è un'uccisione eccessiva per molti piccoli progetti.
---------- check.bat
@echo off
IF EXIST [\epoc32\include\domain\middleware\file_strange] GOTO NEW_API
GOTO OLD_API
GOTO :EOF
:NEW_API
echo.#define NEW_API_SUPPORTED>../inc/file_strange_supported.h
GOTO :EOF
:OLD_API
echo.#define OLD_API_SUPPORTED>../inc/file_strange_supported.h
GOTO :EOF
---------- check.bat termina
quindi ho creato un file gnumake
---------- checkmedialist.mk
do_nothing :
@rem do_nothing
MAKMAKE :
check.bat
BLD : do_nothing
CLEAN : do_nothing
LIB : do_nothing
CLEANLIB : do_nothing
RESOURCE : do_nothing
FREEZE : do_nothing
SAVESPACE : do_nothing
RELEASABLES : do_nothing
FINAL : do_nothing
----------check.mk termina
includi il file check.mk nel tuo file bld.inf, DEVE trovarsi prima dei tuoi file MMP
PRJ_MMPFILES
gnumakefile checkmedialist.mk
ora in fase di compilazione il file file_strange_supported.h
avrà un flag appropriato impostato.
puoi usare questo flag nei tuoi file cpp o anche nel file mmp
ad esempio in mmp
#include "../inc/file_strange_supported.h"
#ifdef NEW_API_SUPPORTED
LIBRARY newapi.lib
#else
LIBRARY oldapi.lib
#endif
e in .cpp
#include "../inc/file_strange_supported.h"
#ifdef NEW_API_SUPPORTED
CStrangeApi* api = Api::NewLC();
#else
// ..
#endif
Contrariamente ad alcune affermazioni qui e su Internet, Visual Studio 2015 NON supporta la funzione __has_include
- almeno secondo la mia esperienza. Testato con l'aggiornamento 3.
Le voci potrebbero essere derivate dal fatto che VS 2017 è anche indicato come "Versione 15"; VS 2015 è invece indicato come "Versione 14". Il supporto per la funzionalità sembra essere stato ufficialmente introdotto con " Visual Studio 2017 versione 15.3 " ;.