Contents

# ------------------------------------------------------------------------------
# MADlib CMake Build Script
# ------------------------------------------------------------------------------

# -- Paths and MD5 hashes of third-party downloadable source code (third-party
#    components needed only by specific ports are downloaded there) ------------

# For in-house testing, we might want to change the base URLs of code-hosting
# sites to something local
# "-DSOURCEFORGE_BASE_URL=http://test.local/projects"
set(SOURCEFORGE_BASE_URL "http://sourceforge.net/projects" CACHE STRING
    "Base URL for Sourceforge projects. May be overridden for testing purposes.")
set(BITBUCKET_BASE_URL "https://bitbucket.org" CACHE STRING
    "Base URL for Bitbucket projects. May be overridden for testing purposes.")

# Boost might not be present on the system (or simply too old). In this case, we
# download the following version (unless it is already present in
# ${CMAKE_CURRENT_BINARY_DIR}/third_party/downloads).
# It is also possible to specify an alternative path to the Boost tarball when
# running cmake:
# "-DBOOST_TAR_SOURCE=/path/to/boost_x_x_x.tar.gz"

set(BOOST_TAR_VERSION "1.46.1")
set(BOOST_TAR_MD5 341e5d993b19d099bf1a548495ea91ec)

string(REPLACE "." "_" _BOOST_TAR_VERSION_UNDERSCORES ${BOOST_TAR_VERSION})
set(BOOST_TAR "boost_${_BOOST_TAR_VERSION_UNDERSCORES}.tar.gz")
set(BOOST_URL "${SOURCEFORGE_BASE_URL}/boost/files/${BOOST_TAR}")

if(NOT BOOST_TAR_SOURCE)
    find_file(BOOST_TAR_SOURCE ${BOOST_TAR}
        PATHS ${MAD_THIRD_PARTY}/downloads)
endif(NOT BOOST_TAR_SOURCE)

if(NOT BOOST_TAR_SOURCE)
    set(BOOST_TAR_SOURCE ${BOOST_URL})
endif (NOT BOOST_TAR_SOURCE)

# We always download Eigen (unless it is already present in
# ${CMAKE_CURRENT_BINARY_DIR}/third_party/downloads). It is also possible to
# specify an alternative path to the Eigen tarball:
# -DEIGEN_TAR_SOURCE=/path/to/eigen-x.x.x.tar.gz

set(EIGEN_VERSION "3.0.3")
set(EIGEN_TAR_MD5 695f24be85c4fe957ce6a4dd11161f48)

set(EIGEN_TAR "eigen-${EIGEN_VERSION}.tar.gz")
set(EIGEN_URL "${BITBUCKET_BASE_URL}/eigen/eigen/get/${EIGEN_VERSION}.tar.gz")
set(EIGEN_SVN "${BITBUCKET_BASE_URL}/eigen/eigen/tags/${EIGEN_VERSION}")

if(NOT EIGEN_TAR_SOURCE)
    find_file(EIGEN_TAR_SOURCE ${EIGEN_TAR}
        PATHS ${MAD_THIRD_PARTY}/downloads)
endif(NOT EIGEN_TAR_SOURCE)

if(NOT EIGEN_TAR_SOURCE)
    set(EIGEN_TAR_SOURCE ${EIGEN_URL})
endif (NOT EIGEN_TAR_SOURCE)

# -- Paths for madpack third-party components (those that are used by multiple
#    ports) -------------------------------------------------------------------- 

# For in-house testing, we might want to change the base URL to something local
# "-DPYPI_BASE_URL=http://test.local/projects"
set(PYPI_BASE_URL "http://pypi.python.org/packages/source" CACHE STRING
    "Base URL for projects from the Python Package Index. May be overridden for testing purposes.")

set(PYGRESQL_TAR_VERSION "4.0")
set(PYGRESQL_TAR_MD5 1aca50e59ff4cc56abe9452a9a49c5ff)

set(PYGRESQL_TAR "PyGreSQL-${PYGRESQL_TAR_VERSION}.tar.gz")
set(PYGRESQL_URL "${PYPI_BASE_URL}/P/PyGreSQL/${PYGRESQL_TAR}")

if(NOT PYGRESQL_TAR_SOURCE)
    find_file(PYGRESQL_TAR_SOURCE ${PYGRESQL_TAR}
        PATHS ${MAD_THIRD_PARTY}/downloads)
