Pull to refresh

Пара слов о Header Map в XCode

Reading time3 min
Views5.4K
Семейство языков Си/Objective C/C++ нуждается в препроцессоре. Препроцессор пропускает компилируемый исходник через себя, прежде чем отдать текст на вход компилятору. Пожалуй самая важная часть работы препроцессора заключается в подстановке на место директив #include<имя-файла> содержимого указанного файла. Обычно указывают относительный путь (ex: stdio.h, sys/stat.h). Возникает закономерный вопрос — как препроцессор находит заголовочные файлы?

Классический ответ такой: препроцессор последовательно перебирает пути в INCLUDE_PATH начиная с первого. Относительный путь из директивы include разрешается относительно (sic) папки из INCLUDE_PATH. Если файл не найден, переходим к следующему элементу INCLUDE_PATH. Если INCLUDE_PATH исчерпан, компилятор сообщает об ошибке.

Но Apple как всегда вносит свои коррективы. При сборке в XCode дополнительно используются т.н. header map. Это индекс всех заголовочных файлов в проекте. Если XCode «знает» про foobar.h, то данный файл будет доступен просто по имени (#include<foobar.h>), вне зависимости от фактического размещения на файловой системе.

Это прекрасное решение — до тех пор, пока оно работает как задумано. К сожалению, механизм header map плохо документирован, что не способствует быстрому разрешению проблем. Постараюсь восполнить этот пробел.


Header Map

определение
Header Map представляет собой индексный файл, генерируемый XCode. Файл называется по имени текущей target и имеет расширение «.hmap». Индекс представляет собой множество строковых пар ключ—значение. Индекс используется для сопоставления полного пути к заголовочному файлу по аргументу директивы include. Поиск в индексе не чувствителен к регистру. Поиск в индексе имеет больший приоритет по сравнению с INCLUDE_PATH. Это сделано для ускорения сборки.

По умолчанию header map активна. Для отключения нужно создать переменную USE_HEADERMAP=NO в конфигурации проекта.

А что если в проекте используются несколько заголовочных файлов с одинаковыми именами? С настройками по умолчанию, в индекс попадет только один из этих файлов — какой именно, предсказать невозможно. Та же самая проблема возникает при совпадении имени файла в проекте с системным заголовочным файлом, без учета регистра (ex: Time.h).


Просмотр Header Map


Для диагностики бывает полезно заглянуть в сгенерированный header map. Это бинарный файл причем с замороченной оптимизацией общих подстрок. Хорошо, что добрые люди написали программу для конвертации в текстовый формат.

python headermap.py build/hmap.build/Debug/primary.build/primary.hmap

Здесь primary — имя цели.

bar.h	/Users/nickz/hmap/bar.h
foo.h	/Users/nickz/hmap/foo.h
primary/foo.h	/Users/nickz/hmap/foo.h

Если с foo.h и bar.h все более-менее понятно, то primary/foo.h вызывает вопросы. Как выяснилось, XCode создает такие записи для заголовочных файлов, являющихся членами текущей цели. (Для того, чтобы XCode позволил сделать заголовочным файлам членство в цели, нужно добавить в нее copy headers build phase.)

В качестве префикса используется PRODUCT_NAME, причем если его поменять, в header map по-прежнему будет попадать старый префикс (проявляется на 3.2.5, возможно исправлено в новых версиях). Лечится закрытием–открытием проекта.


Настройки


USE_HEADERMAP = [YES]/NO
Вкл/выкл.

HEADERMAP_INCLUDES_FLAT_ENTRIES_FOR_TARGET_BEING_BUILT = [YES]/NO
Включать в header map заголовочные файлы, которые являются членами активной цели. На 3.2.5 игнорируется.

HEADERMAP_INCLUDES_FRAMEWORK_ENTRIES_FOR_ALL_PRODUCT_TYPES = [YES]/NO
Включать в header map заголовочные файлы, которые являются членами активной цели. Файлы получают префикс $(PRODUCT_NAME)/. Если значение настройки —YES, такие записи создаются для любых типов целей; иначе только если тип цели — Framework.

HEADERMAP_INCLUDES_PROJECT_HEADERS = [YES]/NO
Включать в header map все заголовочные файлы из проекта без учета целей. На 3.2.5 игнорируется.

Полный список настроек.
Tags:
Hubs:
+13
Comments7

Articles