[CMake] How to create "staging" rules

Paul Smith paul at mad-scientist.net
Wed Jul 31 14:29:27 EDT 2013


On Thu, 2013-07-25 at 10:50 +0100, Matthew Bentham wrote:
> Thanks Paul, I often need to do this and it's interesting to see that
> you've come up with something similar.
> 
> When you have several of these in the same project, do you ever have
> trouble with more than one of them trying to create the staging
> directory at the same time in parallel builds?
> 
> Do you use this on Windows with Visual Studio?  My version seems to
> have a problem if the staged target has a .exe extension, and I was
> wondering if you'd seen that too?

Hi Matthew.  Sorry for the delay in replying.

Yes, my environment runs on Linux, MacOSX, and Windows.  We use Xcode on
MacOSX and VisualStudio on Windows.  I will say that I personally do not
use VisualStudio as an IDE: I only use it for building from the command
line; it definitely works that way.  Other developers do sometimes use
it as an IDE.

I never had any problems with multiple rules creating the same directory
at the same time; I've run this on Linux with make with very high -j
values.  I would hope that CMake would be smart enough to not fail if a
directory creation fails with an "already exists" error, similar to the
UNIX "mkdir -p" functionality.  I've not seen anything to indicate that
this is not the case.

I didn't have any particular issue with .exe files that I recall.


However, in order to make it work properly I needed a bug fixed in
CMake, for MacOSX support.  I'm currently using CMake version 2.8.11.2 I
built myself, checked out from Git, plus these two commits cherry-picked
from the future release: '42bb42d1' and '4bb6e248'.  The second one
(4bb6e248) is the one needed to make this work properly.

Here's what my current utilities look like.  I put this into a
"Stage.cmake" file in my workspace and include it from my main
CMakeLists.txt file.

It's gross, but easy to use; you just write "stagebin(myprog)" or
"stageshlib(mylib)" or whatever in your individual CMakeLists.txt files.
Files (header files, etc.) that are not targets can be staged with
"stagefiles(targ ...)" where "targ" is just some unique name for this
set of files to be staged.

Please be aware that my CMake fu is limited... for example I don't think
I've really grokked the distinction between function and macro, or when
you should or should not quote arguments and what the difference is,
etc.  So it's likely the stuff below could be cleaned up in many ways.



# Stage.cmake
# ---------------------------------------------------------------------
# Stage a derived (built) file to a directory
# usage: stageobj(<target> <dir> [{<ext>|<dir>:<ext>} ...])
function(stageobj target dir)
    get_property(targetpath TARGET ${target} PROPERTY LOCATION)
    get_filename_component(targetname ${targetpath} NAME)
    add_custom_command(OUTPUT "${CMAKE_INSTALL_PREFIX}/${dir}/${targetname}"
        COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_INSTALL_PREFIX}/${dir}"
        COMMAND "${CMAKE_COMMAND}" -E copy "${targetpath}" "${CMAKE_INSTALL_PREFIX}/${dir}"
        DEPENDS ${target}
        COMMENT "Staging ${target} to ${CMAKE_INSTALL_PREFIX}/${dir}"
        VERBATIM)
    set(prereqs "${CMAKE_INSTALL_PREFIX}/${dir}/${targetname}")

    # If there are more arguments, we need to install more files.  This is
    # needed for @#$%^&! Windows, which builds all kinds of side-effect files
    # that need to be staged, like .PDB for binaries and shared libraries, and
    # .LIB files as well as .DLLs for shared libraries.
    get_filename_component(targetdir ${targetpath} PATH)
    get_filename_component(targetbase ${targetpath} NAME_WE)
    foreach(arg ${ARGN})
        if(arg MATCHES "([^:]+):(.+)")
            set(argdir ${CMAKE_MATCH_1})
            set(argname "${targetbase}${CMAKE_MATCH_2}")
            set(mkdir COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_INSTALL_PREFIX}/${CMAKE_MATCH_1}")
        else()
            set(argdir ${dir})
            set(argname "${targetbase}${arg}")
            set(mkdir)
        endif()
        add_custom_command(OUTPUT "${CMAKE_INSTALL_PREFIX}/${argdir}/${argname}"
            ${mkdir}
            COMMAND "${CMAKE_COMMAND}" -E copy "${targetdir}/${argname}" "${CMAKE_INSTALL_PREFIX}/${argdir}"
            DEPENDS ${target}
            COMMENT "Staging ${argname} to ${CMAKE_INSTALL_PREFIX}/${argdir}"
            VERBATIM)
        set(prereqs ${prereqs} "${CMAKE_INSTALL_PREFIX}/${argdir}/${argname}")
    endforeach()

    # Hook all the staged files as prerequisites to the ALL target.
    add_custom_target("stage_${target}" ALL DEPENDS ${prereqs})
endfunction()

# Shortcut to stage a program into the bin directory.
# usage: stagebin(<target>)
macro(stagebin target)
    if(WIN32)
        stageobj(${target} bin .pdb)
    else()
        stageobj(${target} bin)
    endif()
endmacro()

# Shortcut to stage a shared library.
# usage: stageshlib(<target>)
macro(stageshlib target)
    if(WIN32)
        stageobj(${target} bin .pdb lib:.lib)
    else()
        stageobj(${target} lib64)
    endif()
endmacro()

# Shortcut to stage a static library.
# usage: stagelib(<target>)
macro(stagelib target)
    if(WIN32)
        stageobj(${target} lib)
    else()
        stageobj(${target} lib64)
    endif()
endmacro()

# Stage one or more SOURCE files into a directory.
# This will not work with derived files.  See above for those macros.
# The <name> must be unique but does not have to be a target.
# usage: stagefiles(<name> <dir> <file> [...])
function(stagefiles name dest)
    set(prereqs)
    foreach(file ${ARGN})
        get_filename_component(filenm ${file} NAME)
        set(prereqs ${prereqs} "${CMAKE_INSTALL_PREFIX}/${dest}/${filenm}")
        add_custom_command(OUTPUT "${CMAKE_INSTALL_PREFIX}/${dest}/${filenm}"
            COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_INSTALL_PREFIX}/${dest}"
            COMMAND "${CMAKE_COMMAND}" -E copy "${file}" "${CMAKE_INSTALL_PREFIX}/${dest}"
            MAIN_DEPENDENCY ${file}
            COMMENT "Staging ${file} to ${CMAKE_INSTALL_PREFIX}/${dest}"
            VERBATIM)
    endforeach()
    add_custom_target("stage_${name}" ALL DEPENDS ${prereqs})
endfunction()




More information about the CMake mailing list