Введение
Статья 574 уже использовала PRIVATE в target_link_libraries без подробного объяснения — в этой статье разберём три ключевых спецификатора видимости (PUBLIC, PRIVATE, INTERFACE), определяющих, как именно настройки и зависимости одной цели распространяются (или не распространяются) на цели, которые от неё в свою очередь зависят — одна из самых важных, но часто недопонимаемых концепций современного CMake.
Концепция
PRIVATE зависимость используется текущей целью для своей собственной реализации, но НЕ распространяется на цели, зависящие от текущей цели — если библиотека A приватно зависит от библиотеки B, то цель C, зависящая от A, не получает автоматического доступа к B и её настройкам через эту зависимость. PUBLIC зависимость используется и текущей целью, и автоматически распространяется на все цели, зависящие от текущей — это нужно, когда зависимость «видна» в публичном интерфейсе текущей библиотеки (например, тип из библиотеки B используется как параметр публичной функции библиотеки A, и потребители A, соответственно, тоже должны иметь доступ к заголовкам B). INTERFACE зависимость, в противоположность PRIVATE, НЕ используется текущей целью для собственной реализации, но распространяется на зависящие от неё цели — менее частый случай, обычно применимый для INTERFACE-библиотек (статья 574) или специфичных ситуаций, где сама текущая цель не нуждается в зависимости непосредственно, но обязана передать её дальше.
Пример кода: Видимость зависимостей
# Демонстрация разницы PRIVATE/PUBLIC через реалистичный пример трёх связанных библиотек
add_library(JsonParser json_parser.cpp) # внутренняя реализация использует какую-то JSON-библиотеку
add_library(SomeJsonLibrary INTERFACE) # представим, что это внешняя, header-only JSON-библиотека
target_link_libraries(JsonParser PRIVATE SomeJsonLibrary)
# ^ PRIVATE — JsonParser использует JSON-библиотеку ВНУТРИ своей реализации,
# но не "протекает" наружу — потребители JsonParser НЕ обязаны знать о SomeJsonLibrary,
# если публичный API JsonParser НЕ использует типы из SomeJsonLibrary напрямую
add_library(NetworkClient network_client.cpp network_client.h)
target_link_libraries(NetworkClient PUBLIC JsonParser)
# ^ PUBLIC — если network_client.h ВОЗВРАЩАЕТ или ПРИНИМАЕТ типы из JsonParser
# в своих публичных функциях, потребители NetworkClient АВТОМАТИЧЕСКИ
# получают доступ к заголовкам JsonParser, без необходимости отдельно его подключать
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE NetworkClient)
# MyApp автоматически получает доступ И к NetworkClient, И (благодаря PUBLIC выше) к JsonParser,
# но НЕ к SomeJsonLibrary (благодаря PRIVATE на уровне JsonParser) — если только сам не подключит её отдельно
# Практическая проверка — какие заголовки реально ДОСТУПНЫ для конкретной цели
# main.cpp может содержать:
#include "network_client.h" // OK — прямая, явная зависимость
#include "json_parser.h" // OK — благодаря PUBLIC от NetworkClient на JsonParser
#include "some_json_lib.h" // ОШИБКА КОМПИЛЯЦИИ — недоступен, поскольку JsonParser->SomeJsonLibrary это PRIVATE
Пояснения к коду
Цепочка MyApp → NetworkClient → JsonParser → SomeJsonLibrary с разными спецификаторами видимости на каждом шаге показывает, как именно «просачивание» зависимостей контролируется индивидуально для каждого звена этой цепочки — PUBLIC от NetworkClient к JsonParser означает, что заголовки JsonParser автоматически становятся доступны и для MyApp (через транзитивное распространение), тогда как PRIVATE от JsonParser к SomeJsonLibrary «обрывает» дальнейшее распространение именно на этом конкретном шаге, и MyApp не получает автоматического доступа к этой более глубокой, «внутренней» зависимости JsonParser. Закомментированный пример с попыткой подключить заголовок some_json_lib.h непосредственно из main.cpp показывает конкретное, наблюдаемое следствие этой настройки — компилятор не найдёт соответствующий путь включения, поскольку PRIVATE-зависимость JsonParser от SomeJsonLibrary намеренно не передаёт этот путь включения дальше, к MyApp.
Подводные камни
- Использование
PUBLICпо умолчанию «на всякий случай», без реального анализа, действительно ли зависимость видна в публичном интерфейсе текущей цели — это самая частая, практически значимая ошибка при работе с видимостью зависимостей, и избыточное использованиеPUBLICтам, где была бы достаточнаPRIVATE, «просачивает» внутренние, реализационные детали текущей цели наружу, ко всем её потребителям, что усложняет граф зависимостей всего проекта и может затруднить последующую замену внутренней реализации (например, замену одной JSON-библиотеки на другую) без затрагивания всех транзитивных потребителей, которые формально не должны были знать об этой внутренней детали реализации. - Использование
PRIVATEдля зависимости, типы которой реально используются в публичном заголовочном файле текущей цели — это прямо противоположная ошибка: еслиnetwork_client.hреально объявляет публичную функцию, принимающую или возвращающую тип изJsonParser, но зависимость отJsonParserпомечена какPRIVATE, потребителиNetworkClientполучат ошибку компиляции при попытке использовать эту публичную функцию (компилятор не найдёт определение используемого в её сигнатуре типа), что является прямым, наблюдаемым следствием неверно выбранной видимости. - Путаница между видимостью зависимости (PUBLIC/PRIVATE/INTERFACE в
target_link_libraries) и видимостью отдельных свойств (PUBLIC/PRIVATE/INTERFACE вtarget_include_directories/target_compile_definitions, статья 576) — хотя используется тот же набор из трёх ключевых слов с аналогичной общей логикой «что видно текущей цели и что распространяется далее», это технически разные команды CMake, применяемые к разным аспектам конфигурации цели, и согласованное, продуманное использование всех соответствующихtarget_*команд с правильно выбранной видимостью для каждого конкретного свойства/зависимости — важная часть качественного, поддерживаемого описания сборки проекта. - Игнорирование транзитивности
PUBLIC-зависимостей при анализе реального графа зависимостей проекта, особенно для крупных проектов с глубокими цепочками зависимостей между множеством целей — реальный, эффективный набор зависимостей конкретной цели может оказаться значительно шире явно перечисленных непосредственно для неё зависимостей, именно из-за транзитивного распространения черезPUBLICна промежуточных уровнях, и понимание полной картины графа зависимостей (а не только непосредственных, прямых связей) важно для осознанного управления сложностью зависимостей крупного проекта.