From 462da988eef2094f41e4a2d2b5c77294fcfdecbc Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Sat, 3 Sep 2016 00:25:52 +0100 Subject: [PATCH] Fix Unix compilation using CMake. Add platform detection that's compatible with the old buildsystem and its m4 autoconfigury. Make Travis build and test using CMake as a separate target. --- .travis.yml | 7 + infrastructure/cmake/CMakeLists.txt | 269 ++++++++++++++++++++++- infrastructure/travis-build.sh | 30 ++- lib/bbackupquery/makedocumentation.pl.in | 2 +- lib/common/BoxPlatform.h | 10 +- lib/common/Test.h | 16 +- lib/win32/emu.h | 4 +- 7 files changed, 305 insertions(+), 33 deletions(-) diff --git a/.travis.yml b/.travis.yml index dbc1466c1..c10a4d2fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,13 @@ sudo: false addons: apt: + # We need cmake > 2.8.7 + # https://github.com/travis-ci/travis-ci/issues/4631#issuecomment-191153634 + sources: + - george-edison55-precise-backports # cmake 3.2.3 / doxygen 1.8.3 packages: + - cmake + - cmake-data - libdb-dev - libreadline-dev - libssl-dev @@ -23,6 +29,7 @@ addons: env: - TEST_TARGET=debug - TEST_TARGET=release + - BUILD=cmake script: - ./infrastructure/travis-build.sh diff --git a/infrastructure/cmake/CMakeLists.txt b/infrastructure/cmake/CMakeLists.txt index 0838045a5..c2c713e76 100644 --- a/infrastructure/cmake/CMakeLists.txt +++ b/infrastructure/cmake/CMakeLists.txt @@ -89,6 +89,7 @@ if(NOT status EQUAL 0) "status ${status}: ${command_output}") endif() +add_definitions(-DBOX_CMAKE -DNEED_BOX_VERSION_H) if(WIN32) add_definitions(-DWIN32) endif() @@ -324,17 +325,22 @@ endforeach() # generators such as MSVC. We need to use a generator expression instead. target_compile_definitions(lib_common PUBLIC $<$:-DBOX_RELEASE_BUILD>) -# Tell QDBM not to build itself as a DLL, because we want to link statically to it. -target_compile_definitions(qdbm PUBLIC -DQDBM_STATIC) +# Detect platform features and write BoxConfig.h.in. Reuse code from +# infrastructure/m4/boxbackup_tests.m4 where possible -# Silence some less-useful warnings -if(MSVC) - add_definitions(/wd4996 /wd4291) - # target_link_libraries(qdbm PRIVATE /IGNORE:LNK4006) - set_property(TARGET qdbm PROPERTY CMAKE_STATIC_LINKER_FLAGS /IGNORE:LNK4006) -endif(MSVC) +include(CheckIncludeFiles) +include(CheckFunctionExists) +include(CheckSymbolExists) +include(CheckLibraryExists) +include(CheckCXXSourceCompiles) -target_link_libraries(lib_common PUBLIC ws2_32 gdi32) +set(boxconfig_h_file "${CMAKE_BINARY_DIR}/BoxConfig.h.in") +file(REMOVE "${boxconfig_h_file}") +file(WRITE "${boxconfig_h_file}" "// Auto-generated by CMake. Do not edit.\n") + +if(WIN32) + target_link_libraries(lib_common PUBLIC ws2_32 gdi32) +endif() # Link to ZLib # http://stackoverflow.com/a/6174604/648162 @@ -406,6 +412,251 @@ if(READLINE_FOUND) target_link_libraries(lib_common PUBLIC ${Readline_LIBRARY}) endif() +set(boxconfig_cmake_h_dir "${base_dir}/lib/common") +# Get the values of all directories added to the INCLUDE_DIRECTORIES property +# by include_directory() statements, and save it in CMAKE_REQUIRED_INCLUDES +# which check_include_files() uses to set the include file search path: +get_property(CMAKE_REQUIRED_INCLUDES DIRECTORY PROPERTY INCLUDE_DIRECTORIES) +list(APPEND CMAKE_REQUIRED_INCLUDES "${boxconfig_cmake_h_dir}") +# message(STATUS "CMAKE_REQUIRED_INCLUDES=${CMAKE_REQUIRED_INCLUDES}") + +# Save the original BoxConfig.cmake.h so that we can move it back later, +# and not need to recompile everything. +move_file_if_exists( + "${boxconfig_cmake_h_dir}/BoxConfig.cmake.h" + "${boxconfig_cmake_h_dir}/BoxConfig.cmake.h.bak") + +foreach(m4_filename boxbackup_tests.m4 ax_check_mount_point.m4 ax_func_syscall.m4) + file(STRINGS "${base_dir}/infrastructure/m4/${m4_filename}" m4_functions REGEX "^ *AC[_A-Z]+\\(.*\\)$") + foreach(m4_function ${m4_functions}) + if(DEBUG) + message(STATUS "Processing m4_function: ${m4_function}") + endif() + + string(REGEX MATCH .* ac_check_headers ${m4_function}) + if(m4_function MATCHES "^ *AC_CHECK_HEADERS?\\(\\[([a-z./ ]+)\\](.*)\\)$") + if(DEBUG) + message(STATUS "Processing ac_check_headers: ${CMAKE_MATCH_1}") + endif() + + # http://stackoverflow.com/questions/5272781/what-is-common-way-to-split-string-into-list-with-cmake + string(REPLACE " " ";" header_files ${CMAKE_MATCH_1}) + + foreach(header_file ${header_files}) + list(APPEND detect_header_files ${header_file}) + endforeach() + elseif(m4_function MATCHES "^ *AC_CHECK_FUNCS\\(\\[([a-z./_ ]+)\\](.*)\\)$") + if(DEBUG) + message(STATUS "Processing ac_check_funcs: ${CMAKE_MATCH_1}") + endif() + + # http://stackoverflow.com/questions/5272781/what-is-common-way-to-split-string-into-list-with-cmake + string(REPLACE " " ";" function_names ${CMAKE_MATCH_1}) + + foreach(function_name ${function_names}) + list(APPEND detect_functions ${function_name}) + endforeach() + elseif(m4_function MATCHES "^ *AC_CHECK_DECLS\\(\\[([A-Za-z._/ ]+)\\](,,, ..#include <([^>]+)>..)?\\)$") + if(DEBUG) + message(STATUS "Processing ac_check_decls: ${CMAKE_MATCH_1} in ${CMAKE_MATCH_3}") + endif() + + # http://stackoverflow.com/questions/5272781/what-is-common-way-to-split-string-into-list-with-cmake + string(REPLACE " " ";" decl_names "${CMAKE_MATCH_1}") + string(REPLACE " " ";" header_files "${CMAKE_MATCH_3}") + + foreach(decl_name ${decl_names}) + string(TOUPPER ${decl_name} platform_var_name) + string(REGEX REPLACE "[/.]" "_" platform_var_name ${platform_var_name}) + check_symbol_exists("${decl_name}" "${header_files}" HAVE_DECL_${platform_var_name}) + file(APPEND "${boxconfig_h_file}" "#cmakedefine01 HAVE_DECL_${platform_var_name}\n") + endforeach() + elseif(m4_function MATCHES "^ *AC_SEARCH_LIBS\\(\\[([A-Za-z._/ ]+)\\], \\[([A-Za-z._]+)\\]\\)$") + if(DEBUG) + message(STATUS "Processing ac_search_libs: ${CMAKE_MATCH_1} in ${CMAKE_MATCH_2}") + endif() + + set(function_name ${CMAKE_MATCH_1}) + # http://stackoverflow.com/questions/5272781/what-is-common-way-to-split-string-into-list-with-cmake + string(REPLACE " " ";" library_names "${CMAKE_MATCH_2}") + + foreach(library_name ${library_names}) + string(TOUPPER ${library_name} platform_var_name) + check_library_exists(${library_name} ${function_name} "" HAVE_LIB_${platform_var_name}) + if(HAVE_LIB_${platform_var_name}) + target_link_libraries(lib_common PUBLIC ${library_name}) + endif() + endforeach() + elseif(m4_function MATCHES "^ *AC_CHECK_MEMBERS\\(\\[([A-Za-z._/ ]+)\\.([[A-Za-z_]+)\\](,,, ..(#include <([^>]+)>)..)?\\)$") + if(DEBUG) + message(STATUS "Processing ac_check_members: ${CMAKE_MATCH_1}.${CMAKE_MATCH_2} in ${CMAKE_MATCH_5}") + endif() + + set(struct_name "${CMAKE_MATCH_1}") + set(member_name "${CMAKE_MATCH_2}") + set(include_file "${CMAKE_MATCH_5}") + + string(TOUPPER "${struct_name}_${member_name}" platform_var_name) + string(REGEX REPLACE "[/. ]" "_" platform_var_name ${platform_var_name}) + + CHECK_CXX_SOURCE_COMPILES([=[ + #include "BoxConfig.cmake.h" + #include <${include_file}> + int main() + { + ${struct_name} foo; + return sizeof(foo.${member_name}) > 0 ? 0 : 1; + } + ]=] "HAVE_${platform_var_name}") + file(APPEND "${boxconfig_h_file}" "#cmakedefine HAVE_${platform_var_name}\n") + endif() + endforeach() + + # Build an intermediate version of BoxConfig.cmake.h for use in the following tests. + configure_file("${boxconfig_h_file}" "${boxconfig_cmake_h_dir}/BoxConfig.cmake.h") +endforeach() + +list(APPEND detect_header_files mntent.h sys/mnttab.h sys/mount.h sys/param.h) + +foreach(header_file ${detect_header_files}) + list(APPEND detect_header_files ${header_file}) + string(TOUPPER ${header_file} platform_var_name) + string(REGEX REPLACE "[/.]" "_" platform_var_name ${platform_var_name}) + check_include_files(${header_file} HAVE_${platform_var_name}) + file(APPEND "${boxconfig_h_file}" "#cmakedefine HAVE_${platform_var_name}\n") +endforeach() + +if(NOT HAVE_PCREPOSIX_H) + message(FATAL_ERROR "pcreposix.h not found at PCRE_ROOT/include: ${PCRE_ROOT}/include") +endif() + +# PCRE is required, so unconditionally define this: +set(HAVE_REGEX_SUPPORT 1) +file(APPEND "${boxconfig_h_file}" "#cmakedefine HAVE_REGEX_SUPPORT\n") + +foreach(function_name ${detect_functions}) + string(TOUPPER ${function_name} platform_var_name) + string(REGEX REPLACE "[/.]" "_" platform_var_name ${platform_var_name}) + check_function_exists(${function_name} HAVE_${platform_var_name}) + file(APPEND "${boxconfig_h_file}" "#cmakedefine HAVE_${platform_var_name}\n") +endforeach() + +check_symbol_exists(dirfd "dirent.h" HAVE_DECL_DIRFD) +file(APPEND "${boxconfig_h_file}" "#cmakedefine01 HAVE_DECL_DIRFD\n") + +# Emulate ax_check_mount_point.m4 +# These checks are run by multi-line M4 commands which are harder to parse/fake using +# regexps above, so we hard-code them here: +CHECK_CXX_SOURCE_COMPILES([=[ + #include "BoxConfig.cmake.h" + #ifdef HAVE_SYS_PARAM_H + # include + #endif + #include + int main() + { + struct statfs foo; + return sizeof(foo.f_mntonname) > 0 ? 0 : 1; + } + ]=] "HAVE_STRUCT_STATFS_F_MNTONNAME") +file(APPEND "${boxconfig_h_file}" "#cmakedefine HAVE_STRUCT_STATFS_F_MNTONNAME\n") +CHECK_CXX_SOURCE_COMPILES([=[ + #include "BoxConfig.cmake.h" + #ifdef HAVE_SYS_PARAM_H + # include + #endif + #include + int main() + { + struct statvfs foo; + return sizeof(foo.f_mntonname) > 0 ? 0 : 1; + } + ]=] "HAVE_STRUCT_STATVFS_F_MNTONNAME") +file(APPEND "${boxconfig_h_file}" "#cmakedefine HAVE_STRUCT_STATVFS_F_MNTONNAME\n") +if(HAVE_STRUCT_STATFS_F_MNTONNAME OR + HAVE_STRUCT_STATVFS_F_MNTONNAME OR + HAVE_STRUCT_MNTENT_MNT_DIR OR + HAVE_STRUCT_MNTTAB_MNT_MOUNTP) + + set(HAVE_MOUNTS 1) + file(APPEND "${boxconfig_h_file}" "#cmakedefine HAVE_MOUNTS\n") +endif() + +# Emulate ax_random_device.m4 +if(EXISTS /dev/urandom) + set(RANDOM_DEVICE /dev/urandom) +elseif(EXISTS /dev/arandom) + set(RANDOM_DEVICE /dev/arandom) +elseif(EXISTS /dev/random) + set(RANDOM_DEVICE /dev/random) +endif() +if(RANDOM_DEVICE) + set(HAVE_RANDOM_DEVICE TRUE) +endif() +file(APPEND "${boxconfig_h_file}" "#cmakedefine RANDOM_DEVICE \"${RANDOM_DEVICE}\"\n") +file(APPEND "${boxconfig_h_file}" "#cmakedefine HAVE_RANDOM_DEVICE\n") + +# Build an intermediate version of BoxConfig.cmake.h for use in the following tests: +configure_file("${boxconfig_h_file}" "${boxconfig_cmake_h_dir}/BoxConfig.cmake.h") + +foreach(struct_member_name "struct ucred.uid" "struct ucred.cr_uid") + string(REGEX MATCH "(.*)\\.(.*)" dummy_var ${struct_member_name}) + set(struct_name "${CMAKE_MATCH_1}") + set(member_name "${CMAKE_MATCH_2}") + + string(TOUPPER "${struct_name}_${member_name}" platform_var_name) + string(REGEX REPLACE "[/. ]" "_" platform_var_name ${platform_var_name}) + + CHECK_CXX_SOURCE_COMPILES([=[ + #include "BoxConfig.cmake.h" + + #ifdef HAVE_UCRED_H + # include + #endif + + #ifdef HAVE_SYS_PARAM_H + # include + #endif + + #ifdef HAVE_SYS_UCRED_H + # include + #endif + + #ifdef HAVE_SYS_SOCKET_H + # include + #endif + + int main() + { + ${struct_name} foo; + return sizeof(foo.${member_name}) > 0 ? 0 : 1; + } + ]=] "HAVE_${platform_var_name}") + file(APPEND "${boxconfig_h_file}" "#cmakedefine HAVE_${platform_var_name}\n") +endforeach() +set(CMAKE_REQUIRED_INCLUDES "") + +# Build the final version of BoxConfig.cmake.h, as a temporary file. +configure_file("${boxconfig_h_file}" "${boxconfig_cmake_h_dir}/BoxConfig.cmake.h.new") + +# Move the original back into place, and then replace it with the +# temporary one if different (which will force a rebuild of everything). +move_file_if_exists( + "${boxconfig_cmake_h_dir}/BoxConfig.cmake.h.bak" + "${boxconfig_cmake_h_dir}/BoxConfig.cmake.h") +replace_file_if_different( + "${boxconfig_cmake_h_dir}/BoxConfig.cmake.h" + "${boxconfig_cmake_h_dir}/BoxConfig.cmake.h.new") + +# Tell QDBM not to build itself as a DLL, because we want to link statically to it. +target_compile_definitions(qdbm PUBLIC -DQDBM_STATIC) + +# Silence some less-useful warnings +if(MSVC) + add_definitions(/wd4996 /wd4291) + set_property(TARGET qdbm PROPERTY CMAKE_STATIC_LINKER_FLAGS /IGNORE:LNK4006) +endif(MSVC) + # Define the location of the Perl executable, needed by testbackupstorefix file(TO_NATIVE_PATH "${PERL_EXECUTABLE}" perl_executable_native) string(REPLACE "\\" "\\\\" perl_path_escaped ${perl_executable_native}) diff --git a/infrastructure/travis-build.sh b/infrastructure/travis-build.sh index aa9f62c27..ad44e0962 100755 --- a/infrastructure/travis-build.sh +++ b/infrastructure/travis-build.sh @@ -5,17 +5,25 @@ set -x ccache -s -cd `dirname $0`/.. -./bootstrap -./configure CC="ccache $CC" CXX="ccache $CXX" "$@" -grep CXX config.status -make V=1 -./runtest.pl ALL $TEST_TARGET -# When making a release build, also check that we can build the default -# target and "parcels" (which is the same thing): -if [ "$TEST_TARGET" = "release" ]; then - make - make parcels +if [ "$BUILD" = 'cmake' ]; then + cd `dirname $0` + mkdir -p cmake/build + cd cmake/build + cmake --version + cmake -DCMAKE_BUILD_TYPE:STRING=$TEST_TARGET .. + make install + [ "$TEST" = "n" ] || ctest -V +else + cd `dirname $0`/.. + ./bootstrap + ./configure CC="ccache $CC" CXX="ccache $CXX" "$@" + grep CXX config.status + make V=1 + ./runtest.pl ALL $TEST_TARGET + if [ "$TEST_TARGET" = "release" ]; then + make + make parcels + fi fi ccache -s diff --git a/lib/bbackupquery/makedocumentation.pl.in b/lib/bbackupquery/makedocumentation.pl.in index 1def07684..503ac9c85 100755 --- a/lib/bbackupquery/makedocumentation.pl.in +++ b/lib/bbackupquery/makedocumentation.pl.in @@ -3,7 +3,7 @@ use strict; print "Creating built-in documentation for bbackupquery...\n"; -open DOC,"Documentation.txt" or die "Can't open Documentation.txt file: $!"; +open DOC, "Documentation.txt" or die "Can't open Documentation.txt file: $!"; my $section; my %help; my @in_order; diff --git a/lib/common/BoxPlatform.h b/lib/common/BoxPlatform.h index f87205877..f7c74bfc1 100644 --- a/lib/common/BoxPlatform.h +++ b/lib/common/BoxPlatform.h @@ -21,11 +21,13 @@ #define PLATFORM_DEV_NULL "/dev/null" -#ifdef _MSC_VER -#include "BoxConfig-MSVC.h" -#define NEED_BOX_VERSION_H +#if defined BOX_CMAKE +# include "BoxConfig.cmake.h" +#elif defined _MSC_VER +# include "BoxConfig-MSVC.h" +# define NEED_BOX_VERSION_H #else -#include "BoxConfig.h" +# include "BoxConfig.h" #endif #ifdef WIN32 diff --git a/lib/common/Test.h b/lib/common/Test.h index 36cd6a595..4b5cef616 100644 --- a/lib/common/Test.h +++ b/lib/common/Test.h @@ -243,12 +243,14 @@ void safe_sleep(int seconds); std::auto_ptr load_config_file(const std::string& config_file, const ConfigurationVerify& verify); -#ifdef _MSC_VER - // Our CMakeFiles compile tests to different executable filenames, - // e.g. test_common.exe instead of _test.exe. - #define TEST_EXECUTABLE BOX_MODULE ".exe" -#else - #define TEST_EXECUTABLE "./_test" -#endif +#ifndef TEST_EXECUTABLE +# ifdef _MSC_VER + // Our CMakeFiles compile tests to different executable filenames, + // e.g. test_common.exe instead of _test.exe. + #define TEST_EXECUTABLE BOX_MODULE ".exe" +# else + #define TEST_EXECUTABLE "./_test" +# endif +#endif // TEST_EXECUTABLE #endif // TEST__H diff --git a/lib/win32/emu.h b/lib/win32/emu.h index d0b7081bd..91793004a 100644 --- a/lib/win32/emu.h +++ b/lib/win32/emu.h @@ -18,7 +18,9 @@ #define EMU_INCLUDE // Need feature detection macros below -#ifdef _MSC_VER +#if defined BOX_CMAKE +# include "../common/BoxConfig.cmake.h" +#elif defined _MSC_VER # include "../common/BoxConfig-MSVC.h" # define NEED_BOX_VERSION_H #else