[CMake] lexical scoping ... back to the future !

François Mauger mauger at lpccaen.in2p3.fr
Thu Feb 23 19:10:42 EST 2012


 >>> On 23/02/2012 02:00, Jean-Christophe Fillion-Robin wrote:
> Hi François,
>
> Would the use of function be helpful ?
>
> See http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:function
> and http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:set
>
> "If PARENT_SCOPE is present, the variable will be set in the scope above
> the current scope. Each new directory or function creates a new scope.
> This command will set the value of a variable into the parent directory
> or calling function (whichever is applicable to the case at hand)."
>
> I created a small example showing that variable set within a function
> don't interfere with the parent scope if not explicitly required.
>
> git clone git://gist.github.com/1888836.git
> <http://gist.github.com/1888836.git>
> cmake -P 1888836/cmake-illustrate-function-scope.cmake
>

Hi JC

I've also played around with functions vs macros to check for scoping 
features and I have basically written the same test script for my own
learning. I agree that SET coupled with the PARENT_SCOPE directive can 
solve some scope issues within function, preserving the very useful 
ability to have "pure" local variables and still making possible for a 
given variable to be visible ONE scope level up. The problem is when you 
need to invoke several find_package commands from a thread of nested 
function/macros, eventually with some recursive pattern.
AFAIU, a find_package() command implies to be invoked from a macro, and 
not a function, to preserve the global scoping of the xxx_LIBRARIES and 
xxx_INCLUDE_DIRS variables set from a FindXxx.cmake or xxx-config.cmake 
file, and thus make them visible at the top-level CMakeLists.txt file 
for example.
May be I'm wrong and I miss something but it seems other people, trying 
to setup a set of homemade macros/functions to traverse automatically a 
dependency tree, have also faced this kind of scoping issue.

Because I have to build some complex applications and libraries from
a large set of "low-level" dedicated home-made libraries that have 
dependency links between them (not cyclic of course, but possibly 
diamond shaped),  I've implemented a basic CMake dependency driver that 
uses a simple lists of dependency rules for any new home-made library 
(let's call it 'xxx') that depends on some other libraries I have at hand.

from the xxx's top-level CMakeLists.txt,  I just have to set:
{{{
SET( xxx_deprules "GSL:>=1.12;Python:>=2.6:<=2.7.3;foo:>=2.1;bar:=3.2" )
}}}
which means I want to check and use:
- GSL libs with ver >=1.12
- Python (interp+libs) with ver in [2.6-2.7.3]
- my home-made foo lib with ver >=2.1
- my home-made bar with ver==3.2
and then automatically build some xxx_INCLUDE_DIRS and xxx_LIBRARIES 
from these rules.

There is a special set of macros that are given the "xxx_deprules" list 
and are supposed to invoke the 'find_package (...)' command to check if 
the dependencies in the list are fulfilled. Then the macros 
collect/aggregate the various [GSL|Python|foo|bar]_INCLUDE_DIRS and 
[GSL|Python|foo|bar]_LIBRARIES variables. Note that packages like GSL 
and Python or Boost use typically the so-called 'Module' find_package 
mode (FindGSL, FindPythonInterp, FindBoost...)
while my foo and bar package uses the 'Config' mode through
well-formatted foo-config.cmake and bar-config.cmake files.
Note also that because the FindXxx.cmake files provided with the CMake 
distribution does not follow a strict pattern and standard (variable 
names...), I have to wrap the find Module  within some dedicated macros 
to standardize the output (examples: Boost_VERSION gives "104700" and 
not "1.47.0", argh ! ).

So far, so good. Now assume that the "foo" lib depends also on external 
libs, say : 'john' and 'doe'. because they are also home-made libraries, 
I also provide some  'john-config.cmake' and 'doe-config.cmake' files 
for both.

The dependency check for the 'xxx' lib is implemented in the following way:
0) From the xxx Top-level CmakeLists.txt file, I loop on the list of 
dependecy rules (gsl, python, foo...)
1 ) Example for "foo:>=2.1" : invoke
{{{
find_package (foo 2.1 REQUIRED NO_MODULE HINT ...)
}}}
   this loads the 'foo-config.cmake' file with all
   its exported DLL targets : here the john DLL and the doe DLL
2) because foo has been build with john==1.2 and doe==3.1.4, the 
foo-config.cmake calls explicitely :
{{{
    find_package (john 1.2 EXACT REQUIRED NO_MODULE HINT ...)
}}}
and
{{{
    find_package (doe 3.1.4 EXACT REQUIRED NO_MODULE HINT ...)
}}}
with the consequence that both john-config.cmake and doe-config.cmake
will be sourced with their own exported targets.
3) again if john also depends on other libs (exported targets), the 
process recurses down to the leaf dependency

the result is that, given a simple dependency rule
in the top-level cmake file :
   "GSL:>=1.12 ; Python:>=2.6:<=2.7.3 ; foo:>=2.1 ; bar:=3.2"
