Можно ли использовать препроцессор C, чтобы определить, существует ли файл?
У меня очень большая база кода (читай: тысячи модулей), которая имеет общий код для множества проектов, которые все работают на разных операционных системах с разными компиляторами на С++. Разумеется, сохранение процесса сборки может быть довольно сложной задачей.
В кодовой базе есть несколько мест, где она могла бы существенно очистить код, если бы существовал способ заставить препроцессор игнорировать определенный #includes
, если файл не существовал в текущей папке. Кто-нибудь знает способ достичь этого?
В настоящее время мы используем #ifdef
вокруг #include
в общем файле со вторым файлом, специфичным для проекта, который # определяет, существует или нет #include
в проекте. Это работает, но это уродливо. Люди часто забывают правильно обновлять определения при добавлении или удалении файлов из проекта. Я рассматривал возможность написания инструмента предварительной сборки, чтобы поддерживать этот файл в актуальном состоянии, но если бы это был независимый от платформы способ сделать это с помощью препроцессора, я бы скорее сделал это именно так. Любые идеи?
Ответы
Ответ 1
Как правило, это делается с помощью script, который пытается запустить препроцессор при попытке включить файл. В зависимости от того, препроцессор возвращает ошибку, script обновляет сгенерированный файл .h с соответствующим #define (или #undef). В bash script может выглядеть не так:
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.
Ответ 2
Создайте специальную папку для отсутствующих заголовков и сделайте поиск этой папки последним
(то есть специфичный для компилятора - последний элемент в переменной среды "INCLUDES", что-то вроде этого)
Тогда, если некоторые header1.h могут отсутствовать, создайте в этой папке заглушку
header1.h:
#define header1_is_missing
Теперь вы всегда можете написать
#include <header1.h>
#ifdef header1_is_missing
// there is no header1.h
#endif
Ответ 3
Маленькое обновление
Некоторые компиляторы могут поддерживать __has_include ( header-name )
.
Расширение было добавлено в С++ 17 standard (P0061R1).
Поддержка компилятора
- Clang
- GCC от 5.X
- Visual Studio из VS2015 Update 2 (?)
Пример (из веб-сайта clang):
// Note the two possible file name string formats.
#if __has_include("myinclude.h") && __has_include(<stdint.h>)
# include "myinclude.h"
#endif
Источники
Ответ 4
Сам препроцессор не может идентифицировать существование файлов, но вы, конечно же, можете использовать среду сборки для этого. Я в основном знаком с make, который позволит вам сделать что-то подобное в вашем make файле:
ifdef $(test -f filename && echo "present")
DEFINE=-DFILENAME_PRESENT
endif
Конечно, вам придется найти аналог этого в других средах сборки, таких как VisualStudio, но я уверен, что они существуют.
Ответ 5
У вас может быть выполнен шаг предварительной сборки, который генерирует include файл, содержащий список #defines, которые представляют имена файлов, существующих в текущем каталоге:
#define EXISTS_FILE1_C
#define EXISTS_FILE1_H
#define EXISTS_FILE2_C
Затем включите этот файл из исходного кода, а затем ваш источник может проверить EXISTS_*
, чтобы определить, существует ли файл.
Ответ 6
Насколько я знаю, cpp не имеет директивы относительно существования файла.
Возможно, вы сможете выполнить это с небольшой помощью из Makefile, если вы используете один и тот же make на разных платформах. Вы можете обнаружить наличие файла в Makefile:
foo.o: foo.c
if [ -f header1.h ]; then CFLAGS+=-DHEADER1_INC
Как упоминает @Greg Hewgill, вы можете сделать свой #includes условным:
#ifdef HEADER1_INC
#include <header1.h>
#endif
Ответ 7
Другая возможность: заполнить каталог где-нибудь нулевыми версиями всех заголовков, которые вы хотите включить. Передайте аргумент -I этому каталогу в качестве last такой опции.
GCP cpp ищет свои включенные каталоги в порядке, если он найдет заголовочный файл в более раннем каталоге, который он будет использовать. В противном случае он в конечном итоге найдет файл нулевой длины и будет счастлив.
Я предполагаю, что другие реализации cpp также ищут свои каталоги include в указанном порядке.
Ответ 8
Мне пришлось сделать что-то подобное для ОС Symbian. Вот как я это сделал:
скажем, вы хотите проверить, существует ли файл "file_strange.h", и вы хотите включить некоторые заголовки или ссылку на некоторые библиотеки в зависимости от существования этого файла.
сначала создайте небольшой пакетный файл для проверки существования этого файла.
autoconf хорош, но более убивает для многих небольших проектов.
---------- 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 заканчивается
, тогда я создал файл 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 заканчивается
включить файл check.mk в ваш файл bld.inf, он ДОЛЖЕН быть перед вашими файлами MMP
PRJ_MMPFILES
gnumakefile checkmedialist.mk
теперь во время компиляции файл file_strange_supported.h
будет иметь соответствующий флаг.
вы можете использовать этот флаг в своих файлах cpp или даже в файле mmp
например, в mmp
#include "../inc/file_strange_supported.h"
#ifdef NEW_API_SUPPORTED
LIBRARY newapi.lib
#else
LIBRARY oldapi.lib
#endif
и в .cpp
#include "../inc/file_strange_supported.h"
#ifdef NEW_API_SUPPORTED
CStrangeApi* api = Api::NewLC();
#else
// ..
#endif