Skip to content
Snippets Groups Projects
FindBob.cmake 8.16 KiB
# author:  Manuel Gunther <siebenkopf@googlemail.com>
# date:    Wed May 25 10:55:42 MDT 2016
# license: BSD-3, see LICENSE file
#
# This file can be used to find Bob packages that provide a C++ interface.
# To find Bob libraries, use the CMake find_package command, and list all Bob packages as COMPONENTS, e.g.:
#
# - find_package(Bob COMPONENTS bob.io.base bob.learn.em REQUIRED)
#
# This will search recursively for all Bob packages (and its required Bob packages), which need to be installed beforehand, in this order:
#
# - installed (e.g. by using mr.developer) in the src directory
# - installed (e.g. by buildout) in the eggs directory
# - installed (e.g. via virtualenv) in the BOB_PREFIX_PATH (if given, either as an environment variable or as a CMake variable)
# - installed (e.g. via pip) in the system path
#
# After the successful run of find_package, these CMake variables will be filled:
#
# - Bob_INCLUDE_DIRS: The include directories of all required Bob packages, and their external requirements.
#   Use e.g. include_directories(${Bob_INCLUDE_DIRS}) to add them
#
# - Bob_LIBRARY_DIRS: The library directories of all required Bob packages.
#   Use e.g. link_directories(${Bob_LIBRARY_DIRS}) to add them
#
# - Bob_LIBRARIES: The C++ libraries of all required Bob packages.
#   Use e.g. link_libraries(${Bob_LIBRARIES}) to add them
#
# - Bob_DEFINITIONS: The preprocessor defines required to build your package (already including the "-D").
#   Use e.g. add_definitions(${Bob_DEFINITIONS}) to add them


# converts the given package name (including the dots) to a directory and to a variable usable by CMake
macro(_convert_package_to_string package)
  string(REPLACE "." "_" PACKAGE_LIB "${package}")
  string(REPLACE "." "/" PACKAGE_DIR "${package}")
  string(TOUPPER "${PACKAGE_LIB}" PACKAGE)
endmacro()

# finds the python executable, which might be in ./bin, BOB_PREFIX_PATH/bin or in the system path
macro(_find_python)
  if (NOT _python)
    set(_possible_paths ${BOB_SOURCE_DIR}/bin)
    if (BOB_PREFIX_PATH)
      list(APPEND _possible_paths ${BOB_PREFIX_PATH}/bin)
    endif()
    find_program(_python python HINTS ${_possible_paths})
  endif()
endmacro()


# gets the list of external includes required by the Bob package
macro(_get_external_includes package)
  _find_python()
  execute_process(COMMAND ${_python} -c "exec(\"import ${package},sys\\nif hasattr(${package}, 'get_include_directories'): sys.stdout.write(';'.join(${package}.get_include_directories()))\")" OUTPUT_VARIABLE _external_includes)
endmacro()

macro(_get_external_macros package)
  # gets the list of macros required by the Bob package
  _find_python()
  execute_process(COMMAND ${_python} -c "exec(\"import ${package},sys\\nif hasattr(${package}, 'get_macros'): sys.stdout.write(';'.join('-D%s=%s' % m for m in ${package}.get_macros()))\")" OUTPUT_VARIABLE _external_macros)
endmacro()


# recurses through the requirements and get all bob packages
macro(_recurse_requirements requirements)
  file(STRINGS ${requirements} _reqs)
  foreach (_req IN LISTS _reqs)
    # check if string starts with bob
    string(FIND ${_req} bob _pos)
    if (NOT ${_pos} EQUAL -1)
      # find bob package recursively (will not run if pacakge was already found)
      find_bob_package(${_req})
    endif()
  endforeach()
endmacro()