endif(NOT PYGRESQL_TAR_SOURCE)

if(NOT PYGRESQL_TAR_SOURCE)
    set(PYGRESQL_TAR_SOURCE ${PYGRESQL_URL})
endif (NOT PYGRESQL_TAR_SOURCE)


# -- Local definitions (filenames, paths, etc.) --------------------------------

set(MAD_PATCH_DIR ${CMAKE_CURRENT_BINARY_DIR}/patch)

set(MAD_MODULE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/modules)


# ==============================================================================
# From here on, modifications should rarely be necessary.
# In other words: Be careful when you make changes. You have been warned. Don't
# try this at home.
# ==============================================================================



# -- Third-party dependencies: Find or download Boost --------------------------

find_package(Boost 1.46)

# We use BOOST_ASSERT_MSG, which only exists in Boost 1.46 and later.
# Unfortunately, the FindBoost module seems to be broken with respect to version
# checking, so we will set Boost_FOUND to FALSE if the version is too old.
if(Boost_FOUND)
    if(Boost_VERSION LESS 104600)
        set(Boost_FOUND FALSE)
    endif(Boost_VERSION LESS 104600 )
endif(Boost_FOUND)

if(Boost_FOUND)
    include_directories(${Boost_INCLUDE_DIRS})
else(Boost_FOUND)
    message(STATUS "No sufficiently recent version (>= 1.46) of Boost was found. Will download.")

    ExternalProject_Add(EP_boost
        PREFIX ${MAD_THIRD_PARTY}
        DOWNLOAD_DIR ${MAD_THIRD_PARTY}/downloads
        URL ${BOOST_TAR_SOURCE}
        URL_MD5 ${BOOST_TAR_MD5}
        CMAKE_COMMAND /usr/bin/env echo Ignored: cmake
        BUILD_COMMAND /usr/bin/env echo Ignored: make
        INSTALL_COMMAND /usr/bin/env echo Ignored: make
        BINARY_DIR ${MAD_THIRD_PARTY}/src/EP_boost
    )
    include_directories(BEFORE ${MAD_THIRD_PARTY}/src/EP_boost)
endif(Boost_FOUND)


# -- Third-party dependencies: Download the C++ linear-algebra library Eigen ---

string(REGEX MATCH "^[a-zA-Z]+" _EIGEN_PROTOCOL ${EIGEN_TAR_SOURCE})
if(_EIGEN_PROTOCOL STREQUAL "https")
    # FIXME: This is somewhat ugly
    # Unfortunately, CMake does not come with SSL support for file downloads.
    # See: http://www.cmake.org/pipermail/cmake/2009-November/thread.html#33589
    # Bitbucket on the other hand only provides SSL downloads
    # We therefore assume that a "https" protocol refers to a SVN repository
    set(_EIGEN_SOURCE
        SVN_REPOSITORY ${EIGEN_SVN} UPDATE_COMMAND /usr/bin/env echo Ignored: svn update
    )
else(_EIGEN_PROTOCOL STREQUAL "https")
    set(_EIGEN_SOURCE
        URL ${EIGEN_TAR_SOURCE} URL_MD5 ${EIGEN_TAR_MD5}
    )
endif(_EIGEN_PROTOCOL STREQUAL "https")

# FIXME: Eigen is a third-party source that is patched in-place. Other
# third-party headers are patched in the patch directory.
ExternalProject_Add(EP_eigen
    PREFIX ${MAD_THIRD_PARTY}
    DOWNLOAD_DIR ${MAD_THIRD_PARTY}/downloads
    ${_EIGEN_SOURCE}
    PATCH_COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/patch/Eigen.sh"
    CMAKE_COMMAND /usr/bin/env echo Ignored: cmake
    BUILD_COMMAND /usr/bin/env echo Ignored: make
    INSTALL_COMMAND /usr/bin/env echo Ignored: make
    BINARY_DIR ${MAD_THIRD_PARTY}/src/EP_eigen
)

include_directories(${MAD_THIRD_PARTY}/src/EP_eigen)


# -- Macros to be used by ports ------------------------------------------------

