[CMake] Is CMake powerful enough?
gga
ggarra at advancedsl.com.ar
Fri Jun 8 09:50:17 EDT 2007
Oliver Kullmann wrote:
> The directory structure is "fractal" (or "recursive") and "dynamic", so
> for example modules have subdirectories Module/tests, containing generic
> tests for the components provided by the modules, where Module/tests might
> contain a tests-subdirectory itself for testing the testsystem etc.; additions
> are detected automatically, no registrations whatsoever is required for tests,
> but by running "make check" all new or changed tests are compiled and executed
> (of course, the produced link-libraries, executables, text files etc. are
> stored in some other directory-hierarchy, similar to the source-hierarchy, but,
> of course, also with many differences).
> So in the first stage stage of the the build process here, from configuration values,
> parameter values, targets given and the current state of the file-system we need
> to compute dynamically the list of second-stage-targets and -rules, and then we need
> to run make (this is actually achieved by the 2-stage-process of make itself,
> but it's quite clumsy).
This is not a problem. CMake provides FILE( GLOB ...) and similar
constructs to do what you want.
> It appears now that the computational power of cmake is rather restricted?
Hardly. cmake's language has most of the features available on
scripting languages (regexes, arrays, macros, variables, etc), albeit
implemented in a somewhat more cumbersome syntax than, say, ruby.
I'm using cmake on a huge project that requires swig, flex, bison,
multiple APIs, 10 different libraries, a different build for each api, 3
OSes, and cmake has really excelled at it (this after having tried to
use python's scons, ruby's rake and autoconf for it). scons and rake
were not really up to the task. autoconf was, but maintaining it would
have been too painful and windows would have needed to be handled
separately. cmake has been a life saver.
> There seem to be no functions?? (While we had hoped that there are many
> more than in make?) Even just a shell-call seems to need to be programmed
> by ourselves?? (I can't believe that, but couldn't find it.)
CMake does not have functions, but it does have a very simple to use
macro system which in practice acts like functions.
Shell calls can be added by ADD_CUSTOM_COMMAND() or EXECUTE_PROGRAM().
The first one creates an actual Makefile rule. The second one gets run
only when cmake is run. These functions are very easy to use and can
automatically store the outputs and results into cmake variables.
>
> A lesser problem (likely) seems to be that there is no really fine-grained
> control over where all those auxiliary files go? It seems the basic assumption
> is that of having a build-directory (used only once), where all sort of stuff
> can be dumped to, and then with the final "make install" the "relevant"
> files are moved elsewhere? This does not neatly fit with our permanent use
> of the build-system, but likely one could live with it (by automatically
> always running "make install").
This is indeed somewhat of a problem with cmake.
Out-of-source builds are not 'built-in' so you need to do something like:
> mkdir buildir && cd buildir && cmake .. && make
CMake provides some variables that can be set to control where output
goes (see Wiki - useful variables). With them you can control where .a,
.so, executables and the like get placed.
The only thing you don't really have as much control over is where .o
files go, as CMAKE_BINARY_DIR is more or less broken, but in principle
you should not have to worry about that.
I am attaching a simple 'mk' bash script I use to run cmake (and normal
makefiles) in a cross-platform way (and to simplify the creating of
out-of-source builds) and a simple optional FindBuildDir.cmake that,
optionally, works with it.
I think you'll find that useful in understanding how cmake can be made
to work for a large project.
>
> Another, perhaps more serious problem: We are exploiting the tree structure of
> the library, that is, at every point in the tree when calling make exactly
> the sub-tree at this node is considered (everything down, nothing up).
> This works very well for example for testing first the local module (we use
> continuous testing), then going one step up (which needs more time), and before
> having a break we run "make check" at the root of the (source-)tree. To avoid cluttering
> the library with many makefiles, at every node (of the source-tree!) there sits only a *link*
> to one generic makefile (so we have only *one* makefile!), and that's it.
> Now the cmake model seems to be, that either you clutter your source-tree with
> make-files, and then you get the binaries etc. delivered to the source tree (we
> don't want that), or you create the make-files in the "build-tree", but then
> you have to operate in that tree, not in the source-tree?!? (We find it very
> convenient to only work in the source-sub-directory we are currently concerned
> with, and the build system directs all output to the right places (somewhere
> else); and having to "be" at two different places at the same time, in the
> source-directory and in the related "build-directory", might open the
> possibility for subtle bugs.)
No, this is also trivial to do with cmake. My mk script does not handle
this, but it should be easy to add. Rather than perhaps setting the
variables from the bash script, you'd set them from your own cmake
include file.
Also note that cmake automatically generates rules for you (which you
can easily list using 'make help', so running a particular test can also
often be done from the root tree by doing "make <rule>").
>
> Finally, it somehow seems to us that the conception of cmake is not really that
> of a powerful extension of make, but more of a convenient "user-interface", with
> the typical trade-off: What the user-interface does, it does convenient and well,
> but what it not does is very hard to integrate?
>
Pretty much. However cmake really does make creating Makefiles a breeze
and helps to hide most of the limitations present in make and nmake,
with a syntax that is much easier and readable than autohell (sorry,
autoconf). Also, the Makefiles cmake creates are EXTREMELY efficient --
more even than those of autoconf.
--
Gonzalo Garramuño
ggarra at advancedsl.com.ar
AMD4400 - ASUS48N-E
GeForce7300GT
Kubuntu Edgy
-------------- next part --------------
#!/bin/bash --norc
#
# Determine CPU architecture
#
KERNEL=`uname -s`
if [[ $KERNEL == CYGWIN* ]]; then
KERNEL=Windows
RELEASE=(`cmd /c 'ver'`)
RELEASE=${RELEASE[5]%.[0-9]*}
elif [[ $KERNEL == MINGW* ]]; then
KERNEL=Windows
RELEASE=(`cmd /c 'ver'`)
RELEASE=${RELEASE[4]%.[0-9]*}
else
RELEASE=`uname -r`
fi
CMAKE_OPTS=${CMAKE_OPTS=""}
export CMAKE_NATIVE_ARCH=32
export CMAKE_BUILD_TYPE=Release
export OS_32_BITS=1
export LDFLAGS=
export CXXFLAGS=
arch=`uname -a`
if [[ $arch == *x86_64* ]]; then
CMAKE_NATIVE_ARCH=64
export OS_64_BITS=1
else
if [[ $arch == *ia64* ]]; then
CMAKE_NATIVE_ARCH=64
export OS_64_BITS=1
unset OS_32_BITS
fi
fi
export CMAKE_BUILD_ARCH=$CMAKE_NATIVE_ARCH
OS=$KERNEL-$RELEASE
usage()
{
cat <<EOF
$0
$0 [options] [--] [make options]
Wrapper around CMake to easily create out of source builds
(ie. compilations where everything goes into a directory).
For this platform (default settings):
BUILD/$OS-$CMAKE_BUILD_ARCH/$CMAKE_BUILD_TYPE
It must be run from a directory with a valid CMakeLists.txt
file outside of the build tree.
Options:
-q quiet build
-v verbose build (default)
-j N number of cpus to use (default: ${CMAKE_PROCS=1})
-G <name> use a different cmake generator (default: Unix Makefiles)
debug|release|reldebug|small
debug - build a debug build
release - build a release build (default)
reldebug - build a release build with debug information
small - build a small release build
both|32|64
Builds both 32/64 bit versions, 32-bit only,
64-bit only (default: $CMAKE_BUILD_ARCH)
cache - Cleans all CMakeCache.txt files
swig - Cleans all swig files
clean - Cleans BUILD/$OS-$CMAKE_BUILD_ARCH/$CMAKE_BUILD_TYPE
cmake - Runs cmake only.
EOF
exit 1
}
#
# Parse command-line options
#
clean=0
if [[ $OS == Windows* ]]; then
cmake_generator="NMake Makefiles"
else
cmake_generator="Unix Makefiles"
fi
opts='VERBOSE=1'
RUN_MAKE=1
while [ $# -gt 0 ]; do
case $1 in
64)
shift
CMAKE_BUILD_ARCH=64
;;
32)
shift
CMAKE_BUILD_ARCH=32
export LDFLAGS=-m32
export CXXFLAGS=-m32
export CFLAGS=-m32
;;
both)
shift
CMAKE_BUILD_ARCH='Both'
;;
cache)
shift
builddir=BUILD/$OS-$CMAKE_BUILD_ARCH/$CMAKE_BUILD_TYPE
echo
echo "Removing old cmake caches $builddir..."
echo
# Remove cache files
find $builddir -name CMakeCache.txt -exec rm {} \;
# Remove makefiles
find $builddir -name Makefile -exec rm {} \;
;;
clean)
if [ -r CMakeLists.txt ]; then
shift
if [ -d BUILD ]; then
clean=1
fi
else
break
fi
;;
cmake)
shift
RUN_MAKE=0
;;
debug)
shift
export CMAKE_BUILD_TYPE=Debug
;;
release)
shift
export CMAKE_BUILD_TYPE=Release
;;
small)
shift
export CMAKE_BUILD_TYPE=MinSizeRel
;;
swig)
shift
# Remove swig wrappers
builddir=BUILD/$OS-$CMAKE_BUILD_ARCH/$CMAKE_BUILD_TYPE
echo
echo "Removing old swig wrappers $builddir..."
echo
find $builddir -name '*_wrap.*' -exec rm {} \;
;;
-h)
shift
usage
;;
--help)
shift
usage
;;
-j)
shift
if [ $# == 0 ]; then
echo "Missing parameter for -j!"
usage
fi
CMAKE_PROCS=$1
shift
;;
-q)
shift
opts=""
;;
-v)
shift
opts="VERBOSE=1"
;;
-G)
shift
cmake_generator=$1
shift
;;
--)
shift
break
;;
*)
break
;;
esac
done
#
# Simple function to run a command and print it out
#
run_cmd()
{
echo
echo "> $*"
echo
eval command $*
}
#
# Function to just run make on a dir with a Makefile
#
run_make()
{
if [ $RUN_MAKE == 0 ]; then
return
fi
cmd=''
if [[ $cmake_generator == NMake* ]]; then
cmd="nmake $@"
elif [[ $cmake_generator == Visual* ]]; then
return
else
cmd="make -j ${CMAKE_PROCS=1} $@"
fi
run_cmd $cmd
}
#
# Function to run cmake and then run make on generated Makefile
#
run_cmake()
{
builddir=BUILD/$OS-$CMAKE_BUILD_ARCH/$CMAKE_BUILD_TYPE
echo "Buildir ${builddir}"
if [ ! -d $builddir ]; then
cmd="mkdir -p $builddir $builddir/bin $builddir/lib $builddir/tmp"
run_cmd $cmd
fi
cmd="cd $builddir/tmp"
run_cmd $cmd
if [ -r Makefile ]; then
run_make $@
cd ../../../..
return
fi
pwd=$PWD
if [[ $KERNEL == Windows* ]]; then
pwd=`cygpath -d $PWD`
CMAKE_OPTS="-DCMAKE_CFG_INTDIR=/Release ${CMAKE_OPTS}"
fi
cmd="cmake ../../../.. -DCMAKE_SYSTEM=$OS -DEXECUTABLE_OUTPUT_PATH=$builddir/bin -DLIBRARY_OUTPUT_PATH=$builddir/lib -DCMAKE_LIBRARY_PATH=$builddir/lib -DCMAKE_NATIVE_ARCH=$CMAKE_NATIVE_ARCH -DCMAKE_BUILD_ARCH=$CMAKE_BUILD_ARCH -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE -G '${cmake_generator}' ${CMAKE_OPTS}"
run_cmd $cmd
if [ $? != 0 ]; then
exit
fi
run_make $@
cd ../../../..
}
#
# Function used to clean the build directory and exit
#
run_clean()
{
if [ $CMAKE_BUILD_ARCH == "Both" ]; then
CMAKE_BUILD_ARCH=64
run_clean
CMAKE_BUILD_ARCH=32
fi
builddir=$OS-$CMAKE_BUILD_ARCH/$CMAKE_BUILD_TYPE
cd $builddir
run_make clean
echo "Cleaned BUILD/$builddir"
# rm -rf BUILD/$builddir
}
#
# Main routine
#
if [ $clean == 1 ]; then
run_clean
exit 0
fi
if [ -r Makefile ]; then
run_make $opts $@
else
if [ ! -r CMakeLists.txt ]; then
echo "No Makefile or CMakeLists.txt in current directory!"
exit 1
fi
if [ ! -d BUILD ]; then
mkdir BUILD
fi
if [ $CMAKE_BUILD_ARCH == 'Both' ]; then
export CMAKE_BUILD_ARCH=32
export LDFLAGS=-m32
export CXXFLAGS=-m32
export CFLAGS=-m32
run_cmake $opts $@
export CMAKE_BUILD_ARCH=64
export LDFLAGS=
export CXXFLAGS=
export CFLAGS=
run_cmake $opts $@
else
run_cmake $opts $@
fi
fi
-------------- next part --------------
#-*-cmake-*-
#
# This simple CMake extension makes sure that builds get
# created inside a BUILD/os-osversion-arch directory
# and that executables, obj files and lib files get placed
# correctly in subdirectories.
#
#
# Macro to check architecture
#
MACRO( CHECK_ARCHITECTURE )
SET( CMAKE_BUILD_ARCH $ENV{CMAKE_BUILD_ARCH} )
IF( NOT CMAKE_BUILD_ARCH )
SET( CMAKE_BUILD_ARCH "Native" )
ENDIF( NOT CMAKE_BUILD_ARCH )
IF( NOT CMAKE_BUILD_ARCH MATCHES "^(Native|64|32)$" )
MESSAGE( FATAL_ERROR
"CMAKE_BUILD_ARCH set but invalid. "
"Only Native, 64 and 32 are valid settings" )
ENDIF( NOT CMAKE_BUILD_ARCH MATCHES "^(Native|64|32)$" )
#
# Set Native architecture based on void* size
#
INCLUDE(CheckTypeSize)
CHECK_TYPE_SIZE(void* SIZEOF_VOID_PTR)
IF( ${SIZEOF_VOID_PTR} MATCHES "^8$" )
SET( OS_32_BITS 0 )
SET( OS_64_BITS 1 )
SET( CMAKE_NATIVE_ARCH 64 )
ELSE( ${SIZEOF_VOID_PTR} MATCHES "^8$" )
SET( OS_32_BITS 1 )
SET( OS_64_BITS 0 )
SET( CMAKE_NATIVE_ARCH 32 )
ENDIF( ${SIZEOF_VOID_PTR} MATCHES "^8$" )
IF( UNIX )
EXECUTE_PROCESS(
COMMAND uname -a
OUTPUT_VARIABLE OS_ARCH
)
#
# For Linux
#
IF( OS_ARCH MATCHES ".*Linux.*" )
IF( OS_ARCH MATCHES ".*x86_64.*" )
SET( OS_32_BITS 1 )
ELSEIF( OS_ARCH MATCHES ".*ia64.*" )
SET( OS_32_BITS 0 )
ENDIF( OS_ARCH MATCHES ".*x86_64.*" )
ENDIF( OS_ARCH MATCHES ".*Linux.*" )
#
# For SUN
#
IF( OS_ARCH MATCHES ".*SunOS.*" )
EXECUTE_PROCESS(
COMMAND isainfo -v
OUTPUT_VARIABLE SUNOS_ARCH
)
IF ( SUNOS_ARCH MATCHES ".*64-bit.*" )
SET( OS_32_BITS 1 )
ENDIF( SUNOS_ARCH MATCHES ".*64-bit.*" )
ENDIF( OS_ARCH MATCHES ".*SunOS.*" )
IF( APPLE )
#
# @todo: add Apple 64-bit OS detection here
#
ENDIF( APPLE )
ELSE( UNIX )
#
# @todo: add windows 64-bit OS detection here
#
ENDIF( UNIX )
IF ( CMAKE_BUILD_ARCH STREQUAL "Native" )
SET( CMAKE_BUILD_ARCH ${CMAKE_NATIVE_ARCH} )
ENDIF( CMAKE_BUILD_ARCH STREQUAL "Native" )
IF( CMAKE_BUILD_ARCH EQUAL 32 )
IF( NOT OS_32_BITS )
MESSAGE( FATAL_ERROR
"Sorry, but this platform cannot compile 32-bit applications" )
ENDIF( NOT OS_32_BITS )
MESSAGE( STATUS "check compiler" )
IF( CMAKE_COMPILER_IS_GNUCXX )
ADD_DEFINITIONS( -m32 )
SET( LINK_FLAGS "-m32 ${LINK_FLAGS}" )
ELSE( CMAKE_COMPILER_IS_GNUCXX )
MESSAGE( STATUS "checked compiler" )
#
# @todo: add 32-bit compile flags for non-GNU compilers here
#
ENDIF( CMAKE_COMPILER_IS_GNUCXX )
ENDIF( CMAKE_BUILD_ARCH EQUAL 32 )
ENDMACRO( CHECK_ARCHITECTURE )
CHECK_ARCHITECTURE()
#
# Store build type
#
IF(NOT CMAKE_BUILD_TYPE)
SET(CMAKE_BUILD_TYPE Release CACHE STRING
"Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel."
FORCE)
ENDIF(NOT CMAKE_BUILD_TYPE)
IF( NOT CMAKE_SYSTEM )
MESSAGE( FATAL_ERROR "CMAKE_SYSTEM was not set" )
ENDIF( NOT CMAKE_SYSTEM )
#
# @bug in cmake2.5 in windows (workaround)
#
IF( NOT CMAKE_SYSTEM_VERSION )
SET( CMAKE_SYSTEM_VERSION "5.1" )
SET( CMAKE_SYSTEM "${CMAKE_SYSTEM}-${CMAKE_SYSTEM_VERSION}" )
ENDIF( NOT CMAKE_SYSTEM_VERSION )
IF( NOT CMAKE_BUILD_TYPE )
MESSAGE( FATAL_ERROR "CMAKE_BUILD_TYPE was not set" )
ENDIF( NOT CMAKE_BUILD_TYPE )
IF( NOT CMAKE_BUILD_ARCH )
MESSAGE( FATAL_ERROR "CMAKE_BUILD_ARCH was not set" )
ENDIF( NOT CMAKE_BUILD_ARCH )
SET( BUILD_DIR "${CMAKE_SOURCE_DIR}/BUILD/${CMAKE_SYSTEM}-${CMAKE_BUILD_ARCH}/${CMAKE_BUILD_TYPE}" )
SET( PROJECT_BINARY_DIR "${BUILD_DIR}" )
SET( EXECUTABLE_OUTPUT_PATH "${BUILD_DIR}/bin" )
# SET( CMAKE_BINARY_DIR "${BUILD_DIR}/obj" )
SET( LIBRARY_OUTPUT_PATH "${BUILD_DIR}/lib" )
SET( CMAKE_LIBRARY_PATH ${LIBRARY_OUTPUT_PATH} ${CMAKE_LIBRARY_PATH} )
FILE( MAKE_DIRECTORY "${BUILD_DIR}" )
FILE( MAKE_DIRECTORY "${PROJECT_BINARY_DIR}" )
FILE( MAKE_DIRECTORY "${EXECUTABLE_OUTPUT_PATH}" )
FILE( MAKE_DIRECTORY "${LIBRARY_OUTPUT_PATH}" )
#
# Macro used to easily add linker flags to a target
#
MACRO( TARGET_LINK_FLAGS TARGET FLAGS )
GET_TARGET_PROPERTY( OLD_FLAGS ${TARGET} LINK_FLAGS )
SET_TARGET_PROPERTIES(
${TARGET}
PROPERTIES
LINK_FLAGS "${FLAGS} ${OLD_FLAGS}"
)
ENDMACRO( TARGET_LINK_FLAGS )
More information about the CMake
mailing list