Переменные, установленные с помощью PARENT_SCOPE, пусты в соответствующей дочерней области. Почему?

Рассмотрим следующий минимальный пример:

.
├── bar
│   └── CMakeLists.txt
└── CMakeLists.txt

где ./CMakeLists.txt

project( foo )
cmake_minimum_required( VERSION 2.8 )

set( FOO "Exists in both, parent AND in child scope." )

add_subdirectory( bar )
message( STATUS "Variable BAR in ./     = ${BAR}" )
message( STATUS "Variable FOO in ./     = ${FOO}" )

и ./bar/CMakeLists.txt есть

set( BAR "Exists in parent scope only." PARENT_SCOPE )
message( STATUS "Variable BAR in ./bar/ = ${BAR}" )

Соответствующая часть вывода cmake такова:

...
-- Variable BAR in ./bar/ =
-- Variable FOO in ./bar/ = Exists in both, parent AND in child scope.
-- Variable BAR in ./     = Exists in parent scope only.
-- Variable FOO in ./     = Exists in both, parent AND in child scope.
...

Поскольку переменная BAR помещена в родительскую область, я ожидаю, что она будет доступна и в текущей дочерней области (и в последующих) — точно так же, как переменная FOO, которая для начала определена родительской областью. Но, как видно из приведенных выше строк, переменная BAR пуста в ./bar/CMakeLists.txt, что привело меня к следующим вопросам:

Почему измененная родительская область недоступна сразу в дочерней области, ./bar/? Можно ли это смягчить? Если да, то как? И если нет, то что такое обходной путь? Или я полностью упускаю что-то очевидное?

Контекст: мой проект состоит из нескольких исполняемых файлов и библиотек. Для библиотеки, например. bar, я хотел бы установить переменную bar_INCLUDE_DIR, которая добавляется к путям включения любого зависимого исполняемого файла, то есть target_include_directories( my_target PUBLIC bar_INCLUDE_DIR ).


person nils    schedule 27.04.2015    source источник


Ответы (5)


Контекст: мой проект состоит из нескольких исполняемых файлов и библиотек. Для библиотеки, например. bar, я хотел бы установить переменную bar_INCLUDE_DIR, которая добавляется к путям включения любого зависимого исполняемого файла.

Есть гораздо лучший способ сделать это, чем устанавливать переменные в родительской области. CMake позволяет цели указать включаемые каталоги, символы препроцессора и т. д., которые могут использовать определенные цели. В вашем случае вы можете использовать target_include_directories.

Например:

target_include_directories(my_target PUBLIC my_inc_dir)
person Lindydancer    schedule 27.04.2015
comment
На самом деле, это то, что я использую. Но поскольку my_target находится в одноуровневой области относительно требуемой библиотеки bar, я использую обход через родительскую область, чтобы перенаправить расположение включаемого каталога. - person nils; 27.04.2015
comment
Вам не нужны переменные для передачи этой информации. Все, что вам нужно сделать, это применить target_include_directories к библиотеке, и все другие цели, которые ее используют, автоматически получат соответствующие каталоги в своем пути включения. - person Lindydancer; 27.04.2015
comment
Аааа, теперь я понимаю, что вы имеете в виду. Действительно, намного лучше. Спасибо за вашу настойчивость! - person nils; 27.04.2015
comment
@nils В обновленной документации говорится Note that it is not advisable to populate the INSTALL_INTERFACE of the INTERFACE_INCLUDE_DIRECTORIES of a target with paths for dependencies. That would hard-code into installed packages the include directory paths for dependencies as found on the machine the package was made on. Итак, вы также обязательно указать BUILD_INTERFACE - person Antonio; 27.04.2015
comment
@nils Если вам удастся заставить его работать для вашего примера проекта, пожалуйста, разместите код где-нибудь: я пробовал в проекте, который у меня есть, и он не сработает, но это определенно может быть, потому что это сложный проект, и есть несколько других вещей что делают мои файлы CMake. - person Antonio; 27.04.2015

Я не вижу ничего, что не соответствовало бы документация команды SET

Если присутствует PARENT_SCOPE, переменная будет установлена ​​в области над текущей областью. Каждый новый каталог или функция создает новую область. Эта команда установит значение переменной в родительский каталог или вызывающую функцию (в зависимости от того, что применимо к данному случаю).

./bar/CMakeLists.txt

set( BAR "This is bar." PARENT_SCOPE ) #<-- Variable is set only in the PARENT scope
message( STATUS "Variable BAR in ./bar/ = ${BAR}" ) #<--- Still undefined/empty

Вы всегда можете сделать:

set( BAR "This is bar." ) #<-- set in this scope
set( BAR ${BAR} PARENT_SCOPE ) #<-- set in the parent scope too

Grep для PARENT_SCOPE в поставляемых модулях в вашей установке, например FindGTK2