we can rebuild the full path of the
depencency tree, including hidden parent dependencies exported target, 
and store it in some global variables to be used
with the include_directories(...) and target_link_libraries(...) commands.
this system is based on a strict design pattern I use for my home-made 
library packages with a standard interface (names of the variables, 
location of various .cmake files...) and a set of smart *macros* that 
drive the recursion, preserving the global scope of all 
dependency-related variables and exported targets. The main issue comes 
from the fact that to write such macros and algos, one needs to use many 
temporary variables with dynamic naming such as :
{{{
foreach (depname ${depnames})
    set ( ${depname}_DIR ...)
...
}}}
within foreach loops or if/endif tests, also to tokenize some variables 
and locally parse directives stored in string variables.
Without local scoping, this turns to be a mess when you meet a 
dependency recursion with at least two levels (variable names collision, 
corruption of variables in the parent scope due to reuse of a variable 
name in a daughter macro...).

Maybe there is another way in the CMake language to design such a
dep-driver without "local scoping". From my programing experience
with various languages such as C/C++/Java/Python/shell, local scoping
is a natural way to achieve this without significant issues. In short, 
it is a fundamental tool for most language. Here CMake lacks this 
feature and makes life harder (at least mine) !

Even if I've found a solution to workaround this Cmake behaviour, I'm 
still interested in this general question :
" Will Cmake implement some kind of "local scoping" in the future ? "

;-)

Apologize for this long thread, but I hope it will help you to figure 
out the pb I have faced.

cheers

frc
--

> Hth
> Jc
>
> On Wed, Feb 22, 2012 at 7:07 PM, François Mauger
> <mauger at lpccaen.in2p3.fr <mailto:mauger at lpccaen.in2p3.fr>> wrote:
>
>     Hi CMakers,
>
>     In november 2007, there was a long thread titled "lexical scoping".
>     The discussion was really interesting and was suggesting that some
>     "local scoping" features for variables could be implemented in CMake...
>     particularly from macros, which are probably the practical case that
>     causes most issues when users implement several nested levels of
>     macro invocations with many intermediate temporary variables that
>     unfortunately aggregate in the global scope. It is really easy to
>     face such problem in a rather large project driven with Cmake (a
>     dependency driver invoking the find_package command in a foreach
>     loop must be done from a macro AFAIK).
>     Unless I missed something in the jungle of CMake threads and doc,
>     I'm afraid I was not able to find some satisfactory solution other
>     than hacks (using very long variable names to "emulate" some pseudo
>     local scopes). this is tedious and bug prone.
>
>     Browsing the last 4 years of archives and some additionnal docs, I
>     was not able to find a single trace of such new feature :
>     implementing "local scoping" in CMake (whatever "local scoping"
>     could rationally mean).
>
>     What is the current situation on users' side ? Is there any pressure
>     for "local scoping" ? Maybe too few people ask for it so CMake
>     developpers do not consider it as a priority... or no clear definition
>     of "local scoping" has been patterned ?
>     more what is the view of the CMake devel team ? do they consider
>     this idea as a good one or not ?
>
>     Thanks a lot for hints and advices.
>
>     Best regards
>     frc
>     --
>     François Mauger
>     LPC Caen-CNRS/IN2P3-UCBN-ENSICAEN
>     Département de Physique -- Université de Caen Basse-Normandie
>
>     --
>
>     Powered by www.kitware.com <http://www.kitware.com>
>
>     Visit other Kitware open-source projects at
>     http://www.kitware.com/__opensource/opensource.html
>     <http://www.kitware.com/opensource/opensource.html>
>
>     Please keep messages on-topic and check the CMake FAQ at:
>     http://www.cmake.org/Wiki/__CMake_FAQ
>     <http://www.cmake.org/Wiki/CMake_FAQ>
>
>     Follow this link to subscribe/unsubscribe:
>     http://www.cmake.org/mailman/__listinfo/cmake
>     <http://www.cmake.org/mailman/listinfo/cmake>
>
>
>
>
> --
> +1 919 869 8849
>


-- 
François Mauger
   Groupe "Interactions Fondamentales et Nature du Neutrino"
   NEMO-3/SuperNEMO Collaboration
LPC Caen-CNRS/IN2P3-UCBN-ENSICAEN
Département de Physique -- Université de Caen Basse-Normandie
Adresse/address:
   Laboratoire de Physique Corpusculaire de Caen (UMR 6534)
   ENSICAEN
   6, Boulevard du Marechal Juin
   14050 CAEN Cedex
   FRANCE
Courriel/e-mail: mauger at lpccaen.in2p3.fr
Tél./phone:      02 31 45 25 12 / (+33) 2 31 45 25 12
Fax:             02 31 45 25 49 / (+33) 2 31 45 25 49



More information about the CMake mailing list