diff --git a/.gitignore b/.gitignore index e2bde249..6df2334d 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,5 @@ nbproject/* # Generated headers /src/ArcusExport.h + +/cmake-build-*/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d736ba4..cee1ebc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,63 +1,24 @@ project(arcus) -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.13) -include(GNUInstallDirs) -include(CMakePackageConfigHelpers) -include(GenerateExportHeader) - -option(BUILD_PYTHON "Build " ON) +# Basic projects options +option(BUILD_PYTHON "Build Python module" ON) option(BUILD_EXAMPLES "Build the example programs" ON) option(BUILD_STATIC "Build as a static library" OFF) -if(WIN32) - option(MSVC_STATIC_RUNTIME "Link the MSVC runtime statically" OFF) -endif() - -# We want to have access to protobuf_generate_cpp and other FindProtobuf features. -# However, if ProtobufConfig is used instead, there is a CMake option that controls -# this, which defaults to OFF. We need to force this option to ON instead. -set(protobuf_MODULE_COMPATIBLE ON CACHE INTERNAL "" FORCE) -find_package(Protobuf 3.0.0 REQUIRED) - -set(CMAKE_POSITION_INDEPENDENT_CODE ON) #Required if a patch to libArcus needs to be made via templates. - -if(BUILD_PYTHON) - list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) - - # FIXME: Remove the code for CMake <3.12 once we have switched over completely. - # FindPython3 is a new module since CMake 3.12. It deprecates FindPythonInterp and FindPythonLibs. - if(${CMAKE_VERSION} VERSION_LESS 3.12) - # FIXME: Use FindPython3 to find Python, new in CMake 3.12. - # However currently on our CI server it finds the wrong Python version and then doesn't find the headers. - find_package(PythonInterp 3.4 REQUIRED) - find_package(PythonLibs 3.4 REQUIRED) - - else() - # Use FindPython3 for CMake >=3.12 - find_package(Python3 3.4 REQUIRED COMPONENTS Interpreter Development) - endif() - - find_package(SIP REQUIRED) - if(NOT DEFINED LIB_SUFFIX) - set(LIB_SUFFIX "") - endif() - - include_directories(python/ src/ ${SIP_INCLUDE_DIRS} ${Python3_INCLUDE_DIRS}) -endif() - -set(CMAKE_CXX_STANDARD 17) +include(cmake/StandardProjectSettings.cmake) +include(CMakePackageConfigHelpers) +include(GenerateExportHeader) -if(APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") -endif() +set(ARCUS_VERSION 1.1.0) +set(ARCUS_SOVERSION 3) set(arcus_SRCS src/Socket.cpp src/SocketListener.cpp src/MessageTypeStore.cpp src/PlatformSocket.cpp - src/Error.cpp -) + src/Error.cpp) set(arcus_HDRS src/Socket.h @@ -65,53 +26,43 @@ set(arcus_HDRS src/Types.h src/MessageTypeStore.h src/Error.h - ${CMAKE_CURRENT_BINARY_DIR}/src/ArcusExport.h + src/ArcusExport.h ) -set(ARCUS_VERSION 1.1.0) -set(ARCUS_SOVERSION 3) - -set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_FULL_LIBDIR}") - -if(BUILD_STATIC) - add_library(Arcus STATIC ${arcus_SRCS}) - if(NOT WIN32 OR CMAKE_COMPILER_IS_GNUCXX) - target_link_libraries(Arcus PRIVATE pthread) - set_target_properties(Arcus PROPERTIES COMPILE_FLAGS -fPIC) - endif() -else() +if(${BUILD_SHARED_LIBS}) add_library(Arcus SHARED ${arcus_SRCS}) +else() + add_library(Arcus STATIC ${arcus_SRCS}) endif() -if(MSVC_STATIC_RUNTIME) - foreach(flag_var - CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) - if(${flag_var} MATCHES "/MD") - string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") - endif(${flag_var} MATCHES "/MD") - endforeach(flag_var) -endif() +use_threads(Arcus) -if(BUILD_PYTHON) - set(SIP_EXTRA_FILES_DEPEND python/SocketListener.sip python/Types.sip python/PythonMessage.sip python/Error.sip) - set(SIP_EXTRA_SOURCE_FILES python/PythonMessage.cpp) - set(SIP_EXTRA_OPTIONS -g -n PyQt5.sip) # -g means always release the GIL before calling C++ methods. -n PyQt5.sip is required to not get the PyCapsule error - add_sip_python_module(Arcus python/Socket.sip Arcus) -endif() +find_package(Protobuf 3.9.2 REQUIRED) +target_link_libraries(Arcus PUBLIC protobuf::libprotobuf) -target_include_directories(Arcus PUBLIC - $ - $ - ${PROTOBUF_INCLUDE_DIR} -) -target_link_libraries(Arcus PUBLIC ${PROTOBUF_LIBRARIES}) +set_project_standards(Arcus) +set_project_warnings(Arcus) +enable_sanitizers(Arcus) + +if(NOT MSVC) + if(${CMAKE_BUILD_TYPE} STREQUAL "Debug" OR ${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") + target_compile_definitions(Arcus PUBLIC -DARCUS_DEBUG) + endif() +endif() if(WIN32) - add_definitions(-D_WIN32_WINNT=0x0600) # Declare we require Vista or higher, this allows us to use IPv6 functions. + # Declare we require Vista or higher, this allows us to use IPv6 functions. + target_compile_definitions(Arcus PUBLIC -D_WIN32_WINNT=0x0600) target_link_libraries(Arcus PUBLIC Ws2_32) endif() +generate_export_header(Arcus EXPORT_FILE_NAME ${CMAKE_SOURCE_DIR}/src/ArcusExport.h) +target_include_directories(Arcus + PUBLIC + $ + $ + ) + if(${CMAKE_BUILD_TYPE}) if(${CMAKE_BUILD_TYPE} STREQUAL "Debug" OR ${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") add_definitions(-DARCUS_DEBUG) @@ -128,12 +79,34 @@ set_target_properties(Arcus PROPERTIES VISIBILITY_INLINES_HIDDEN 1 ) -generate_export_header(Arcus - EXPORT_FILE_NAME src/ArcusExport.h -) -# This is required when building out-of-tree. -# The compiler won't find the generated header otherwise. -include_directories(${CMAKE_BINARY_DIR}/src) +if(BUILD_PYTHON) + list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) + + find_package(SIP REQUIRED) + if(NOT DEFINED LIB_SUFFIX) + set(LIB_SUFFIX "") + endif() + + set(SIP_EXTRA_FILES_DEPEND python/SocketListener.sip python/Types.sip python/PythonMessage.sip python/Error.sip) + set(SIP_EXTRA_SOURCE_FILES python/PythonMessage.cpp) + set(SIP_EXTRA_OPTIONS -y Arcus.pyi -o -g -n PyQt5.sip) # -g means always release the GIL before calling C++ methods. -n PyQt5.sip is required to not get the PyCapsule error + add_sip_python_module(Arcus python/Socket.sip Arcus) + use_python(python_module_Arcus Interpreter Development) + + target_link_libraries(python_module_Arcus + PRIVATE + SIP::SIP + protobuf::libprotobuf + Arcus + ) + target_include_directories(python_module_Arcus + PRIVATE + $ + $ + ) + set_project_standards(python_module_Arcus) + set_project_warnings(python_module_Arcus) +endif() if(BUILD_EXAMPLES) add_subdirectory(examples) @@ -147,9 +120,7 @@ install(TARGETS Arcus PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/Arcus ) -install(EXPORT Arcus-targets - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Arcus -) +install(EXPORT Arcus-targets DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Arcus) configure_package_config_file(ArcusConfig.cmake.in ${CMAKE_BINARY_DIR}/ArcusConfig.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Arcus) write_basic_package_version_file(${CMAKE_BINARY_DIR}/ArcusConfigVersion.cmake VERSION ${ARCUS_VERSION} COMPATIBILITY SameMajorVersion) diff --git a/cmake/FindSIP.cmake b/cmake/FindSIP.cmake index e15e2b4e..023b7759 100644 --- a/cmake/FindSIP.cmake +++ b/cmake/FindSIP.cmake @@ -24,16 +24,13 @@ if(APPLE) set(CMAKE_FIND_FRAMEWORK LAST) endif() -# FIXME: Use FindPython3 to find Python, new in CMake 3.12. -# However currently on our CI server it finds the wrong Python version and then doesn't find the headers. -find_package(PythonInterp 3.5 REQUIRED) -find_package(PythonLibs 3.5 REQUIRED) - -# Define variables that are available in FindPython3, so there's no need to branch off in the later part. -set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE}) -set(Python3_INCLUDE_DIRS ${PYTHON_INCLUDE_DIRS}) -set(Python3_LIBRARIES ${PYTHON_LIBRARIES}) -set(Python3_VERSION_MINOR "${PYTHON_VERSION_MINOR}") +if(NOT Python_VERSION) + set(Python_VERSION + 3.8 + CACHE STRING "Python Version" FORCE) + message(STATUS "Setting Python version to ${Python_VERSION}. Set Python_VERSION if you want to compile against an other version.") +endif() +find_package(Python3 ${Python_VERSION} EXACT REQUIRED COMPONENTS Interpreter Development) execute_process( COMMAND ${Python3_EXECUTABLE} -c @@ -89,4 +86,9 @@ if(SIP_FOUND) include(${CMAKE_CURRENT_LIST_DIR}/SIPMacros.cmake) endif() -mark_as_advanced(SIP_EXECUTABLE SIP_INCLUDE_DIRS SIP_VERSION) +add_library(SIP::SIP INTERFACE IMPORTED) +set_property(TARGET SIP::SIP + PROPERTY INTERFACE_INCLUDE_DIRECTORIES + ${SIP_INCLUDE_DIRS} APPEND) + +mark_as_advanced(SIP_EXECUTABLE SIP_INCLUDE_DIRS SIP_VERSION SIP::SIP) diff --git a/cmake/SIPMacros.cmake b/cmake/SIPMacros.cmake index 50553e0f..a0149bcf 100644 --- a/cmake/SIPMacros.cmake +++ b/cmake/SIPMacros.cmake @@ -106,14 +106,6 @@ MACRO(ADD_SIP_PYTHON_MODULE MODULE_NAME MODULE_SIP) DEPENDS ${_abs_module_sip} ${SIP_EXTRA_FILES_DEPEND} ) ADD_LIBRARY(${_logical_name} MODULE ${_sip_output_files} ${SIP_EXTRA_SOURCE_FILES}) - IF (NOT APPLE) - IF ("${Python3_VERSION_MINOR}" GREATER 7) - MESSAGE(STATUS "Python > 3.7 - not linking to libpython") - ELSE () - TARGET_LINK_LIBRARIES(${_logical_name} ${Python3_LIBRARIES}) - ENDIF () - ENDIF (NOT APPLE) - TARGET_LINK_LIBRARIES(${_logical_name} ${EXTRA_LINK_LIBRARIES}) IF (APPLE) SET_TARGET_PROPERTIES(${_logical_name} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") ENDIF (APPLE) diff --git a/cmake/StandardProjectSettings.cmake b/cmake/StandardProjectSettings.cmake new file mode 100644 index 00000000..0d5dedea --- /dev/null +++ b/cmake/StandardProjectSettings.cmake @@ -0,0 +1,321 @@ +include(GNUInstallDirs) # Standard install dirs +set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_FULL_LIBDIR}") # TODO: Check if this is still needed + +# Uniform how we define BUILD_STATIC or BUILD_SHARED_LIBS (common practice) +if(DEFINED BUILD_STATIC) + if(DEFINED BUILD_SHARED_LIBS) + if(${BUILD_SHARED_LIBS} AND ${BUILD_STATIC}) + message(FATAL_ERROR "Conflicting arguments for BUILD_SHARED_LIBS=${BUILD_SHARED_LIBS} and BUILD_STATIC=${BUILD_STATIC}") + endif() + else() + set(BUILD_SHARED_LIBS NOT ${BUILD_STATIC}) + endif() +else() + if(NOT DEFINED BUILD_SHARED_LIBS) + set(BUILD_SHARED_LIBS ON) + endif() +endif() +message(STATUS "Setting BUILD_SHARED_LIBS to ${BUILD_SHARED_LIBS}") + +# Set a default build type if none was specified +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to 'Release' as none was specified.") + set(CMAKE_BUILD_TYPE + Release + CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui, ccmake + set_property( + CACHE CMAKE_BUILD_TYPE + PROPERTY STRINGS + "Debug" + "Release" + "MinSizeRel" + "RelWithDebInfo") +endif() + +# Generate compile_commands.json to make it easier to work with clang based tools +message(STATUS "Generating compile commands to ${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json") +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +option(ENABLE_IPO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)" ON) +if(ENABLE_IPO) + include(CheckIPOSupported) + check_ipo_supported( + RESULT + result + OUTPUT + output) + if(result) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + else() + message(SEND_ERROR "IPO is not supported: ${output}") + endif() +endif() + +if (NOT MSVC) + # Compile with the -fPIC options if supported + if(DEFINED POSITION_INDEPENDENT_CODE) # Use the user/Conan set value + message(STATUS "Using POSITION_INDEPENDENT_CODE: ${POSITION_INDEPENDENT_CODE}") + else() + set(POSITION_INDEPENDENT_CODE ON) # Defaults to on + message(STATUS "Setting POSITION_INDEPENDENT_CODE: ${POSITION_INDEPENDENT_CODE}") + endif() +else() + # Set Visual Studio flags MD/MDd or MT/MTd + if(NOT DEFINED CMAKE_MSVC_RUNTIME_LIBRARY) + if(BUILD_STATIC OR NOT BUILD_SHARED_LIBS) + message(STATUS "Setting MT/MTd flags") + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + else() + message(STATUS "Setting MD/MDd flags") + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") + endif() + endif() +endif() + +# Use C++17 Standard +message(STATUS "Setting C++17 support with extensions off and standard required") +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Set common project options for the target +function(set_project_standards project_name) + if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + option(ENABLE_BUILD_WITH_TIME_TRACE "Enable -ftime-trace to generate time tracing .json files on clang" OFF) + if(ENABLE_BUILD_WITH_TIME_TRACE) + message(STATUS "Enabling time tracing for ${project_name}") + add_compile_definitions(${project_name} PRIVATE -ftime-trace) + endif() + if (APPLE) + message(STATUS "Compiling ${project_name} against libc++") + target_compile_options(${project_name} PRIVATE "-stdlib=libc++") + endif() + endif() +endfunction() + +# Ultimaker uniform Python linking method +function(use_python project_name) + set(COMPONENTS ${ARGN}) + if(NOT DEFINED Python_VERSION) + set(Python_VERSION + 3.8 + CACHE STRING "Python Version" FORCE) + message(STATUS "Setting Python version to ${Python_VERSION}. Set Python_VERSION if you want to compile against an other version.") + endif() + if(APPLE) + set(Python_FIND_FRAMEWORK NEVER) + endif() + find_package(Python ${Python_VERSION} EXACT REQUIRED COMPONENTS ${COMPONENTS}) + target_link_libraries(${project_name} PRIVATE Python::Python) + target_include_directories(${project_name} PUBLIC ${Python_INCLUDE_DIRS}) + message(STATUS "Linking and building ${project_name} against Python ${Python_VERSION}") +endfunction() + +# Ultimaker uniform Thread linking method +function(use_threads project_name) + message(STATUS "Enabling threading support for ${project_name}") + set(CMAKE_THREAD_PREFER_PTHREAD TRUE) + set(THREADS_PREFER_PTHREAD_FLAG TRUE) + find_package(Threads) + target_link_libraries(${project_name} PRIVATE Threads::Threads) +endfunction() + +# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md +function(set_project_warnings project_name) + message(STATUS "Setting warnings for ${project_name}") + set(MSVC_WARNINGS + /W4 # Baseline reasonable warnings + /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data + /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data + /w14263 # 'function': member function does not override any base class virtual member function + /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not + # be destructed correctly + /w14287 # 'operator': unsigned/negative constant mismatch + /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside + # the for-loop scope + /w14296 # 'operator': expression is always 'boolean_value' + /w14311 # 'variable': pointer truncation from 'type1' to 'type2' + /w14545 # expression before comma evaluates to a function which is missing an argument list + /w14546 # function call before comma missing argument list + /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect + /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? + /w14555 # expression has no effect; expected expression with side- effect + /w14619 # pragma warning: there is no warning number 'number' + /w14640 # Enable warning on thread un-safe static member initialization + /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior. + /w14905 # wide string literal cast to 'LPSTR' + /w14906 # string literal cast to 'LPWSTR' + /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied + /permissive- # standards conformance mode for MSVC compiler. + ) + + set(CLANG_WARNINGS + -Wall + -Wextra # reasonable and standard + -Wshadow # warn the user if a variable declaration shadows one from a parent context + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps + # catch hard to track down memory errors + -Wold-style-cast # warn for c-style casts + -Wcast-align # warn for potential performance problem casts + -Wunused # warn on anything being unused + -Woverloaded-virtual # warn if you overload (not override) a virtual function + -Wpedantic # warn if non-standard C++ is used + -Wconversion # warn on type conversions that may lose data + -Wsign-conversion # warn on sign conversions + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output (ie printf) + ) + + set(GCC_WARNINGS + ${CLANG_WARNINGS} + -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist + -Wduplicated-cond # warn if if / else chain has duplicated conditions + -Wduplicated-branches # warn if if / else branches have duplicated code + -Wlogical-op # warn about logical operations being used where bitwise were probably wanted + -Wuseless-cast # warn if you perform a cast to the same type + ) + + if(MSVC) + set(PROJECT_WARNINGS ${MSVC_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(PROJECT_WARNINGS ${CLANG_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(PROJECT_WARNINGS ${GCC_WARNINGS}) + else() + message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") + endif() + + target_compile_options(${project_name} PRIVATE ${PROJECT_WARNINGS}) + +endfunction() + +# This function will prevent in-source builds +function(AssureOutOfSourceBuilds) + # make sure the user doesn't play dirty with symlinks + get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) + get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) + + # disallow in-source builds + if("${srcdir}" STREQUAL "${bindir}") + message("######################################################") + message("Warning: in-source builds are disabled") + message("Please create a separate build directory and run cmake from there") + message("######################################################") + message(FATAL_ERROR "Quitting configuration") + endif() +endfunction() + +option(ALLOW_IN_SOURCE_BUILD "Allow building in your source folder. Strongly discouraged" OFF) +if(NOT ALLOW_IN_SOURCE_BUILD) + assureoutofsourcebuilds() +endif() + +function(enable_sanitizers project_name) + + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + option(ENABLE_COVERAGE "Enable coverage reporting for gcc/clang" FALSE) + + if(ENABLE_COVERAGE) + target_compile_options(${project_name} INTERFACE --coverage -O0 -g) + target_link_libraries(${project_name} INTERFACE --coverage) + endif() + + set(SANITIZERS "") + + option(ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" FALSE) + if(ENABLE_SANITIZER_ADDRESS) + list(APPEND SANITIZERS "address") + endif() + + option(ENABLE_SANITIZER_LEAK "Enable leak sanitizer" FALSE) + if(ENABLE_SANITIZER_LEAK) + list(APPEND SANITIZERS "leak") + endif() + + option(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR "Enable undefined behavior sanitizer" FALSE) + if(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR) + list(APPEND SANITIZERS "undefined") + endif() + + option(ENABLE_SANITIZER_THREAD "Enable thread sanitizer" FALSE) + if(ENABLE_SANITIZER_THREAD) + if("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS) + message(WARNING "Thread sanitizer does not work with Address and Leak sanitizer enabled") + else() + list(APPEND SANITIZERS "thread") + endif() + endif() + + option(ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" FALSE) + if(ENABLE_SANITIZER_MEMORY AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + if("address" IN_LIST SANITIZERS + OR "thread" IN_LIST SANITIZERS + OR "leak" IN_LIST SANITIZERS) + message(WARNING "Memory sanitizer does not work with Address, Thread and Leak sanitizer enabled") + else() + list(APPEND SANITIZERS "memory") + endif() + endif() + + list( + JOIN + SANITIZERS + "," + LIST_OF_SANITIZERS) + + endif() + + if(LIST_OF_SANITIZERS) + if(NOT + "${LIST_OF_SANITIZERS}" + STREQUAL + "") + target_compile_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) + target_link_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) + endif() + endif() + +endfunction() + +option(ENABLE_CPPCHECK "Enable static analysis with cppcheck" OFF) +option(ENABLE_CLANG_TIDY "Enable static analysis with clang-tidy" OFF) +option(ENABLE_INCLUDE_WHAT_YOU_USE "Enable static analysis with include-what-you-use" OFF) + +if(ENABLE_CPPCHECK) + find_program(CPPCHECK cppcheck) + if(CPPCHECK) + message(STATUS "Using cppcheck") + set(CMAKE_CXX_CPPCHECK + ${CPPCHECK} + --suppress=missingInclude + --enable=all + --inline-suppr + --inconclusive + -i + ${CMAKE_SOURCE_DIR}/imgui/lib) + else() + message(WARNING "cppcheck requested but executable not found") + endif() +endif() + +if(ENABLE_CLANG_TIDY) + find_program(CLANGTIDY clang-tidy) + if(CLANGTIDY) + message(STATUS "Using clang-tidy") + set(CMAKE_CXX_CLANG_TIDY ${CLANGTIDY} -extra-arg=-Wno-unknown-warning-option) + else() + message(WARNING "clang-tidy requested but executable not found") + endif() +endif() + +if(ENABLE_INCLUDE_WHAT_YOU_USE) + find_program(INCLUDE_WHAT_YOU_USE include-what-you-use) + if(INCLUDE_WHAT_YOU_USE) + message(STATUS "Using include-what-you-use") + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE}) + else() + message(WARNING "include-what-you-use requested but executable not found") + endif() +endif() \ No newline at end of file diff --git a/cmake/mingw_toolchain.cmake b/cmake/mingw_toolchain.cmake new file mode 100644 index 00000000..62a30edc --- /dev/null +++ b/cmake/mingw_toolchain.cmake @@ -0,0 +1,8 @@ +set(CMAKE_BUILD_TYPE Release) +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32) +set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) +set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) +set(BUILD_PYTHON OFF) +set(BUILD_EXAMPLES OFF) +set(BUILD_STATIC ON) \ No newline at end of file diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 00000000..6199b929 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,125 @@ +import os +import pathlib + +from conans import ConanFile, tools +from conan.tools.cmake import CMakeToolchain, CMakeDeps, CMake + +class ArcusConan(ConanFile): + name = "Arcus" + version = "4.11.0" + license = "LGPL-3.0" + author = "Ultimaker B.V." + url = "https://github.com/Ultimaker/libArcus" + description = "Communication library between internal components for Ultimaker software" + topics = ("conan", "python", "binding", "sip", "cura", "protobuf", "c++") + settings = "os", "compiler", "build_type", "arch" + revision_mode = "scm" + build_policy = "missing" + exports = "LICENSE" + options = { + "build_python": [True, False], + "shared": [True, False], + "fPIC": [True, False], + "examples": [True, False] + } + default_options = { + "build_python": True, + "shared": True, + "fPIC": True, + "examples": False + } + scm = { + "type": "git", + "subfolder": ".", + "url": "auto", + "revision": "auto" + } + + def config_options(self): + if self.settings.os == "Windows": + self.options.shared = False + if self.settings.compiler == "gcc": + self.options.build_python = False + if self.settings.os == "Macos": + self.options["protobuf"].shared = False + else: + self.options["protobuf"].shared = self.options.shared + + def configure(self): + if self.options.build_python: + self.options["SIP"].shared = self.options.shared + if self.options.shared or self.settings.compiler == "Visual Studio": + del self.options.fPIC + + def build_requirements(self): + self.build_requires("cmake/[>=3.16.2]") + + def requirements(self): + if self.options.build_python: + self.requires("Python/3.8.10@python/testing") + self.requires("SIP/[>=4.19.24]@riverbankcomputing/testing") + self.requires("protobuf/3.17.1") + + def validate(self): + if self.settings.compiler.get_safe("cppstd"): + tools.check_min_cppstd(self, 17) + + def generate(self): + cmake = CMakeDeps(self) + cmake.generate() + + tc = CMakeToolchain(self) + + # FIXME: This shouldn't be necessary (maybe a bug in Conan????) + if self.settings.compiler == "Visual Studio": + tc.blocks["generic_system"].values["generator_platform"] = None + tc.blocks["generic_system"].values["toolset"] = None + + tc.variables["ALLOW_IN_SOURCE_BUILD"] = True + tc.variables["BUILD_EXAMPLES"] = self.options.examples + tc.variables["BUILD_PYTHON"] = self.options.build_python + if self.options.build_python: + tc.variables["Python_VERSION"] = self.deps_cpp_info["Python"].version + tc.variables["SIP_MODULE_SITE_PATH"] = "site-packages" + + tc.variables["BUILD_STATIC"] = not self.options.shared + + tc.generate() + + _cmake = None + + def configure_cmake(self): + if self._cmake: + return self._cmake + self._cmake = CMake(self) + self._cmake.configure() + return self._cmake + + def build(self): + cmake = self.configure_cmake() + cmake.build() + + def package(self): + cmake = self.configure_cmake() + cmake.install() + + def package_info(self): + self.cpp_info.includedirs = ["include"] + self.cpp_info.libs = tools.collect_libs(self) + self.libdirs = ["lib64", "lib"] + if self.in_local_cache: + self.runenv_info.prepend_path("PYTHONPATH", os.path.join(self.package_folder, "site-packages")) + else: + self.runenv_info.prepend_path("PYTHONPATH", os.path.join(pathlib.Path(__file__).parent.absolute(), + f"cmake-build-{self.options.build_type}".lower())) + try: + tools.rmdir(os.path.join(self.package_folder, "site-packages", str(self.settings.build_type))) + except: + pass + self.cpp_info.defines.append("ARCUS") + if self.settings.build_type == "Debug": + self.cpp_info.defines.append("ARCUS_DEBUG") + if self.settings.os in ["Linux", "FreeBSD", "Macos"]: + self.cpp_info.system_libs.append("pthread") + elif self.settings.os == "Windows": + self.cpp_info.system_libs.append("ws2_32") diff --git a/docker/build-deb-mingw64.sh b/docker/build-deb-mingw64.sh index b5927bfb..a545b773 100755 --- a/docker/build-deb-mingw64.sh +++ b/docker/build-deb-mingw64.sh @@ -9,18 +9,13 @@ CURA_DEV_ENV_ROOT=/opt/cura-dev export PATH="${CURA_DEV_ENV_ROOT}/bin:${PATH}" +apt -y remove cmake +apt -y --no-install-recommends install python3-pip +pip3 install cmake --upgrade + mkdir build cd build -cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_SYSTEM_NAME="Windows" \ - -DCMAKE_FIND_ROOT_PATH=/usr/x86_64-w64-mingw32 \ - -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc \ - -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ \ - -DBUILD_PYTHON=OFF \ - -DBUILD_EXAMPLES=OFF \ - -DBUILD_STATIC=ON \ - .. +cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/mingw_toolchain.cmake .. make cpack \ --config ../cmake/cpack_config_deb_mingw64.cmake \ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 012d2ebb..3a898873 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,25 +1,34 @@ - -include_directories(example ${CMAKE_CURRENT_BINARY_DIR}) +find_package(Threads) +find_package(Protobuf 3.9.2 REQUIRED) set(example_SRCS - example.cpp - example_pb2.py -) + example.cpp + example_pb2.py + ) protobuf_generate_cpp(example_PB_SRCS example_PB_HDRS "example.proto") add_executable(example ${example_SRCS} ${example_PB_SRCS}) -target_link_libraries(example Arcus) -if(NOT WIN32 OR CMAKE_COMPILER_IS_GNUCXX) - target_link_libraries(example pthread) - set_target_properties(example PROPERTIES COMPILE_FLAGS "-std=c++11") +target_link_libraries(example + PRIVATE + Arcus + protobuf::libprotobuf + Threads::Threads + ) +target_include_directories(example PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + +if (WIN32 AND NOT BUILD_STATIC) + add_custom_command(TARGET example POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_BINARY_DIR}/source/Arcus.dll" + "${CMAKE_CURRENT_BINARY_DIR}") endif() add_custom_command( - OUTPUT example_pb2.py - COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} - ARGS --python_out ${CMAKE_CURRENT_BINARY_DIR} --proto_path=${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/example.proto - COMMENT "Running Python protocol buffer compiler on example.proto" - VERBATIM ) + OUTPUT example_pb2.py + COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} + ARGS --python_out ${CMAKE_CURRENT_BINARY_DIR} --proto_path=${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/example.proto + COMMENT "Running Python protocol buffer compiler on example.proto" + VERBATIM ) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/example_py.sh ${CMAKE_CURRENT_BINARY_DIR}/example_py.sh) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/example_py.sh ${CMAKE_CURRENT_BINARY_DIR}/example_py.sh) \ No newline at end of file diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt new file mode 100644 index 00000000..5be691aa --- /dev/null +++ b/test_package/CMakeLists.txt @@ -0,0 +1,19 @@ +project(PackageTest) +cmake_minimum_required(VERSION 3.13) + +find_package(Arcus REQUIRED) +find_package(Threads) +find_package(Protobuf 3.9.2 REQUIRED) + +protobuf_generate_cpp(test_PB_SRC test_PB_HDR "test.proto") + +add_executable(test test.cpp ${test_PB_SRC} ${test_PB_HDR}) + +target_link_libraries(test + PRIVATE + Arcus::Arcus + protobuf::libprotobuf + Threads::Threads + ) + +target_include_directories(test PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) \ No newline at end of file diff --git a/test_package/conanfile.py b/test_package/conanfile.py new file mode 100644 index 00000000..9b4205d4 --- /dev/null +++ b/test_package/conanfile.py @@ -0,0 +1,28 @@ +from conans import ConanFile, CMake +from conan.tools.cmake import CMakeToolchain, CMakeDeps, CMake + + +class ArcusTestConan(ConanFile): + settings = "os", "compiler", "build_type", "arch" + + def generate(self): + cmake = CMakeDeps(self) + cmake.generate() + tc = CMakeToolchain(self) + if self.settings.compiler == "Visual Studio": + tc.blocks["generic_system"].values["generator_platform"] = None + tc.blocks["generic_system"].values["toolset"] = None + + # FIXME: Otherwise it throws: error LNK2001: unresolved external symbol "__declspec(dllimport) + tc.variables["BUILD_STATIC"] = False if self.settings.os != "Windows" else True + tc.variables["BUILD_SHARED_LIBS"] = True if self.settings.os != "Windows" else False + + tc.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def test(self): + pass # only interested in compiling and linking \ No newline at end of file diff --git a/test_package/test.cpp b/test_package/test.cpp new file mode 100644 index 00000000..0139bb77 --- /dev/null +++ b/test_package/test.cpp @@ -0,0 +1,98 @@ +#include +#include +#include + +#include "Arcus/Socket.h" +#include "Arcus/SocketListener.h" +#include "Arcus/Error.h" + +#include "test.pb.h" + +struct Object + { + public: + int id; + std::string vertices; + std::string normals; + std::string indices; + }; + +class Listener : public Arcus::SocketListener + { + public: + void stateChanged(Arcus::SocketState::SocketState new_state) + { + std::cout << "State Changed: " << new_state << std::endl; + } + + void messageReceived() + { + } + + void error(const Arcus::Error& new_error) + { + std::cout << new_error << std::endl; + } + }; + +std::vector objects; + +void handleMessage(Arcus::Socket& socket, Arcus::MessagePtr message); + +int main(int argc, char** argv) +{ + Arcus::Socket socket; + + socket.registerMessageType(&Example::ObjectList::default_instance()); + socket.registerMessageType(&Example::ProgressUpdate::default_instance()); + socket.registerMessageType(&Example::SlicedObjectList::default_instance()); + + return 0; +} + +void handleMessage(Arcus::Socket& socket, Arcus::MessagePtr message) +{ + // (Dynamicly) cast the message to one of our types. If this works (does not return a nullptr), we've found the right type. + auto objectList = dynamic_cast(message.get()); + if(objectList) + { + objects.clear(); + + std::cout << "Received object list containing " << objectList->objects_size() << " objects" << std::endl; + + for(auto objectDesc : objectList->objects()) + { + Object obj; + obj.id = objectDesc.id(); + obj.vertices = objectDesc.vertices(); + obj.normals = objectDesc.normals(); + obj.indices = objectDesc.indices(); + objects.push_back(obj); + } + + auto msg = std::make_shared(); + int progress = 0; + for(auto object : objects) + { + auto slicedObject = msg->add_objects(); + slicedObject->set_id(object.id); + + for(int i = 0; i < 1000; ++i) + { + auto polygon = slicedObject->add_polygons(); + polygon->set_type(i % 2 == 0 ? Example::Polygon_Type_InnerType : Example::Polygon_Type_OuterType); + polygon->set_points(object.vertices); + } + + auto update = std::make_shared(); + update->set_objectid(object.id); + update->set_amount((float(++progress) / float(objects.size())) * 100.f); + socket.sendMessage(update); + } + + std::cout << "Sending SlicedObjectList" << std::endl; + socket.sendMessage(msg); + + return; + } +} diff --git a/test_package/test.proto b/test_package/test.proto new file mode 100644 index 00000000..f585fb9a --- /dev/null +++ b/test_package/test.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +package Example; + +message ObjectList { + repeated Object objects = 1; +} + +message Object { + int32 id = 1; + bytes vertices = 2; //An array of 3 floats. + bytes normals = 3; //An array of 3 floats. + bytes indices = 4; //An array of ints. +} + +message ProgressUpdate { + int32 objectId = 1; + int32 amount = 2; +} + +message SlicedObjectList { + repeated SlicedObject objects = 1; +} + +message SlicedObject { + int32 id = 1; + + repeated Polygon polygons = 2; +} + +message Polygon { + enum Type + { + InnerType = 0; + OuterType = 1; + } + + Type type = 1; + bytes points = 2; +} \ No newline at end of file