Променливите, зададени с 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
СИЛАТА е излишна тук, тъй като ВЪТРЕШНОТО предполага СИЛА. Вижте: 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