CMake - How to check if string is a file path? - cmake

I am gathering all license files for the git submodules of my project that are inside a child directory to my project's root directory. From my experience a license file is normally called LICENSE or COPYRIGHT but ultimately I will just add a list of file names to look for.
I am facing a problem with one of the libraries, which has a different name for its license file namely COPYING and not LICENSE (which will be handled eventually with the list I have mentioned above) but what's more important is that it has a directory called RELICENSES, which has nothing to do with license files. Needless to say for this dependency my function fails with and error that it's unable to read it (permission denied) as a text file.
Here is my CMake function:
# extract_deps_licenses
# For a given directory (root directory for all dependencies) the function will look
# into ONLY the first level of each subdirectory (meaning non-recursive behaviour) and
# look for a license text file. If it finds one, its contents will be read and copyied
# into the provided target license file thus generatng a single license file for all
# dependencies
# Currently looks for a license file containing the word "LICENSE" (not case-sensitive!)
# Special cases such as a license file not being a text file (e.g. executable) will lead
# to exiting with error
#
# #param root_dir Root directory for all dependencies
# #param dest_license Path to license file that will be populated
#
# TODO For the future a third argument of type list will be provided. The list will contain
# possible names for license files incl. COPYING, COPYRIGHT etc.
function(extract_deps_licenses root_dir dest_license)
message("Retrieving git submodule status for dependencies")
execute_process(
COMMAND ${GIT_EXECUTABLE} submodule status
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
RESULT_VARIABLE deps_git_exit_code
OUTPUT_VARIABLE deps_git_status
)
file(APPEND
"${dest_license}"
"\nGit information:\n"
"\n${deps_git_status}\n"
)
# Get all directories under root_dir without recursion (subdirectories will not be traversed)
file(GLOB dep_dirs "${root_dir}/*" DIRECTORY)
foreach(dep_dir ${dep_dirs})
# Get all but the last directory
string(REGEX MATCH ".*\/" dep_parent_dirs ${dep_dir})
# Get the last directory by excluding everything else
# This leaves the dependency directory name, which will be used later
string(REGEX REPLACE ${dep_parent_dirs} "" dep_name ${dep_dir})
string(TOUPPER ${dep_name} dep_name)
message("Found dependency ${dep_name} in \"${dep_dir}\"")
# Get all items inside
file(GLOB paths "${dep_dir}/*")
message("Looking for license file (non-recursive)")
foreach(path_license ${paths})
#get_filename_component(fname ${item} NAME_WE)
string(TOUPPER "${path_license}" path_up)
# Check if full path contains LICENSE
# Will not work if LICENSE is a directory!
string(REGEX MATCH "LICENSE" match ${path_up})
# Exclude prefixes and suffixes for name of license file
# directory that has nothing to do with license file)
# Skip if path does not include LICENSE
# FIXME Check if match is a file and not directory
if((NOT match))# OR (NOT path_license FILE)) # <-------------------- CHECK IF A FILE HERE!!!
continue()
endif()
set(license_path ${path_license})
message("Found license \"${license_path}\"")
# Read license text and append to full license text
message("Copying license text to final license file at '${license_path}'")
file(READ ${license_path} license_content)
file(APPEND
"${dest_license}"
"\n${dep_name}\n\n"
"${license_content}"
"\n-----------------------------------------------------------------------------------------------------------\n"
)
endforeach()
endforeach()
I know the function is not perfect but I am looking into solving the particular problem mentioned in the question - how do I check if a string is a path to a file (not directory!). If I can do that, I can skip a lot of the processing by just dismissing an entry from my list of items found inside the directory if it's not an file (I will not handle the case where a file LICENSE is weird such as an executable).

You could call file(GLOB) with the optional LIST_DIRECTORIES false parameter:
file(GLOB paths LIST_DIRECTORIES false "${dep_dir}/*")
Or use if(IS_DIRECTORY):
if((NOT match) OR IS_DIRECTORY "${path_license}")
continue()
endif()

Related

Should I modify CMAKE_MODULE_PATH in PackageConfig.cmake