# Function to find a specific Bob package;
# This function will search for Bob packages in several directories
# If found, it will add include directories -- and library directories and libraries if applicable
function(find_bob_package package)

  # get the uppercase package name in ${PACKAGE}
  # the package directory in ${PACKAGE_DIR}
  # and the possible lib name in ${PACKAGE_LIB}
  _convert_package_to_string(${package})

  if (${PACKAGE}_FOUND)
    return()
  endif()

  message(STATUS "Searching for Bob package '${package}'")

  # define possible path
  set(_possible_paths
    ${BOB_SOURCE_DIR}/src/${package}
  )
  # .. egg directory
  file(GLOB _egg_dir ${BOB_SOURCE_DIR}/eggs/${package}*.egg)
  if (_egg_dir)
    list(APPEND _possible_paths ${_egg_dir})
  endif()
  # .. BOB_PREFIX_PATH
  if (BOB_PREFIX_PATH)
    list(APPEND _possible_paths ${BOB_PREFIX_PATH}/lib/*/site-packages)
  endif()

  # add common path to locations
  set(_bob_paths)
  foreach(_path IN LISTS _possible_paths)
    list(APPEND _bob_paths ${_path}/${PACKAGE_DIR})
  endforeach()

  # check if we find any bob directory
  find_path(_package_dir
    NAMES __init__.py
    HINTS ${_bob_paths}
  )

  # assert that we have found the package
  if (NOT _package_dir)
    if (Bob_FIND_REQUIRED)
      # if the package was required and we didn't find it, stop here
      message(FATAL_ERROR "Could not find bob package '${package}'")
    endif()
    # otherwise, set the package to NOTFOUND and continue
    set(${PACKAGE} ${PACKAGE}-NOTFOUND PARENT_SCOPE)
    return()
  endif()

  set(${PACKAGE}_FOUND TRUE CACHE BOOL "The package ${package} has been found." FORCE)

  # first, parse the requirements (requires.txt) from the requires.txt inside ${PACKAGE_BASE_DIR}/${package}*.egg-info or ${PACKAGE_BASE_DIR}/EGG-INFO
  # get the base directory of the currently found package
  string(FIND ${_package_dir} /${PACKAGE_DIR} _pos)
  string(SUBSTRING ${_package_dir} 0 ${_pos} _base_dir)
  # get the egg info directory (either in package-version.egg-info or in EGG-INFO)
  file(GLOB _egg_info_dir ${_base_dir}/${package}*.egg-info ${_base_dir}/EGG-INFO)
  find_file(
    _requirements
    NAMES requires.txt
    HINTS ${_egg_info_dir}
    NO_CMAKE_PATH
  )
  # parse the requirements and add packages recursively
  if (_requirements)
    set(_r ${_requirements})
    unset(_requirements CACHE)
    set(_p ${_package_dir})
    unset(_package_dir CACHE)
    _recurse_requirements(${_r})
    set(_package_dir ${_p} CACHE STRING INTERNAL)
  endif()


  # find include directories
  find_path(_include_dir
    NAMES include
    HINTS ${_package_dir}
  )
  if (_include_dir)
    set(Bob_INCLUDE_DIRS ${Bob_INCLUDE_DIRS} ${_include_dir}/include CACHE STRING "The list of Bob include directories" FORCE)
  endif()

  # find external dependencies
  _get_external_includes(${package})
  if (_external_includes)
    set(Bob_INCLUDE_DIRS ${Bob_INCLUDE_DIRS} ${_external_includes} CACHE STRING "The list of Bob include directories" FORCE)
  endif()
  # find macros
  _get_external_macros(${package})
  if (_external_macros)
    set(Bob_DEFINITIONS ${Bob_DEFINITIONS} ${_external_macros} CACHE STRING "The list of Bob definitions" FORCE)
  endif()

  # find library
  find_library(_library
    NAMES ${PACKAGE_LIB}
    HINTS ${_package_dir}
  )
  if (_library)
    set(Bob_LIBRARY_DIRS ${Bob_LIBRARY_DIRS} ${_package_dir} CACHE STRING "The list of Bob library directories" FORCE)
    set(Bob_LIBRARIES ${Bob_LIBRARIES} ${PACKAGE_LIB} CACHE STRING "The list of Bob libraries" FORCE)
  endif()

  # clean up so that the next package is acually searched and not skipped
  unset(_package_dir CACHE)
  unset(_include_dir CACHE)
  unset(_library CACHE)
endfunction()


# HERE the main FIND_BOB starts

# get the list of components
if (NOT Bob_FIND_COMPONENTS)
  message(FATAL_ERROR "Please specify the bob packages that you want to search for as COMPONENTS")
endif()

# set Bob source directory, if not given yet (i.e., no FORCE option here)
set(BOB_SOURCE_DIR ${CMAKE_SOURCE_DIR} CACHE STRING "Source directory of Bob packages")

# set BOB_PREFIX_PATH as CMake variable when given in as environment variable
if (NOT "$ENV{BOB_PREFIX_PATH}" STREQUAL "")
  if (BOB_PREFIX_PATH)
    if (NOT "${BOB_PREFIX_PATH}" STREQUAL "$ENV{BOB_PREFIX_PATH}")
      message(WARNING "Ignoring the BOB_PREFIX_PATH environment variable '$ENV{BOB_PREFIX_PATH}' and using '${BOB_PREFIX_PATH}' instead")
    endif()
  else()
    set(BOB_PREFIX_PATH $ENV{BOB_PREFIX_PATH} CACHE STRING "Bob installation prefix path" FORCE)
  endif()
endif()


# iterate over all COMPONENTS and add the packages
foreach (package IN LISTS Bob_FIND_COMPONENTS)
  find_bob_package(${package})
endforeach()

# Some debug output; can be disabled if you are annoyed.
message(STATUS "Found Bob include directories '${Bob_INCLUDE_DIRS}'")
message(STATUS "Found Bob library directories '${Bob_LIBRARY_DIRS}'")
message(STATUS "Found Bob libraries '${Bob_LIBRARIES}'")
message(STATUS "Found Bob definitions '${Bob_DEFINITIONS}'")