# Get the architectures in a Mac OS X binary
macro(osx_archs FILENAME OUT_ARCHS)
    execute_process(
        COMMAND /usr/bin/lipo -info ${FILENAME}
        OUTPUT_VARIABLE _LIPO_OUTPUT)
    string(REPLACE "\n" "" _LIPO_OUTPUT ${_LIPO_OUTPUT})
    string(REGEX REPLACE ".*:[ ]*([^ ].*[^ ])[ ]*\$" "\\1" ${OUT_ARCHS} "${_LIPO_OUTPUT}")
    string(REPLACE " " ";" ${OUT_ARCHS} ${${OUT_ARCHS}})
endmacro(osx_archs)

# Add Python files to be preprocessed with m4
macro(add_python_files OUT_PYTHON_TARGET_FILES IN_SOURCE_DIR IN_TARGET_DIR)
    set(IN_M4_ARGUMENTS ${ARGN})
        
    get_filename_component(SOURCE_DIR_ABS "${IN_SOURCE_DIR}" ABSOLUTE)
    get_filename_component(TARGET_DIR_ABS "${IN_TARGET_DIR}" ABSOLUTE)
    file(GLOB_RECURSE PYTHON_FILES
        RELATIVE "${SOURCE_DIR_ABS}"
        "${SOURCE_DIR_ABS}/*.py_in"
    )
    set(MADLIB_PYTHON_M4_PREPROCESSING
        COMMAND ${CMAKE_COMMAND} -E make_directory "\"\${OUTDIR}\""
        COMMAND ${M4_BINARY} ${M4_ARGUMENTS} ${IN_M4_ARGUMENTS}
            "\"\${CURRENT_PATH}\"" > "\"\${OUTFILE}\""
    )
    batch_add_command(
        TARGET_PREFIX "${TARGET_DIR_ABS}/"
        SOURCE_PREFIX "${SOURCE_DIR_ABS}/"
        TARGET_SUFFIX ".py"
        SOURCE_SUFFIX "[.]py_in"
        RUN "${MADLIB_PYTHON_M4_PREPROCESSING}"
        COMMENT "Preprocessing \${CURRENT_FILE} with m4."
        TARGET_FILE_LIST_REF ${OUT_PYTHON_TARGET_FILES}
        SOURCE_FILE_LIST ${PYTHON_FILES}
    )
endmacro(add_python_files)

# Add sql files to be copied
macro(add_sql_files OUT_SQL_TARGET_FILES IN_SOURCE_DIR IN_TARGET_DIR)
    get_filename_component(SOURCE_DIR_ABS "${IN_SOURCE_DIR}" ABSOLUTE)
    get_filename_component(TARGET_DIR_ABS "${IN_TARGET_DIR}" ABSOLUTE)
    file(GLOB_RECURSE SQL_FILES
        RELATIVE "${SOURCE_DIR_ABS}"
        "${SOURCE_DIR_ABS}/*.sql_in"
    )
    # psql of PostgreSQL < 9 does not like byte-order marks
    set(_MADLIB_VERIFY_AND_COPY_COMMAND
        COMMAND "${CMAKE_SOURCE_DIR}/cmake/TestIfNoUTF8BOM.py" "\"\${CURRENT_PATH}\""
        COMMAND "${CMAKE_COMMAND}" -E copy "\"\${CURRENT_PATH}\"" "\"\${OUTFILE}\""
    )
    batch_add_command(
        TARGET_PREFIX "${TARGET_DIR_ABS}/"
        SOURCE_PREFIX "${SOURCE_DIR_ABS}/"
        TARGET_SUFFIX ""
        SOURCE_SUFFIX ""
        RUN "${_MADLIB_VERIFY_AND_COPY_COMMAND}"
        COMMENT "Validating and copying \${CURRENT_FILE}."
        TARGET_FILE_LIST_REF ${OUT_SQL_TARGET_FILES}
        SOURCE_FILE_LIST ${SQL_FILES}
    )
endmacro(add_sql_files)

# Add a connector library for a specific DBMS port
macro(add_madlib_connector_library IN_TARGET_NAME IN_LIB_DIR IN_LIB_LOADER)
    set(IN_LIBRARY_SOURCES ${ARGN})

    add_library(
        ${IN_TARGET_NAME}
        MODULE
        ${IN_LIBRARY_SOURCES}
    )
    add_dependencies(${IN_TARGET_NAME} EP_eigen)
    set_target_properties(${IN_TARGET_NAME} PROPERTIES
        LIBRARY_OUTPUT_DIRECTORY "${IN_LIB_DIR}"
        OUTPUT_NAME "madlib"
        BUILD_WITH_INSTALL_RPATH YES
    )
    
    if(APPLE)
        set_target_properties(${IN_TARGET_NAME} PROPERTIES
            LINK_FLAGS "-bundle_loader \"${IN_LIB_LOADER}\"")
    endif(APPLE)
