[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