diff --git a/README.md b/README.md index b59f173..11850b7 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,54 @@ cookiecutter-cpp ================ -A test-driven C++ project template built on CMake and GoogleTest -[![Build Status] - (https://travis-ci.org/hbristow/cookiecutter-cpp.svg)] - (https://travis-ci.org/hbristow/cookiecutter-cpp) -Usage: + +A test-driven C++ project template built on CMake and GoogleTest, that uses Armadillo as an example. + +CMake is a fast and easy build tool. +Use of GoogleTest test ensures robust code. +Doxygen enables documentation. +pmm is a package manager manager. You can configure your project to use Conan or VCPKG. This may become more important for cross platform development. + +This template was made by combining: +Original Cookicutter project: https://github.com/hbristow/cookiecutter-cpp +A lecture of Igor Bogoslavskyi: https://www.youtube.com/watch?v=q8xO2eJijy4&list=PLgnQpQtFTOGR50iIOtO36nK6aNPtVq98C&index=4 +Boilerplate pmm project: https://github.com/ariveron/boilerplate-pmm-vcpkg-cmake +CMake Example: https://github.com/dev-cafe/cmake-cookbook/blob/master/chapter-10/recipe-01/cxx-example/CMakeLists.txt + + +Note: currently the Vcpkg version of Armadillo is not compiled using any wrappers. Therefore they must be explicitly disabled in the scripts that use them. Vcpkg versions of OpenBLAS and LAPACK are linked explicitly. + +```cpp +#define ARMA_DONT_USE_WRAPPER +#define ARMA_DONT_USE_BLAS +#define ARMA_DONT_USE_LAPACK +//#define ARMA_USE_BLAS +#include //system wide installed package. +``` + +Linux: +------ + + $ pip install cookiecutter + $ cookiecutter gh:reinout91/cookiecutter-cpp + $ cd {project dir}/build + $ cmake .. + $ make + $ ctest -vv + $ ../bin/main + $ make install + +Windows: (TESTS DON'T COMPILE YET!) ------ +note: add C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin (or similar) to PATH (edit environment settings) $ pip install cookiecutter - $ cookiecutter gh:hbristow/cookiecutter-cpp + $ python -m cookiecutter gh:reinout91/cookiecutter-cpp + $ cd {project dir}/build + $ cmake .. + $ MSBuild.exe {project name}.sln /property:Configuration=Debug + +This template was tested on Windows (using msvc compiler) and Linux (using clang) + diff --git a/cookiecutter.json b/cookiecutter.json index 14ba594..9776cc1 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -5,7 +5,7 @@ "check_style": true, "pedantic": true, "version": "0.0.1", - "year": 2015, + "year": 2020, "author": "First Last", "email": "{{ cookiecutter.author.split()|join('.')|lower }}@gmail.com", "username": "{{ cookiecutter.author|first|lower }}{{ cookiecutter.author.split()|last|lower }}", diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 7190096..1aa5594 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -1,4 +1,6 @@ """Post-project generation hooks""" +import os +import shutil if __name__ == '__main__': """Initialize a git repository based on the configured branch and repo""" @@ -10,4 +12,8 @@ subprocess.call(('git', 'init')) subprocess.call(('git', 'checkout', '-b', branch)) subprocess.call(('git', 'remote', 'add', remote, url)) - subprocess.call(('mv', 'pre-commit', '.git/hooks/')) + if os.name == 'nt': + shutil.move('pre-commit', '.git\\hooks') + else: + subprocess.call(('mv', 'pre-commit', '.git/hooks/')) + diff --git a/{{cookiecutter.repo_name}}/CMakeLists.txt b/{{cookiecutter.repo_name}}/CMakeLists.txt index 4a76ac5..e81aa69 100644 --- a/{{cookiecutter.repo_name}}/CMakeLists.txt +++ b/{{cookiecutter.repo_name}}/CMakeLists.txt @@ -1,28 +1,125 @@ + # {{ cookiecutter.project_name }} # {{ cookiecutter.description }} # Copyright {{ cookiecutter.year }}, {{ cookiecutter.author }} -cmake_minimum_required(VERSION 3.1) -project({{ cookiecutter.project_name }} C CXX) +cmake_minimum_required(VERSION 3.8 FATAL_ERROR) + +# Can be useful for vim? +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Project +project({{cookiecutter.project_name}} C CXX) +project({{cookiecutter.repo_name}} VERSION {{ cookiecutter.version }} LANGUAGES CXX) set(PROJECT_DESCRIPTION "{{ cookiecutter.description }}") set(PROJECT_AUTHOR "{{ cookiecutter.author }}") set(PROJECT_URL "https://github.com/{{ cookiecutter.username }}/{{ cookiecutter.repo_name }}") -set(PROJECT_VERSION_MAJOR "{{ cookiecutter.version.split('.')[0] }}") -set(PROJECT_VERSION_MINOR "{{ cookiecutter.version.split('.')[1] }}") -set(PROJECT_VERSION_PATCH "{{ cookiecutter.version.split('.')[2] }}") # Build flags -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -march=native") -{% if cookiecutter.pedantic -%} -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror -pedantic") -{%- endif %} +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug) +endif() + +set (CMAKE_CXX_STANDARD 14) + +if (MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3 /utf-8") +else () + set(CMAKE_CXX_FLAGS_DEBUG "-g -O0") + set(CMAKE_CXX_FLAGS_RELEASE "-O3") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -march=native") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror -pedantic") +endif () + +# Tell cmake to output binaries here: +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib) # Static link libraries +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin) # Executables +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib) # Dynamic link libraries + +include(pmm.cmake) + +# In the case of a Microsoft system, all packages should be installed by VCPKG, for UNIX systems, the user has to install the required packages using their system package manager. +# In Linux, when a broken VCPKG package is installed by VCPKG, it should be manually uninstalled. Otherwise build errors might occur. + +set(BUILD_SHARED_LIBS=false) +if (MSVC) + set(PMM_PACKAGES fmt openblas lapack armadillo) +else () + set(PMM_PACKAGES fmt openblas lapack armadillo) + #sudo apt-get install libarmadillo-dev (VCPKG version is broken) +endif () +pmm( + # Use vcpkg + VCPKG + # Specify vcpkg branch, tag, or revision + REVISION master + # Space seperated list of required vcpkg packages + REQUIRES ${PMM_PACKAGES} +) + +# Find required packages for project, these are the cmake targets reported by vcpkg. +find_package(fmt CONFIG REQUIRED) +find_package(LAPACK) +find_package(OpenBLAS CONFIG REQUIRED) +find_package(Armadillo REQUIRED) + +#include_directories(${ARMADILLO_INCLUDE_DIRS}) -# --------------------------------------------------------- -# Features -# --------------------------------------------------------- add_subdirectory(external) +# Tell cmake where to look for *.h files. +include_directories(${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/src/{{cookiecutter.project_name}} ) +# Outsource the actual processing to folders. add_subdirectory(src) -enable_testing() -add_subdirectory(test) +# Must be in the top-most CMakeLists.txt file. +# testing +enable_testing () +option ({{ cookiecutter.project_name|upper }}_BUILD_TESTS "Build test programs" ON) +if ({{ cookiecutter.project_name|upper }}_BUILD_TESTS) + add_subdirectory (test) +endif () + + + add_subdirectory(doc) + +# Prepare RPATH +if (NOT MSVC) + set(CMAKE_INSTALL_PREFIX "$ENV{HOME}/.local") #We don't want to install to the default /usr/local. +endif () +file(RELATIVE_PATH _rel ${CMAKE_INSTALL_PREFIX}/${INSTALL_BINDIR} ${CMAKE_INSTALL_PREFIX}) +if(APPLE) + set(_rpath "@loader_path/${_rel}") +else() + set(_rpath "\$ORIGIN/${_rel}") +endif() +file(TO_NATIVE_PATH "${_rpath}/${INSTALL_LIBDIR}" message_RPATH) + +set_target_properties({{cookiecutter.project_name}} + PROPERTIES + MACOSX_RPATH ON + SKIP_BUILD_RPATH OFF + BUILD_WITH_INSTALL_RPATH OFF + INSTALL_RPATH "${message_RPATH}" + INSTALL_RPATH_USE_LINK_PATH ON + ) + +# Here you can select the binaries and libraries that have to be installed to the system. +message(STATUS "Project will be installed to ${CMAKE_INSTALL_PREFIX}, admin rights may be required to make install") +install( + TARGETS + {{cookiecutter.repo_name}}Utils + {{cookiecutter.repo_name}} + ARCHIVE + DESTINATION ${INSTALL_LIBDIR} + COMPONENT ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} + RUNTIME + DESTINATION ${INSTALL_BINDIR} + COMPONENT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} + LIBRARY + DESTINATION ${INSTALL_LIBDIR} + COMPONENT ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} + PUBLIC_HEADER + DESTINATION ${INSTALL_INCLUDEDIR}/tythUtils + COMPONENT dev + ) diff --git a/{{cookiecutter.repo_name}}/README.md b/{{cookiecutter.repo_name}}/README.md deleted file mode 100644 index e604702..0000000 --- a/{{cookiecutter.repo_name}}/README.md +++ /dev/null @@ -1,7 +0,0 @@ -{{ cookiecutter.project_name }} -{{ '=' * cookiecutter.project_name | length }} -{{ cookiecutter.description }} - -[![Build Status] - (https://travis-ci.org/{{ cookiecutter.username }}/{{ cookiecutter.repo_name }}.svg)] - (https://travis-ci.org/{{ cookiecutter.username }}/{{ cookiecutter.repo_name }}) diff --git a/{{cookiecutter.repo_name}}/external/gtest.cmake b/{{cookiecutter.repo_name}}/external/gtest.cmake index e11ca1e..1ea0d8a 100644 --- a/{{cookiecutter.repo_name}}/external/gtest.cmake +++ b/{{cookiecutter.repo_name}}/external/gtest.cmake @@ -1,14 +1,14 @@ # GoogleTest (gtest) # A unit testing library for C/C++ -# Creates a libgtest target packaged with the required include driectories +# Creates a libgtest target packaged with the required include directories find_package(Threads REQUIRED) include(ExternalProject) # Fetch GoogleTest remotely ExternalProject_Add( gtest - URL https://googletest.googlecode.com/files/gtest-1.7.0.zip - URL_MD5 2d6ec8ccdf5c46b05ba54a9fd1d130d7 + URL https://github.com/google/googletest/archive/release-1.10.0.zip + URL_MD5 82358affdd7ab94854c8ee73a180fc53 PREFIX ${CMAKE_CURRENT_BINARY_DIR} # Disable INSTALL INSTALL_COMMAND "" @@ -18,9 +18,9 @@ add_dependencies(libgtest gtest) # Setup the build tree and package the target ExternalProject_Get_Property(gtest SOURCE_DIR BINARY_DIR) -file(MAKE_DIRECTORY ${SOURCE_DIR}/include) +file(MAKE_DIRECTORY ${SOURCE_DIR}/googletest/include) set_target_properties(libgtest PROPERTIES - "IMPORTED_LOCATION" "${BINARY_DIR}/libgtest.a" + "IMPORTED_LOCATION" "${BINARY_DIR}/lib/libgtest.a" "IMPORTED_LINK_INTERFACE_LIBRARIES" "${CMAKE_THREAD_LIBS_INIT}" - "INTERFACE_INCLUDE_DIRECTORIES" "${SOURCE_DIR}/include" + "INTERFACE_INCLUDE_DIRECTORIES" "${SOURCE_DIR}/googletest/include" ) diff --git a/{{cookiecutter.repo_name}}/pmm.cmake b/{{cookiecutter.repo_name}}/pmm.cmake new file mode 100644 index 0000000..5e3ee65 --- /dev/null +++ b/{{cookiecutter.repo_name}}/pmm.cmake @@ -0,0 +1,84 @@ +## MIT License +## +## Copyright (c) 2019 vector-of-bool +## +## Permission is hereby granted, free of charge, to any person obtaining a copy +## of this software and associated documentation files (the "Software"), to deal +## in the Software without restriction, including without limitation the rights +## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +## copies of the Software, and to permit persons to whom the Software is +## furnished to do so, subject to the following conditions: +## +## The above copyright notice and this permission notice shall be included in all +## copies or substantial portions of the Software. +## +## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +## SOFTWARE. + +# Bump this version to change what PMM version is downloaded +set(PMM_VERSION_INIT 1.5.0) + +# Helpful macro to set a variable if it isn't already set +macro(_pmm_set_if_undef varname) + if(NOT DEFINED "${varname}") + set("${varname}" "${ARGN}") + endif() +endmacro() + +## Variables used by this script +# The version: +_pmm_set_if_undef(PMM_VERSION ${PMM_VERSION_INIT}) +# The base URL we download PMM from: +_pmm_set_if_undef(PMM_URL_BASE "https://vector-of-bool.github.io/pmm") +# The real URL we download from (Based on the version) +_pmm_set_if_undef(PMM_URL "${PMM_URL_BASE}/${PMM_VERSION}") +# The directory where we store our downloaded files +_pmm_set_if_undef(PMM_DIR_BASE "${CMAKE_BINARY_DIR}/_pmm") +_pmm_set_if_undef(PMM_DIR "${PMM_DIR_BASE}/${PMM_VERSION}") +# The location of the current file +_pmm_set_if_undef(PMM_MODULE "${CMAKE_CURRENT_LIST_FILE}") + +# The file that we first download +set(_PMM_ENTRY_FILE "${PMM_DIR}/entry.cmake") + +# Guard against multiple processes trying to use the PMM dir simultaneously +file(LOCK "${PMM_DIR}/_init-pmm" + GUARD PROCESS + TIMEOUT 10 + RESULT_VARIABLE _lock_res + ) +if(NOT _lock_res STREQUAL "0") + message(WARNING "PMM entry didn't lock the directory ${PMM_DIR} successfully (${_lock_res}). We'll continue as best we can.") + set(_pmm_init_did_lock FALSE) +else() + set(_pmm_init_did_lock TRUE) +endif() + +if(NOT EXISTS "${_PMM_ENTRY_FILE}" OR PMM_ALWAYS_DOWNLOAD) + file( + DOWNLOAD "${PMM_URL}/entry.cmake" + "${_PMM_ENTRY_FILE}.tmp" + STATUS pair + ) + list(GET pair 0 rc) + list(GET pair 1 msg) + if (rc) + message(FATAL_ERROR "Failed to download PMM entry file: ${msg}") + endif () + file(RENAME "${_PMM_ENTRY_FILE}.tmp" "${_PMM_ENTRY_FILE}") +endif() + +# ^^^ DO NOT CHANGE THIS LINE vvv +set(_PMM_BOOTSTRAP_VERSION 3) +# ^^^ DO NOT CHANGE THIS LINE ^^^ + +include("${_PMM_ENTRY_FILE}") + +if(_pmm_init_did_lock) + file(LOCK "${PMM_DIR}/_init-pmm" RELEASE) +endif() diff --git a/{{cookiecutter.repo_name}}/src/CMakeLists.txt b/{{cookiecutter.repo_name}}/src/CMakeLists.txt index 3e2e0ba..72c615e 100644 --- a/{{cookiecutter.repo_name}}/src/CMakeLists.txt +++ b/{{cookiecutter.repo_name}}/src/CMakeLists.txt @@ -1,10 +1 @@ -# Target: {{ cookiecutter.project_name }}_lib -# Type: Library -file(GLOB SOURCE *.cpp) -add_library({{ cookiecutter.project_name }}_lib SHARED ${SOURCE}) -set_target_properties({{ cookiecutter.project_name }}_lib PROPERTIES - "OUTPUT_NAME" "{{ cookiecutter.project_name }}" - "INCLUDE_DIRECTORIES" "${CMAKE_CURRENT_SOURCE_DIR}/include" -) -install(TARGETS {{ cookiecutter.project_name }}_lib EXPORT {{ cookiecutter.project_name }} DESTINATION ${CMAKE_SOURCE_DIR}/lib) -export(TARGETS {{ cookiecutter.project_name }}_lib FILE ${CMAKE_BINARY_DIR}/{{ cookiecutter.project_name }}-config.cmake) +add_subdirectory({{cookiecutter.repo_name}}) diff --git a/{{cookiecutter.repo_name}}/src/feature.cpp b/{{cookiecutter.repo_name}}/src/feature.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/{{cookiecutter.repo_name}}/src/{{cookiecutter.repo_name}}/CMakeLists.txt b/{{cookiecutter.repo_name}}/src/{{cookiecutter.repo_name}}/CMakeLists.txt new file mode 100644 index 0000000..d419381 --- /dev/null +++ b/{{cookiecutter.repo_name}}/src/{{cookiecutter.repo_name}}/CMakeLists.txt @@ -0,0 +1,10 @@ +add_library({{cookiecutter.repo_name}}Utils STATIC {{cookiecutter.repo_name}}Utils.cpp) +message(STATUS armalibs ${ARMADILLO_LIBRARIES}) +add_executable({{cookiecutter.repo_name}} {{cookiecutter.repo_name}}.cpp) +set(DIRS fmt::fmt) +list(APPEND DIRS ${ARMADILLO_LIBRARIES}) +#list(APPEND DIRS -LC:/external/armadillo-10.1.0/build/Debug/armadillo.lib) +list(APPEND DIRS {{cookiecutter.repo_name}}Utils) +list(APPEND DIRS LAPACK::LAPACK) +list(APPEND DIRS OpenBLAS::OpenBLAS) +target_link_libraries({{cookiecutter.repo_name}} ${DIRS}) diff --git a/{{cookiecutter.repo_name}}/src/{{cookiecutter.repo_name}}/{{ cookiecutter.project_name }}.cpp b/{{cookiecutter.repo_name}}/src/{{cookiecutter.repo_name}}/{{ cookiecutter.project_name }}.cpp new file mode 100644 index 0000000..0bf8896 --- /dev/null +++ b/{{cookiecutter.repo_name}}/src/{{cookiecutter.repo_name}}/{{ cookiecutter.project_name }}.cpp @@ -0,0 +1,22 @@ +#include +#include <{{ cookiecutter.project_name }}Utils.h> +#include //packagemanager downloaded library. +#define ARMA_DONT_USE_WRAPPER +#define ARMA_DONT_USE_BLAS +#define ARMA_DONT_USE_LAPACK +//#define ARMA_USE_BLAS +#include //system wide installed package. + +using namespace std; +using namespace arma; + +int main() +{ + auto message = fmt::format("The answer is {}", sum(1,2)); + std::cout << message <(3,3); + mat b = randu(3,3); + std::cout << v*b << std::endl; +return EXIT_SUCCESS; +} + diff --git a/{{cookiecutter.repo_name}}/src/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}Utils.cpp b/{{cookiecutter.repo_name}}/src/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}Utils.cpp new file mode 100644 index 0000000..af0306b --- /dev/null +++ b/{{cookiecutter.repo_name}}/src/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}Utils.cpp @@ -0,0 +1,6 @@ +#include "./{{cookiecutter.repo_name}}Utils.h" +int sum(int a, int b) +{ + std::cout << "input a: " << a << std::endl << "input b: " << b << std::endl; + return a + b; +} diff --git a/{{cookiecutter.repo_name}}/src/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}Utils.h b/{{cookiecutter.repo_name}}/src/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}Utils.h new file mode 100644 index 0000000..d1050ee --- /dev/null +++ b/{{cookiecutter.repo_name}}/src/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}Utils.h @@ -0,0 +1,3 @@ +#pragma once +#include +int sum(int, int); diff --git a/{{cookiecutter.repo_name}}/test/CMakeLists.txt b/{{cookiecutter.repo_name}}/test/CMakeLists.txt index fb41ad2..635aedc 100644 --- a/{{cookiecutter.repo_name}}/test/CMakeLists.txt +++ b/{{cookiecutter.repo_name}}/test/CMakeLists.txt @@ -1,8 +1,7 @@ # Tests for {{ cookiecutter.project_name }} file(GLOB SOURCES *.cpp) add_executable(${PROJECT_NAME}_test ${SOURCES}) -target_link_libraries(${PROJECT_NAME}_test ${PROJECT_NAME}_lib libgtest) - +target_link_libraries(${PROJECT_NAME}_test {{cookiecutter.repo_name}}Utils libgtest) # Register the tests add_test(NAME ${PROJECT_NAME}_test - COMMAND ${PROJECT_NAME}_test) + COMMAND ${PROJECT_NAME}_test) diff --git a/{{cookiecutter.repo_name}}/test/test_{{cookiecutter.project_name}}.cpp b/{{cookiecutter.repo_name}}/test/test_{{cookiecutter.project_name}}.cpp deleted file mode 100644 index ac4d862..0000000 --- a/{{cookiecutter.repo_name}}/test/test_{{cookiecutter.project_name}}.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include - -TEST({{ cookiecutter.project_name | capitalize }}Test, Contrived) { - EXPECT_TRUE(true); -} diff --git a/{{cookiecutter.repo_name}}/test/test{{cookiecutter.repo_name}}_utils.cpp b/{{cookiecutter.repo_name}}/test/test{{cookiecutter.repo_name}}_utils.cpp new file mode 100644 index 0000000..25f9bf0 --- /dev/null +++ b/{{cookiecutter.repo_name}}/test/test{{cookiecutter.repo_name}}_utils.cpp @@ -0,0 +1,6 @@ +#include +#include <{{cookiecutter.repo_name}}Utils.h> + +TEST(testSum, OutputTest) { + EXPECT_EQ(4, sum(1,3)); +}