web-dev-qa-db-fra.com

Le préprocesseur C peut-il être utilisé pour savoir si un fichier existe?

J'ai une très grande base de code (lire: des milliers de modules) qui a du code partagé entre de nombreux projets qui s'exécutent tous sur différents systèmes d'exploitation avec différents compilateurs C++. Inutile de dire que maintenir le processus de construction peut être une corvée.

Il y a plusieurs endroits dans la base de code où il pourrait nettoyer le code substantiellement si seulement il y avait un moyen de faire ignorer au pré-processeur certains #includes si le fichier n'existait pas dans le dossier actuel. Quelqu'un connaît-il un moyen d'y parvenir?

Actuellement, nous utilisons un #ifdef autour de la #include dans le fichier partagé, avec un second fichier spécifique au projet qui # définit si #include existe dans le projet. Cela fonctionne, mais c'est moche. Les gens oublient souvent de mettre à jour correctement les définitions lorsqu'ils ajoutent ou suppriment des fichiers du projet. J'ai envisagé d'écrire un outil de pré-construction pour garder ce fichier à jour, mais s'il existe une façon indépendante de la plate-forme de le faire avec le préprocesseur, je préfère de loin le faire de cette façon. Des idées?

60
Lisa

Généralement, cela se fait à l'aide d'un script qui tente d'exécuter le préprocesseur lors d'une tentative d'inclusion du fichier. Selon que le préprocesseur renvoie une erreur, le script met à jour un fichier .h généré avec un #define (ou #undef) approprié. En bash, le script pourrait ressembler vaguement à ceci:

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

autoconf est un cadre assez complet pour travailler de manière portable avec des contrôles de portabilité comme celui-ci (ainsi que des milliers d'autres).

30
Logan

Petite mise à jour

Certains compilateurs peuvent prendre en charge __has_include ( header-name ).

L'extension a été ajoutée à la norme C++ 17 ( P0061R1 ).

Prise en charge du compilateur

  • Bruit
  • GCC à partir de 5.X
  • Visual Studio à partir de VS2015 Update 2 (?)

Exemple (tiré du site Web de clang):

// Note the two possible file name string formats.
#if __has_include("myinclude.h") && __has_include(<stdint.h>)
# include "myinclude.h"
#endif

Sources

56
Setepenre

Créez un dossier spécial pour les en-têtes manquants et effectuez la dernière recherche dans ce dossier
(c'est spécifique au compilateur - dernier élément de la variable d'environnement "INCLUDES", quelque chose comme ça)

Ensuite, si certains header1.h peuvent être manquants, créez dans ce dossier un stub

header1.h:

#define header1_is_missing

Maintenant, vous pouvez toujours écrire

#include <header1.h>
#ifdef header1_is_missing

   // there is no header1.h 

#endif
45
eugensk00

Le préprocesseur lui-même ne peut pas identifier l'existence de fichiers, mais vous pouvez certainement utiliser l'environnement de génération pour le faire. Je connais surtout make, ce qui vous permettrait de faire quelque chose comme ça dans votre makefile:

ifdef $(test -f filename && echo "present")
  DEFINE=-DFILENAME_PRESENT
endif

Bien sûr, vous devriez trouver un analogue à cela dans d'autres environnements de construction comme VisualStudio, mais je suis sûr qu'ils existent.

8
bmdhacks

Vous pourriez avoir une étape de pré-génération exécutée qui génère un fichier include qui contient une liste de #defines qui représentent les noms des fichiers existant dans le répertoire actuel:

#define EXISTS_FILE1_C
#define EXISTS_FILE1_H
#define EXISTS_FILE2_C

Ensuite, incluez ce fichier à partir de votre code source, puis votre source peut tester le EXISTS_* définit pour voir si un fichier existe ou non.

4
Greg Hewgill

Autant que je sache, cpp n'a pas de directive concernant l'existence d'un fichier.

Vous pourrez peut-être accomplir cela avec un peu d'aide du Makefile, si vous utilisez la même marque sur toutes les plateformes. Vous pouvez détecter la présence d'un fichier dans le Makefile:

foo.o: foo.c
    if [ -f header1.h ]; then CFLAGS+=-DHEADER1_INC

Comme @Greg Hewgill le mentionne, vous pouvez alors rendre vos #includes conditionnels:

#ifdef HEADER1_INC
#include <header1.h>
#endif
4
DGentry

Autre possibilité: remplir un répertoire quelque part avec des versions de longueur nulle de tous les en-têtes que vous souhaitez éventuellement inclure. Passez un argument -I à ce répertoire comme dernier cette option.

Le GCC cpp recherche ses répertoires include dans l'ordre, s'il trouve un fichier d'en-tête dans un répertoire antérieur, il l'utilisera. Sinon, il finira par trouver le fichier de longueur nulle et sera heureux.

Je suppose que d'autres implémentations cpp recherchent également leurs répertoires include dans l'ordre spécifié.

4
DGentry

J'ai dû faire quelque chose de similaire pour le système d'exploitation Symbian. Voici comment je l'ai fait: disons que vous voulez vérifier si le fichier "file_strange.h" existe et que vous souhaitez inclure des en-têtes ou un lien vers certaines bibliothèques selon sur l'existence de ce fichier.

créez d'abord un petit fichier batch pour vérifier l'existence de ce fichier.

autoconf est une bonne solution mais une mise à mort pour de nombreux petits projets.

---------- 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 se termine

puis j'ai créé un fichier 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 se termine

incluez le fichier check.mk dans votre fichier bld.inf, il DOIT être avant vos fichiers MMP

PRJ_MMPFILES
gnumakefile checkmedialist.mk

maintenant au moment de la compilation, le fichier file_strange_supported.h aura un indicateur approprié. vous pouvez utiliser cet indicateur dans vos fichiers cpp ou même dans le fichier mmp par exemple dans mmp

#include "../inc/file_strange_supported.h"
#ifdef NEW_API_SUPPORTED
LIBRARY newapi.lib
#else
LIBRARY oldapi.lib
#endif

et en .cpp

#include "../inc/file_strange_supported.h"
#ifdef NEW_API_SUPPORTED
CStrangeApi* api = Api::NewLC();
#else
// ..
#endif
2
Ahmad Mushtaq

Contrairement à certaines affirmations ici et sur Internet, Visual Studio 2015 ne prend PAS en charge le __has_include fonctionnalité - au moins selon mon expérience. Testé avec la mise à jour 3.

Les rumeurs peuvent provenir du fait que VS 2017 est également appelé "Version 15"; VS 2015 est plutôt appelé "Version 14". La prise en charge de la fonctionnalité semble avoir été officiellement introduite avec "Visual Studio 2017 Version 15.3".

1
Christoph Lipka