if(GTK2_${_var}_FOUND)
   set(GTK2_LIBRARIES ${GTK2_LIBRARIES} ${GTK2_${_var}_LIBRARY})
   set(GTK2_LIBRARIES ${GTK2_LIBRARIES} PARENT_SCOPE)
endif()
person Peter    schedule 27.04.2015
comment
Что ж, рассмотрим переменную FOO, которая для начала определена в родительской области, затем эта переменная также доступна в дочерней области (см. обновленный пример). Так почему бы BAR не быть доступным в дочерней области, если оба находятся в одной и той же (родительской) области? Я ожидаю, что в документации будет упомянуто такое поведение. - person nils; 27.04.2015
comment
Это можно рассматривать как побочный эффект. - person Peter; 27.04.2015

Питер хорошо объяснил причину такого поведения.

Обходной путь, который я обычно использую в этом случае, заключается в установке кэшированной переменной, которая будет видна везде:

set(BAR "Visible everywhere"
        CACHE INTERNAL ""
)

INTERNAL сделать его невидимым из cmake-gui. INTERNAL подразумевает FORCE, обеспечивая обновление, если вы что-то измените для пример в вашей структуре папок. Пустая строка — это строка описания, которую вы можете заполнить, если считаете это необходимым.

Обратите внимание, однако, что правильный подход – прикреплять свойства к целевым объектам, когда это возможно, например с помощью target_incude_directories и распространять их на другие цели, устанавливая зависимости.

person Antonio    schedule 27.04.2015
comment
Спасибо, что указали на вариант CACHE. Однако, поняв, что имел в виду @Lindydancer, я предпочитаю этот метод. - person nils; 27.04.2015
comment
Это определенно неправильный подход, поскольку он может повлиять на каждую функцию и контекст случайным образом. CACHE удалит исходное значение, FORCE заменит соответствующий параметр -D. Вместо того, чтобы использовать кеш, вы должны установить значение в обоих контекстах, в родительском и в дочернем, а не везде. В противном случае вообще не используйте переменные и используйте вместо них свойства, прикрепленные к цели или чему-то еще. - person Andry; 14.02.2019
comment
@andry Лучший подход действительно состоит в том, чтобы использовать как можно больше свойств, связанных с целью, и распространять их все, указав зависимости. Этот ответ касается необходимости (если таковая существует, я заявил, что это действительно обходной путь) установить глобальную переменную, видимую везде, особенно в каталогах родственных элементов. - person Antonio; 15.02.2019
comment
FORCE здесь избыточен, так как INTERNAL подразумевает FORCE. См.: cmake.org/cmake/help/v3.0/command/ set.html - person Mustafa Kemal GILOR; 25.11.2019

Если вам нужно только установить переменную как в локальной, так и в родительской области, макрос может помочь уменьшить дублирование:

macro(set_local_and_parent NAME VALUE)
  set(${ARGV0} ${ARGV1})
  set(${ARGV0} ${ARGV1} PARENT_SCOPE)
endmacro()
person Istvan Balazs Opra    schedule 30.04.2021

Каждая переменная в cmake имеет свою собственную область видимости, поэтому опасно использовать случай, когда переменная автоматически распространяется в дочернем контексте, потому что она может мешать ей из родительской области!

Но вы можете установить еще одну переменную в дочерней области, чтобы проверить ее позже, вместо того, чтобы перечитывать родительскую:

./bar/CMakeLists.txt:

set( BAR "Exists in parent scope only." PARENT_SCOPE )
set( _somerandomid_BAR "Exists in child scope only.")
message( STATUS "Variable BAR in ./bar/ = ${_somerandomid_BAR}" )

Теперь, если в вашем коде есть циклы, вы можете протестировать обе переменные:

foreach(...)
  ...
  # read a variable token name and token value, for example, from a configuration file
  set(my_var_name_token ...)
  set(my_var_value_token ...)
  ...
  # parse a variable name and value tokens into a real name and value
  set(my_var_name ...)
  set(my_var_value ...)
  ...
  if (DEFINED ${my_var_name})
    if (DEFINED local_${my_var_name})
      message("has been defined before and was resetted");
    else()
      message("has been defined before and was not resetted");
    endif()
  else()
    if (DEFINED local_${my_var_name})
      message("has not been defined before and was setted");
    else()
      message("has not been defined before and was not touched");
    endif()
  endif()
  ...
  # sets parsed name and value into cmake context
  set(${my_var_name} "..." PARENT_SCOPE)

  # Do save all values has been setting from this function to differently compare and 
  # validate them versus already existed before:
  # 1. If has been set before, then must be set to the same value, otherwise - error
  # 2. If has not been set before, then should set only once, otherwise - ignore new 
  # value (a constant variable behaviour)
  set(local_${my_var_name} "...")
  ...
endforeach()
person Andry    schedule 14.02.2019