endmacro(add_madlib_connector_library)


# -- Speciy files that will be compiled into MADlib core library ---------------

# FIXME: The CMake description of file(GLOB ...) says:
# "We do not recommend using GLOB to collect a list of source files from your
# source tree. If no CMakeLists.txt file changes when a source is added or
# removed then the generated build system cannot know when to ask CMake to
# regenerate."
# We still use GLOB here because we think the benefits outweigh the mentioned
# disadvantage.

file(GLOB_RECURSE MAD_CPP_SOURCES
    dbal/*.hpp
    modules/*.cpp modules/*.hpp
    utils/*.hpp
)
list(APPEND MAD_SOURCES
    ${MAD_CPP_SOURCES}
)

include_directories(${CMAKE_CURRENT_SOURCE_DIR})


if(CMAKE_COMPILER_IS_GNUCXX)
    if(GNUCXX_VERSION VERSION_GREATER 4.4 OR GNUCXX_VERSION VERSION_EQUAL 4.4)
        if(_AUTOINCLUDE_LIBSTDCXX)
            # FIXME: The following only takes care of the symbolic link
            # Need to implement copying of files containing version numbers in
            # file name
            
            # Both CentOS/RH 5 ship with old versions of libstdc++
            # Starging with gcc 4.4, C++ code may therefore not run any more on
            # vanilla installations of CentOS/RH 5. We therefore include libstdc++
            # in this case and install it in $MADLIB_ROOT/lib
            set(_LIBSTDCXX_FILENAME
                "${CMAKE_SHARED_LIBRARY_PREFIX}stdc++${CMAKE_SHARED_LIBRARY_SUFFIX}")
            execute_process(
                COMMAND ${CMAKE_C_COMPILER}
                    "-print-file-name=${_LIBSTDCXX_FILENAME}"
                OUTPUT_VARIABLE _LIBSTDCXX_PATH
                OUTPUT_STRIP_TRAILING_WHITESPACE)
            add_custom_command(
                OUTPUT
                    "${CMAKE_CURRENT_BINARY_DIR}/lib/${_LIBSTDCXX_FILENAME}"
                COMMAND "${CMAKE_COMMAND}" -E copy ${_LIBSTDCXX_PATH}
                    "${CMAKE_CURRENT_BINARY_DIR}/lib/${_LIBSTDCXX_FILENAME}"
                COMMENT "Copying libstdc++ to lib directory."
                )
            add_custom_target(copyLibStdCXX ALL DEPENDS
                "${CMAKE_CURRENT_BINARY_DIR}/lib/${_LIBSTDCXX_FILENAME}")
        endif(_AUTOINCLUDE_LIBSTDCXX)
        
        # Also install it
        install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib
            DESTINATION .
            COMPONENT core
            FILES_MATCHING REGEX "^.*/[^/]*stdc\\+\\+[^/]*\$"
        )
    endif(GNUCXX_VERSION VERSION_GREATER 4.4 OR GNUCXX_VERSION VERSION_EQUAL 4.4)
endif(CMAKE_COMPILER_IS_GNUCXX)


# -- Preprocess/copy all Python/SQL files --------------------------------------

add_python_files(
    PYTHON_TARGET_FILES
    "modules"
    "${CMAKE_CURRENT_BINARY_DIR}/modules"
)
add_custom_target(pythonFiles ALL DEPENDS ${PYTHON_TARGET_FILES})

add_sql_files(
    SQL_TARGET_FILES
    "modules"
    "${CMAKE_CURRENT_BINARY_DIR}/modules"
)
add_custom_target(sqlFiles ALL DEPENDS ${SQL_TARGET_FILES})

# -- Use all necessary patches directory ---------------------------------------

include_directories(BEFORE "${MAD_PATCH_DIR}")


# -- Add subdirectories --------------------------------------------------------

add_subdirectory(patch)
add_subdirectory(bin)
add_subdirectory(config)
add_subdirectory(madpack)
add_subdirectory(ports)