I am writing a MyPackageConfig file for my project with exported targets so that other projects can easily find MyPackage and it's dependencies. It looks like this:
include(CMakeFindDependencyMacro)
find_dependency(LIB1_WITHOUT_CMAKE_CONFIG)
find_dependency(LIB2_WITH_CMAKE_CONFIG)
include (Some/Install/Dir/MyPackageTargets.cmake)
I am wondering if it is smart to add the following lines to the MyPackageConfig.cmake before the find_dependency calls
# Find target dependencies
# Allows packages linking with MyPackage to use the find modules that
# MyPackage used to find it's dependencies. Since this path is appended to
# the existing module path, the calling package's module path will take
# precedence
list(APPEND CMAKE_MODULE_PATH #CMAKE_INSTALL_PREFIX#/lib/cmake/MyPackage/modules)
# Allows packages linking with MyPackage to find MyPacakge's dependencies if
# they don't already have them. Since this path (or these paths) are
# appended to the existing prefix path, the calling package's prefix
# path will take precedence
list(APPEND CMAKE_PREFIX_PATH #CMAKE_PREFIX_PATH#)
find_dependency(LIB1_WITHOUT_CMAKE_CONFIG)
find_dependency(LIB2_WITH_CMAKE_CONFIG)
Good idea? No?
Longer explanation of my rationale:
How does YourPackage that uses MyPackage find LIB1?
(i). You could write your own FindLIB1.cmake but that's duplication of effort
(ii). I could install my FindLIB1.cmake alongside my MyPackageConfig.cmake in a Modules dir. But you will have to include this path in your module path.
My suggestion: Add a line before find_dependency(LIB1_WITHOUT_CMAKE_CONFIG) modifying the module path like so:
list(APPEND CMAKE_MODULE_PATH #CMAKE_INSTALL_PREFIX#/lib/cmake/mstk/modules)
This will ensure that if you have a FindLIB1.cmake, it will be used but if you don't mine will be found and used.
How do you know where the LIB1 and LIB2 reside (including LIB2's Config file)?
By adding the line
list(APPEND CMAKE_PREFIX_PATH #CMAKE_PREFIX_PATH#)
I tell your package where I searched and found my dependencies (but only if you didn't already have them in the CMAKE_PREFIX_PATH you specified)
Yes, you may change variables like CMAKE_MODULE_PATH or CMAKE_PREFIX_PATH for the purpose of your config script.
Any "good" project should be prepared to prepending/appending CMAKE_PREFIX_PATH, because this variable could normally be set by a user (when call cmake). As for CMAKE_MODULE_PATH, module's names in different directories are rarely conflicted.
Some hints:
You may restore the variables at the end of your script. Such way you won't affect the calling code when changing the variables:
# Store old value of the variable
set(old_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH})
# Change variable, use it, ...
# ...
# Restore the variable at the end
set(CMAKE_MODULE_PATH ${old_CMAKE_MODULE_PATH})
Note, however, that find_dependency returns from the script if an (inner) package not found, so restoring the variable won't trigger in that case. But usually find_package() is called with REQUIRED keyword, so failing to find the inner package would be fatal.
Instead of changing CMAKE_PREFIX_PATH you may set other variables which affect only specific find script and doesn't affect others. E.g many find scripts use XXX_ROOT variables for hint about location.
For find config script itself, you may use PATHS or HINTS options of find_package():
# Was:
#- list(APPEND CMAKE_PREFIX_PATH #CMAKE_PREFIX_PATH#)
#- find_dependency(LIB2_WITH_CMAKE_CONFIG)
# Replace with:
find_dependency(LIB2_WITH_CMAKE_CONFIG
PATHS #CMAKE_PREFIX_PATH# # Where to find config script
NO_DEFAULT_PATH # Do not search other places
)

How do I reset a file created by cmake each time cmake reconfigures/regenerates my Makefiles?

I have a cmake function I use to create targets with proper dependencies as defined by my projects debian/control files. This works beautifully.
Now, I want to create a DOT file to have a graphical representation of my dependencies. For that I create a "deps.dot" file as follow:
function(CreateTargets COMPONENT)
[...]
if(NOT EXISTS "${CMAKE_BINARY_DIR}/deps.dot")
file(WRITE
"${CMAKE_BINARY_DIR}/deps.dot"
"digraph dependencies {\n")
endif()
foreach( DEP ${DEPENDS_LIST} )
file(APPEND
"${CMAKE_BINARY_DIR}/deps.dot"
"\"${ARG_PROJECT_NAME}\" [shape=box];\n...
...\"${ARG_PROJECT_NAME}\" -> \"${DEP}\";\n")
endforeach()
[...]
endfunction()
The very first time I configure, it works as expected. It creates the file with digraph dependencies { and then adds said dependencies in the foreach() loop.
The second component to be added will not trigger the file(WRITE ... since the file already exists.
Now, when I make changes to my CMakeLists.txt or module files, it re-runs that function and... unfortunately it reapplies the file(APPEND ... without first doing the file(WRITE .... The result is a completely invalid DOT file.
How do I know I have to rerun the file(WRITE ...?
My current solution is to rename the file at the end, when I generate the SVG file:
file(APPEND "${CMAKE_BINARY_DIR}/deps.dot" "}\n")
# We change the name of the deps.dot file so next time the
# configuration runs it generates a brand new file
#
file(RENAME
"${CMAKE_BINARY_DIR}/deps.dot"
"${CMAKE_BINARY_DIR}/dependencies.dot")
execute_process(
COMMAND dot -Tsvg ${CMAKE_BINARY_DIR}/dependencies.dot
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
OUTPUT_FILE "${CMAKE_BINARY_DIR}/dependencies.svg")
This works assuming the configuration works properly. If an error occurs, the deps.dot file lingers and I get that error from DOT saying the file is not a valid graph.

CMake: install(FILES ...) for a file containing a list of files

Consider I have a simple text file containing a list of (absolute) file paths.
Is there any easy way to make CMake install those files (as if install(FILES ...) was used)?
Directory structure (minus some constant leading path components) should be maintained.
Right now the only option I could come up with was to use
install(CODE "execute_process(COMMAND my_script.sh)")
and do it using normal shell commands, but that seems to defeat the purpose of using a build system in the first place...
I believe this would do the trick:
# 'filename' is the file that contains a list ';' separated paths relative to that input file
function(install_my_files filename)
file(READ ${filename} relative_paths)
get_filename_component(parent_directory ${filename} DIRECTORY) # parent directory of input file
foreach(relative_path ${relative_paths})
get_filename_component(relative_directory ${relative_path} DIRECTORY)
install(FILES "${parent_directory}/${relative_path}" DESTINATION ${relative_directory})
endforeach()
endfunction()

Issue when using CMake to install Qpid broker

I'm currently going through the process of installing the Qpid broker on Windows. I've already installed CMake, boost, and python and I've added the environment variable for the root of the Boost directory. I'm now at the step where Qpid client is to be build from a source distribution (Visual Studio 12). After having changed into the directory qpid\cpp I ran the command: cmake -G "Visual Studio 12 2013". Toward the end of the log, I got the error:
CMake Error at CMakeLists.txt:73 (install):
install FILES given no DESTINATION!
So I checked the CMakeLists.txt file and saw that lines 73 - 76 have:
install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/README.txt
DESTINATION ${QPID_INSTALL_EXAMPLESDIR}
COMPONENT ${QPID_COMPONENT_EXAMPLES})
I think the problem could be related to the fact that EXAMPLESDIR is being used but the INSTALL_WINDOWS text file instructs you to go into the cpp directory which is located in examples (the full path is: qpid-cpp-0.34/bindings/qmf2/examples/cpp/)
This is the first time I've had to work with qpid or messaging brokers so I have a limited understanding of what's going on here. What destination should I be providing for the install files?
The CMakeLists.txt file is below for reference:
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
project(qmf2_examples)
cmake_minimum_required(VERSION 2.4.0 FATAL_ERROR)
if(COMMAND cmake_policy)
cmake_policy(SET CMP0003 NEW)
endif(COMMAND cmake_policy)
include_directories(${CMAKE_BINARY_DIR}/include)
include_directories(${CMAKE_SOURCE_DIR}/include)
# Shouldn't need this... but there are still client header inclusions
# of Boost. When building examples at an install site, the Boost files
# should be locatable aside from these settings.
# So set up to find the headers, find the libs at link time, but dynamically
# link them all and clear the CMake Boost library names to avoid adding them to
# the project files.
include_directories( ${Boost_INCLUDE_DIR} )
link_directories( ${Boost_LIBRARY_DIRS} )
# Visual Studio needs some Windows-specific simplifications.
if (MSVC)
add_definitions( /D "NOMINMAX" /D "WIN32_LEAN_AND_MEAN" /D "BOOST_ALL_DYN_LINK" )
# On Windows, prevent the accidental inclusion of Boost headers from
# autolinking in the Boost libs. There should be no direct references to
# Boost in the examples, and references via qpidclient/qpidcommon are
# resolved in the Qpid libs.
add_definitions( /D "BOOST_ALL_NO_LIB" )
endif (MSVC)
# There are numerous duplicate names within the examples. Since all target
# names must be unique, define a macro to prepend a prefix and manage the
# actual names.
# There can be an optional arguments at the end: libs to include
macro(add_example subdir example)
add_executable(${subdir}_${example} ${example}.cpp)
set_target_properties(${subdir}_${example} PROPERTIES OUTPUT_NAME ${example})
if (${ARGC} GREATER 2)
target_link_libraries(${subdir}_${example} ${ARGN} qpidmessaging qpidtypes
${_boost_libs_needed})
else (${ARGC} GREATER 2)
target_link_libraries(${subdir}_${example} qpidmessaging qpidtypes
${_boost_libs_needed})
endif (${ARGC} GREATER 2)
endmacro(add_example)
macro(add_installed_example subdir example)
add_example(${subdir} ${example} ${ARGN})
# For installs, don't install the built example; that would be pointless.
# Install the things a user needs to build the example on-site.
install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/${example}.cpp
DESTINATION ${QPID_INSTALL_EXAMPLESDIR}/${subdir}
COMPONENT ${QPID_COMPONENT_EXAMPLES})
endmacro(add_installed_example)
install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/README.txt
DESTINATION ${QPID_INSTALL_EXAMPLESDIR}
COMPONENT ${QPID_COMPONENT_EXAMPLES})
add_installed_example(qmf2 agent qmf2)
if (NOT WIN32)
# uses posix select()
add_installed_example(qmf2 event_driven_list_agents qmf2)
endif (NOT WIN32)
add_installed_example(qmf2 list_agents qmf2)
add_installed_example(qmf2 print_events qmf2)

Simple CMakeLists.txt that reflects typical directory structure (/src, /inc, /bin subdirectories)

I am struggling to make a CMakeList.txt file to reflect a simple, typical makefile. The original is here http://pastebin.com/S9Czr1pt .
I tried many things (like SET(SOURCE ... and SET(HEADERS... ) to add /src, /lib and /third-party//include ), but I have no luck.
Can anyone either help me out or point to a tutorial that does this thing?
This is just an out of the blue skeleton - please see the CMake documentation for details of each function:
cmake_minimum_required(VERSION 2.6)
# Project name, can be used as target name too
project(Example)
# Search all .cpp files within src - recursive!
# You can add all source files by hand here too
file(GLOB_RECURSE SRCS "src/*.cpp")
# Add include path (you can also add it's sub directories
include_directories("include")
# Search for packages -- PLEASE NOTE: many libraries provide alternative's to this
# which often provide more functionality
find_package(PkgConfig REQUIRED)
# TODO: add proper pkg modul search info's and change the variable's name
pkg_search_module(PACKAGE_NO1 ...)
# Show some messages (optional)
if( (PACKAGE_NO1 )
include_directories(${(PACKAGE_NO1_INCLUDE_DIRS})
message(STATUS "Using OpenSSL ${(PACKAGE_NO1_VERSION}")
else()
# do error handling
endif()
# Add compiler flags
add_definitions(-std=c++11 -Wall) # -g O3 etc are added according to Release / Debug compilation
# Build a executable target (1st param: Target name; 2nd: source files)
add_executable(${PROJECT_NAME} ${SRCS})