diff --git a/.ci/conda/bld.bat b/.ci/conda/bld.bat index fdcf6a9..456c2f9 100644 --- a/.ci/conda/bld.bat +++ b/.ci/conda/bld.bat @@ -2,11 +2,10 @@ mkdir build cd build cmake -G "%GENERATOR%" ^ - -DCMAKE_BUILD_TYPE="Release" ^ + -DCMAKE_BUILD_TYPE="Release" ^ -DCMAKE_PREFIX_PATH:FILEPATH="%PREFIX%" ^ -DCMAKE_INSTALL_PREFIX:FILEPATH="%LIBRARY_PREFIX%" ^ - -DBUILD_TESTING=OFF ^ - -DUSE_ISPC=OFF ^ + -DTETWILD_WITH_ISPC=OFF ^ .. msbuild /m /p:Configuration=Release INSTALL.vcxproj diff --git a/.ci/conda/build.sh b/.ci/conda/build.sh index a9af1f1..91eb747 100644 --- a/.ci/conda/build.sh +++ b/.ci/conda/build.sh @@ -3,10 +3,9 @@ mkdir build cd build cmake -G "Ninja" \ - -DCMAKE_BUILD_TYPE="Release" \ + -DCMAKE_BUILD_TYPE="Release" \ -DCMAKE_INSTALL_PREFIX:FILEPATH=$HOME/.local/ \ - -DBUILD_TESTING=OFF \ - -DUSE_ISPC=OFF \ + -DTETWILD_WITH_ISPC=OFF \ .. ninja install diff --git a/.ci/conda/meta.yaml b/.ci/conda/meta.yaml index 66a284b..10c7e08 100644 --- a/.ci/conda/meta.yaml +++ b/.ci/conda/meta.yaml @@ -15,13 +15,13 @@ requirements: - vc 14 # [win] - cmake - ninja - - cgal + - boost-cpp run: - vc 14 # [win] - - cgal + - boost-cpp about: home: https://github.com/Yixin-Hu/TetWild license: MPLv2.0 - summary: Robust Tetrahedral Meshing in the Wild \ No newline at end of file + summary: Robust Tetrahedral Meshing in the Wild diff --git a/.gitignore b/.gitignore index ba969e9..b6264e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Extern /extern/.cache /extern/spdlog +/extern/geogram +/extern/libigl +/extern/cli11 # Build src/ispc/energy.h diff --git a/.gitmodules b/.gitmodules index 473acfe..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +0,0 @@ -[submodule "extern/libigl"] - path = extern/libigl - url = https://github.com/libigl/libigl -[submodule "extern/geogram"] - path = extern/geogram - url = https://github.com/alicevision/geogram diff --git a/.travis.yml b/.travis.yml index cc5e3bc..c85ed80 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ addons: - ubuntu-toolchain-r-test packages: - g++-4.9 + - libmpfr-dev - libboost1.54-all-dev - libxi-dev @@ -22,7 +23,6 @@ matrix: osx_image: xcode8.3 before_install: - - git submodule update --init --recursive - if [ "$TRAVIS_OS_NAME" == "linux" ]; then wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; else diff --git a/CMakeLists.txt b/CMakeLists.txt index e36c9cb..7dfc918 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,12 @@ -################################################################################ -# TetWild CMake -################################################################################ cmake_minimum_required(VERSION 3.3) -project(TetWild) +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) ################################################################################ # Options ################################################################################ # tetwild -option(TETWILD_WITH_ISPC "Use ISPC" OFF) +option(TETWILD_WITH_HUNTER "Use Hunter to download and configure Boost" OFF) +option(TETWILD_WITH_ISPC "Use ISPC" OFF) # libigl library option(LIBIGL_USE_STATIC_LIBRARY "Use libigl as static library" OFF) option(LIBIGL_WITH_ANTTWEAKBAR "Use AntTweakBar" OFF) @@ -32,6 +30,21 @@ option(LIBIGL_WITH_VIEWER "Use OpenGL viewer" OFF) #geogram option(GEOGRAM_WITH_TRIANGLE "Use Triangle" OFF) +if(TETWILD_WITH_HUNTER) + # Needs to be set before the main project... argh =/ + include(HunterGate) + HunterGate( + URL "https://github.com/ruslo/hunter/archive/v0.23.25.tar.gz" + SHA1 "cb75cce9a3a8d552e70e7118f3203eb4ac05c201" + ) +endif() + +################################################################################ +# Project name +################################################################################ + +project(TetWild) + ################################################################################ # Settings ################################################################################ @@ -41,7 +54,6 @@ if(NOT CMAKE_BUILD_TYPE) endif() set(TETWILD_EXTERNAL "${CMAKE_CURRENT_SOURCE_DIR}/extern") -list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # Color output include(UseColors) @@ -58,30 +70,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) ################################################################################ # 3rd party libraries ################################################################################ - -# download external dependencies -include(TetWildDownloadExternal) - -# spdlog -if(NOT TARGET spdlog::spdlog) - tetwild_download_spdlog() - add_subdirectory(${TETWILD_EXTERNAL}/spdlog) -endif() - -# cgal -find_package(CGAL REQUIRED) - -# libigl -find_package(LIBIGL REQUIRED) - -# geogram -include(geogram) - -# pymesh loaders -add_subdirectory(${TETWILD_EXTERNAL}/pymesh) - -# CL11 -add_subdirectory(${TETWILD_EXTERNAL}/CLI) +include(TetWildDependencies) ################################################################################ # TetWild @@ -134,9 +123,10 @@ target_link_libraries(libTetWild PUBLIC geogram igl::core - igl::cgal pymesh::pymesh spdlog::spdlog + PRIVATE + igl::cgal ) set_target_properties(libTetWild PROPERTIES OUTPUT_NAME "tetwild") @@ -150,10 +140,32 @@ endif() # Building executable add_executable(TetWild src/main.cpp) target_link_libraries(TetWild - libTetWild - CLI11::CLI11 + libTetWild + CLI11::CLI11 + igl::cgal ) target_include_directories(TetWild PRIVATE src) +igl_copy_cgal_dll(TetWild) # Install install(TARGETS TetWild RUNTIME DESTINATION bin) + +################################################################################ +# Folders for Visual Studio/XCode IDEs +################################################################################ + +# geogram +set_target_properties(geogram PROPERTIES FOLDER extern/geogram) +set_target_properties(geogram_third_party PROPERTIES FOLDER extern/geogram) +set_target_properties(uninstall PROPERTIES FOLDER extern/geogram) +# spdlog +set_target_properties(async_bench PROPERTIES FOLDER extern/spdlog) +set_target_properties(bench PROPERTIES FOLDER extern/spdlog) +set_target_properties(example PROPERTIES FOLDER extern/spdlog) +set_target_properties(latency PROPERTIES FOLDER extern/spdlog) +set_target_properties(multisink PROPERTIES FOLDER extern/spdlog) +set_target_properties(spdlog_headers_for_ide PROPERTIES FOLDER extern/spdlog) +set_target_properties(spdlog-utests PROPERTIES FOLDER extern/spdlog) +set_target_properties(multisink PROPERTIES FOLDER extern/spdlog) +# pymesh +set_target_properties(pymesh_tiny PROPERTIES FOLDER extern/pymesh) diff --git a/README.md b/README.md index 52e0dd0..3dccdcf 100644 --- a/README.md +++ b/README.md @@ -27,21 +27,15 @@ Here is pre-generated tetmeshes and the extracted surface meshes for research-pu ## Installation -Our code was originally developed on MacOS and has been tested on Lunix and Windows. +Our code was originally developed on MacOS and has been tested on Linux and Windows. - Clone the repository into your local machine: ```bash -git clone https://github.com/Yixin-Hu/TetWild --recursive +git clone https://github.com/Yixin-Hu/TetWild ``` -- For Windows users, a patch needs to be applied to the geogram library to avoid a link error: - -```bash -git apply geogram_win.patch -``` - -- Compile the code using cmake (default in release mode): +- Compile the code using cmake (default in Release mode): You need to install [CGAL](https://doc.cgal.org/latest/Manual/installation.html) before compiling the code. @@ -53,6 +47,8 @@ cmake .. make ``` +💡 If you do not have Boost installed (which is need for CGAL), you can enable the cmake option `-DTETWILD_WITH_HUNTER=ON`. This will let CMake use [Hunter](https://github.com/ruslo/hunter) to download and configure Boost automatically. Other options include installing Boost via Conda, compile from source, etc. + 💡 If you find `Could not find Matlab` or `Could not find Mosek` in the output of cmake, it does not matter since they are not used. 💡 We provide users an option to use [ISPC](https://ispc.github.io/index.html) for computing energy parallelly. It reduces the timimg for computing energy to 50% of the original, but it could result in more optimization iterations and more overall running time. According to our experiment on 1000 models, it reduces the overall running time by 4% in average. If you want to use ISPC, please [install it first](https://ispc.github.io/ispc.html#installing-ispc) and then turn on the flag `GTET_ISPC` in `CMakeLists.txt`. diff --git a/appveyor.yml b/appveyor.yml index 257f99a..a1a0974 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,8 +23,6 @@ install: - cmd: conda.exe config --remove channels defaults - cmd: conda.exe config --add channels defaults - cmd: conda.exe config --add channels conda-forge - - cmd: git submodule update --init --recursive - - cmd: git apply geogram_win.patch build: off diff --git a/cmake/HunterGate.cmake b/cmake/HunterGate.cmake new file mode 100644 index 0000000..887557a --- /dev/null +++ b/cmake/HunterGate.cmake @@ -0,0 +1,540 @@ +# Copyright (c) 2013-2018, Ruslan Baratov +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# This is a gate file to Hunter package manager. +# Include this file using `include` command and add package you need, example: +# +# cmake_minimum_required(VERSION 3.2) +# +# include("cmake/HunterGate.cmake") +# HunterGate( +# URL "https://github.com/path/to/hunter/archive.tar.gz" +# SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d" +# ) +# +# project(MyProject) +# +# hunter_add_package(Foo) +# hunter_add_package(Boo COMPONENTS Bar Baz) +# +# Projects: +# * https://github.com/hunter-packages/gate/ +# * https://github.com/ruslo/hunter + +option(HUNTER_ENABLED "Enable Hunter package manager support" ON) + +if(HUNTER_ENABLED) + if(CMAKE_VERSION VERSION_LESS "3.2") + message( + FATAL_ERROR + "At least CMake version 3.2 required for Hunter dependency management." + " Update CMake or set HUNTER_ENABLED to OFF." + ) + endif() +endif() + +include(CMakeParseArguments) # cmake_parse_arguments + +option(HUNTER_STATUS_PRINT "Print working status" ON) +option(HUNTER_STATUS_DEBUG "Print a lot info" OFF) +option(HUNTER_TLS_VERIFY "Enable/disable TLS certificate checking on downloads" ON) + +set(HUNTER_WIKI "https://github.com/ruslo/hunter/wiki") + +function(hunter_gate_status_print) + if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG) + foreach(print_message ${ARGV}) + message(STATUS "[hunter] ${print_message}") + endforeach() + endif() +endfunction() + +function(hunter_gate_status_debug) + if(HUNTER_STATUS_DEBUG) + foreach(print_message ${ARGV}) + string(TIMESTAMP timestamp) + message(STATUS "[hunter *** DEBUG *** ${timestamp}] ${print_message}") + endforeach() + endif() +endfunction() + +function(hunter_gate_wiki wiki_page) + message("------------------------------ WIKI -------------------------------") + message(" ${HUNTER_WIKI}/${wiki_page}") + message("-------------------------------------------------------------------") + message("") + message(FATAL_ERROR "") +endfunction() + +function(hunter_gate_internal_error) + message("") + foreach(print_message ${ARGV}) + message("[hunter ** INTERNAL **] ${print_message}") + endforeach() + message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") + message("") + hunter_gate_wiki("error.internal") +endfunction() + +function(hunter_gate_fatal_error) + cmake_parse_arguments(hunter "" "WIKI" "" "${ARGV}") + string(COMPARE EQUAL "${hunter_WIKI}" "" have_no_wiki) + if(have_no_wiki) + hunter_gate_internal_error("Expected wiki") + endif() + message("") + foreach(x ${hunter_UNPARSED_ARGUMENTS}) + message("[hunter ** FATAL ERROR **] ${x}") + endforeach() + message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") + message("") + hunter_gate_wiki("${hunter_WIKI}") +endfunction() + +function(hunter_gate_user_error) + hunter_gate_fatal_error(${ARGV} WIKI "error.incorrect.input.data") +endfunction() + +function(hunter_gate_self root version sha1 result) + string(COMPARE EQUAL "${root}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("root is empty") + endif() + + string(COMPARE EQUAL "${version}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("version is empty") + endif() + + string(COMPARE EQUAL "${sha1}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("sha1 is empty") + endif() + + string(SUBSTRING "${sha1}" 0 7 archive_id) + + if(EXISTS "${root}/cmake/Hunter") + set(hunter_self "${root}") + else() + set( + hunter_self + "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked" + ) + endif() + + set("${result}" "${hunter_self}" PARENT_SCOPE) +endfunction() + +# Set HUNTER_GATE_ROOT cmake variable to suitable value. +function(hunter_gate_detect_root) + # Check CMake variable + string(COMPARE NOTEQUAL "${HUNTER_ROOT}" "" not_empty) + if(not_empty) + set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE) + hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable") + return() + endif() + + # Check environment variable + string(COMPARE NOTEQUAL "$ENV{HUNTER_ROOT}" "" not_empty) + if(not_empty) + set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE) + hunter_gate_status_debug("HUNTER_ROOT detected by environment variable") + return() + endif() + + # Check HOME environment variable + string(COMPARE NOTEQUAL "$ENV{HOME}" "" result) + if(result) + set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE) + hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable") + return() + endif() + + # Check SYSTEMDRIVE and USERPROFILE environment variable (windows only) + if(WIN32) + string(COMPARE NOTEQUAL "$ENV{SYSTEMDRIVE}" "" result) + if(result) + set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE) + hunter_gate_status_debug( + "HUNTER_ROOT set using SYSTEMDRIVE environment variable" + ) + return() + endif() + + string(COMPARE NOTEQUAL "$ENV{USERPROFILE}" "" result) + if(result) + set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE) + hunter_gate_status_debug( + "HUNTER_ROOT set using USERPROFILE environment variable" + ) + return() + endif() + endif() + + hunter_gate_fatal_error( + "Can't detect HUNTER_ROOT" + WIKI "error.detect.hunter.root" + ) +endfunction() + +function(hunter_gate_download dir) + string( + COMPARE + NOTEQUAL + "$ENV{HUNTER_DISABLE_AUTOINSTALL}" + "" + disable_autoinstall + ) + if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL) + hunter_gate_fatal_error( + "Hunter not found in '${dir}'" + "Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'" + "Settings:" + " HUNTER_ROOT: ${HUNTER_GATE_ROOT}" + " HUNTER_SHA1: ${HUNTER_GATE_SHA1}" + WIKI "error.run.install" + ) + endif() + string(COMPARE EQUAL "${dir}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("Empty 'dir' argument") + endif() + + string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("HUNTER_GATE_SHA1 empty") + endif() + + string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("HUNTER_GATE_URL empty") + endif() + + set(done_location "${dir}/DONE") + set(sha1_location "${dir}/SHA1") + + set(build_dir "${dir}/Build") + set(cmakelists "${dir}/CMakeLists.txt") + + hunter_gate_status_debug("Locking directory: ${dir}") + file(LOCK "${dir}" DIRECTORY GUARD FUNCTION) + hunter_gate_status_debug("Lock done") + + if(EXISTS "${done_location}") + # while waiting for lock other instance can do all the job + hunter_gate_status_debug("File '${done_location}' found, skip install") + return() + endif() + + file(REMOVE_RECURSE "${build_dir}") + file(REMOVE_RECURSE "${cmakelists}") + + file(MAKE_DIRECTORY "${build_dir}") # check directory permissions + + # Disabling languages speeds up a little bit, reduces noise in the output + # and avoids path too long windows error + file( + WRITE + "${cmakelists}" + "cmake_minimum_required(VERSION 3.2)\n" + "project(HunterDownload LANGUAGES NONE)\n" + "include(ExternalProject)\n" + "ExternalProject_Add(\n" + " Hunter\n" + " URL\n" + " \"${HUNTER_GATE_URL}\"\n" + " URL_HASH\n" + " SHA1=${HUNTER_GATE_SHA1}\n" + " DOWNLOAD_DIR\n" + " \"${dir}\"\n" + " TLS_VERIFY\n" + " ${HUNTER_TLS_VERIFY}\n" + " SOURCE_DIR\n" + " \"${dir}/Unpacked\"\n" + " CONFIGURE_COMMAND\n" + " \"\"\n" + " BUILD_COMMAND\n" + " \"\"\n" + " INSTALL_COMMAND\n" + " \"\"\n" + ")\n" + ) + + if(HUNTER_STATUS_DEBUG) + set(logging_params "") + else() + set(logging_params OUTPUT_QUIET) + endif() + + hunter_gate_status_debug("Run generate") + + # Need to add toolchain file too. + # Otherwise on Visual Studio + MDD this will fail with error: + # "Could not find an appropriate version of the Windows 10 SDK installed on this machine" + if(EXISTS "${CMAKE_TOOLCHAIN_FILE}") + get_filename_component(absolute_CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE) + set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=${absolute_CMAKE_TOOLCHAIN_FILE}") + else() + # 'toolchain_arg' can't be empty + set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=") + endif() + + string(COMPARE EQUAL "${CMAKE_MAKE_PROGRAM}" "" no_make) + if(no_make) + set(make_arg "") + else() + # Test case: remove Ninja from PATH but set it via CMAKE_MAKE_PROGRAM + set(make_arg "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}") + endif() + + execute_process( + COMMAND + "${CMAKE_COMMAND}" + "-H${dir}" + "-B${build_dir}" + "-G${CMAKE_GENERATOR}" + "${toolchain_arg}" + ${make_arg} + WORKING_DIRECTORY "${dir}" + RESULT_VARIABLE download_result + ${logging_params} + ) + + if(NOT download_result EQUAL 0) + hunter_gate_internal_error( + "Configure project failed." + "To reproduce the error run: ${CMAKE_COMMAND} -H${dir} -B${build_dir} -G${CMAKE_GENERATOR} ${toolchain_arg} ${make_arg}" + "In directory ${dir}" + ) + endif() + + hunter_gate_status_print( + "Initializing Hunter workspace (${HUNTER_GATE_SHA1})" + " ${HUNTER_GATE_URL}" + " -> ${dir}" + ) + execute_process( + COMMAND "${CMAKE_COMMAND}" --build "${build_dir}" + WORKING_DIRECTORY "${dir}" + RESULT_VARIABLE download_result + ${logging_params} + ) + + if(NOT download_result EQUAL 0) + hunter_gate_internal_error("Build project failed") + endif() + + file(REMOVE_RECURSE "${build_dir}") + file(REMOVE_RECURSE "${cmakelists}") + + file(WRITE "${sha1_location}" "${HUNTER_GATE_SHA1}") + file(WRITE "${done_location}" "DONE") + + hunter_gate_status_debug("Finished") +endfunction() + +# Must be a macro so master file 'cmake/Hunter' can +# apply all variables easily just by 'include' command +# (otherwise PARENT_SCOPE magic needed) +macro(HunterGate) + if(HUNTER_GATE_DONE) + # variable HUNTER_GATE_DONE set explicitly for external project + # (see `hunter_download`) + set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) + endif() + + # First HunterGate command will init Hunter, others will be ignored + get_property(_hunter_gate_done GLOBAL PROPERTY HUNTER_GATE_DONE SET) + + if(NOT HUNTER_ENABLED) + # Empty function to avoid error "unknown function" + function(hunter_add_package) + endfunction() + + set( + _hunter_gate_disabled_mode_dir + "${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode" + ) + if(EXISTS "${_hunter_gate_disabled_mode_dir}") + hunter_gate_status_debug( + "Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}" + ) + list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}") + endif() + elseif(_hunter_gate_done) + hunter_gate_status_debug("Secondary HunterGate (use old settings)") + hunter_gate_self( + "${HUNTER_CACHED_ROOT}" + "${HUNTER_VERSION}" + "${HUNTER_SHA1}" + _hunter_self + ) + include("${_hunter_self}/cmake/Hunter") + else() + set(HUNTER_GATE_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}") + + string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name) + if(_have_project_name) + hunter_gate_fatal_error( + "Please set HunterGate *before* 'project' command. " + "Detected project: ${PROJECT_NAME}" + WIKI "error.huntergate.before.project" + ) + endif() + + cmake_parse_arguments( + HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV} + ) + + string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1) + string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url) + string( + COMPARE + NOTEQUAL + "${HUNTER_GATE_UNPARSED_ARGUMENTS}" + "" + _have_unparsed + ) + string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global) + string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath) + + if(_have_unparsed) + hunter_gate_user_error( + "HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}" + ) + endif() + if(_empty_sha1) + hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory") + endif() + if(_empty_url) + hunter_gate_user_error("URL suboption of HunterGate is mandatory") + endif() + if(_have_global) + if(HUNTER_GATE_LOCAL) + hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)") + endif() + if(_have_filepath) + hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)") + endif() + endif() + if(HUNTER_GATE_LOCAL) + if(_have_global) + hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)") + endif() + if(_have_filepath) + hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)") + endif() + endif() + if(_have_filepath) + if(_have_global) + hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)") + endif() + if(HUNTER_GATE_LOCAL) + hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)") + endif() + endif() + + hunter_gate_detect_root() # set HUNTER_GATE_ROOT + + # Beautify path, fix probable problems with windows path slashes + get_filename_component( + HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE + ) + hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}") + if(NOT HUNTER_ALLOW_SPACES_IN_PATH) + string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces) + if(NOT _contain_spaces EQUAL -1) + hunter_gate_fatal_error( + "HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces." + "Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error" + "(Use at your own risk!)" + WIKI "error.spaces.in.hunter.root" + ) + endif() + endif() + + string( + REGEX + MATCH + "[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*" + HUNTER_GATE_VERSION + "${HUNTER_GATE_URL}" + ) + string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty) + if(_is_empty) + set(HUNTER_GATE_VERSION "unknown") + endif() + + hunter_gate_self( + "${HUNTER_GATE_ROOT}" + "${HUNTER_GATE_VERSION}" + "${HUNTER_GATE_SHA1}" + _hunter_self + ) + + set(_master_location "${_hunter_self}/cmake/Hunter") + if(EXISTS "${HUNTER_GATE_ROOT}/cmake/Hunter") + # Hunter downloaded manually (e.g. by 'git clone') + set(_unused "xxxxxxxxxx") + set(HUNTER_GATE_SHA1 "${_unused}") + set(HUNTER_GATE_VERSION "${_unused}") + else() + get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE) + set(_done_location "${_archive_id_location}/DONE") + set(_sha1_location "${_archive_id_location}/SHA1") + + # Check Hunter already downloaded by HunterGate + if(NOT EXISTS "${_done_location}") + hunter_gate_download("${_archive_id_location}") + endif() + + if(NOT EXISTS "${_done_location}") + hunter_gate_internal_error("hunter_gate_download failed") + endif() + + if(NOT EXISTS "${_sha1_location}") + hunter_gate_internal_error("${_sha1_location} not found") + endif() + file(READ "${_sha1_location}" _sha1_value) + string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal) + if(NOT _is_equal) + hunter_gate_internal_error( + "Short SHA1 collision:" + " ${_sha1_value} (from ${_sha1_location})" + " ${HUNTER_GATE_SHA1} (HunterGate)" + ) + endif() + if(NOT EXISTS "${_master_location}") + hunter_gate_user_error( + "Master file not found:" + " ${_master_location}" + "try to update Hunter/HunterGate" + ) + endif() + endif() + include("${_master_location}") + set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) + endif() +endmacro() diff --git a/cmake/TetWildDependencies.cmake b/cmake/TetWildDependencies.cmake new file mode 100644 index 0000000..494d862 --- /dev/null +++ b/cmake/TetWildDependencies.cmake @@ -0,0 +1,42 @@ +################################################################################ +# CMake download helpers +################################################################################ + +# download external dependencies +include(TetWildDownloadExternal) + +################################################################################ +# Required dependencies +################################################################################ + +# Boost +if(TETWILD_WITH_HUNTER) + hunter_add_package(Boost COMPONENTS thread system) +endif() + +# spdlog +if(NOT TARGET spdlog::spdlog) + tetwild_download_spdlog() + add_subdirectory(${TETWILD_EXTERNAL}/spdlog) +endif() + +# libigl +if(NOT TARGET igl::core) + tetwild_download_libigl() + find_package(LIBIGL REQUIRED) +endif() + +# geogram +if(NOT TARGET geogram) + tetwild_download_geogram() + include(geogram) +endif() + +# pymesh loaders +add_subdirectory(${TETWILD_EXTERNAL}/pymesh) + +# CL11 +if(NOT TARGET CLI11::CLI11) + tetwild_download_cli11() + add_subdirectory(${TETWILD_EXTERNAL}/cli11) +endif() diff --git a/cmake/TetWildDownloadExternal.cmake b/cmake/TetWildDownloadExternal.cmake index 6e8cdf3..d52ab60 100644 --- a/cmake/TetWildDownloadExternal.cmake +++ b/cmake/TetWildDownloadExternal.cmake @@ -13,6 +13,22 @@ endfunction() ################################################################################ +## libigl +function(tetwild_download_libigl) + tetwild_download_project(libigl + GIT_REPOSITORY https://github.com/jdumas/libigl.git + GIT_TAG 5cfc34dd1680cfe5a6e545ed9f89ae5cf2d644b7 + ) +endfunction() + +## geogram +function(tetwild_download_geogram) + tetwild_download_project(geogram + GIT_REPOSITORY https://github.com/alicevision/geogram.git + GIT_TAG v1.6.6 + ) +endfunction() + ## spdlog function(tetwild_download_spdlog) tetwild_download_project(spdlog @@ -20,3 +36,11 @@ function(tetwild_download_spdlog) GIT_TAG v1.1.0 ) endfunction() + +## CLI11 +function(tetwild_download_cli11) + tetwild_download_project(cli11 + URL https://github.com/CLIUtils/CLI11/archive/v1.6.1.tar.gz + URL_MD5 48ef97262adb0b47a2f0a7edbda6e2aa + ) +endfunction() diff --git a/cmake/geogram.cmake b/cmake/geogram.cmake index d645ecc..ee18db5 100644 --- a/cmake/geogram.cmake +++ b/cmake/geogram.cmake @@ -32,7 +32,7 @@ set(GEOGRAM_ROOT ${GEOGRAM_SOURCE_INCLUDE_DIR}/../..) if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") set(VORPALINE_ARCH_64 TRUE CACHE BOOL "" FORCE) - set(VORPALINE_PLATFORM Win-vs-dynamic-generic CACHE STRING "" FORCE) + set(VORPALINE_PLATFORM Win-vs-generic CACHE STRING "" FORCE) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") set(VORPALINE_PLATFORM Linux64-gcc-dynamic CACHE STRING "" FORCE) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") diff --git a/extern/CLI/CLI11.hpp b/extern/CLI/CLI11.hpp deleted file mode 100644 index 767142d..0000000 --- a/extern/CLI/CLI11.hpp +++ /dev/null @@ -1,2914 +0,0 @@ -#pragma once - -// Distributed under the 3-Clause BSD License. See accompanying -// file LICENSE or https://github.com/CLIUtils/CLI11 for details. - -// This file was generated using MakeSingleHeader.py in CLI11/scripts -// from: v1.3.0 - -// This has the complete CLI library in one file. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// From CLI/Version.hpp - -namespace CLI { - -// Note that all code in CLI11 must be in a namespace, even if it just a define. - -#define CLI11_VERSION_MAJOR 1 -#define CLI11_VERSION_MINOR 3 -#define CLI11_VERSION_PATCH 0 -#define CLI11_VERSION "1.3.0" - -} // namespace CLI - -// From CLI/StringTools.hpp - -namespace CLI { -namespace detail { - -// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c -/// Split a string by a delim -inline std::vector split(const std::string &s, char delim) { - std::vector elems; - // Check to see if empty string, give consistent result - if(s.empty()) - elems.emplace_back(""); - else { - std::stringstream ss; - ss.str(s); - std::string item; - while(std::getline(ss, item, delim)) { - elems.push_back(item); - } - } - return elems; -} - -/// Simple function to join a string -template std::string join(const T &v, std::string delim = ",") { - std::ostringstream s; - size_t start = 0; - for(const auto &i : v) { - if(start++ > 0) - s << delim; - s << i; - } - return s.str(); -} - -/// Join a string in reverse order -template std::string rjoin(const T &v, std::string delim = ",") { - std::ostringstream s; - for(size_t start = 0; start < v.size(); start++) { - if(start > 0) - s << delim; - s << v[v.size() - start - 1]; - } - return s.str(); -} - -// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string - -/// Trim whitespace from left of string -inline std::string <rim(std::string &str) { - auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); - str.erase(str.begin(), it); - return str; -} - -/// Trim anything from left of string -inline std::string <rim(std::string &str, const std::string &filter) { - auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); - str.erase(str.begin(), it); - return str; -} - -/// Trim whitespace from right of string -inline std::string &rtrim(std::string &str) { - auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); - str.erase(it.base(), str.end()); - return str; -} - -/// Trim anything from right of string -inline std::string &rtrim(std::string &str, const std::string &filter) { - auto it = - std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); - str.erase(it.base(), str.end()); - return str; -} - -/// Trim whitespace from string -inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } - -/// Trim anything from string -inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } - -/// Make a copy of the string and then trim it -inline std::string trim_copy(const std::string &str) { - std::string s = str; - return trim(s); -} - -/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) -inline std::string trim_copy(const std::string &str, const std::string &filter) { - std::string s = str; - return trim(s, filter); -} -/// Print a two part "help" string -inline void format_help(std::stringstream &out, std::string name, std::string description, size_t wid) { - name = " " + name; - out << std::setw(static_cast(wid)) << std::left << name; - if(!description.empty()) { - if(name.length() >= wid) - out << std::endl << std::setw(static_cast(wid)) << ""; - out << description; - } - out << std::endl; -} - -/// Verify the first character of an option -template bool valid_first_char(T c) { return std::isalpha(c, std::locale()) || c == '_'; } - -/// Verify following characters of an option -template bool valid_later_char(T c) { - return std::isalnum(c, std::locale()) || c == '_' || c == '.' || c == '-'; -} - -/// Verify an option name -inline bool valid_name_string(const std::string &str) { - if(str.empty() || !valid_first_char(str[0])) - return false; - for(auto c : str.substr(1)) - if(!valid_later_char(c)) - return false; - return true; -} - -/// Return a lower case version of a string -inline std::string to_lower(std::string str) { - std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { - return std::tolower(x, std::locale()); - }); - return str; -} - -/// Split a string '"one two" "three"' into 'one two', 'three' -inline std::vector split_up(std::string str) { - - std::vector delims = {'\'', '\"'}; - auto find_ws = [](char ch) { return std::isspace(ch, std::locale()); }; - trim(str); - - std::vector output; - - while(!str.empty()) { - if(str[0] == '\'') { - auto end = str.find('\'', 1); - if(end != std::string::npos) { - output.push_back(str.substr(1, end - 1)); - str = str.substr(end + 1); - } else { - output.push_back(str.substr(1)); - str = ""; - } - } else if(str[0] == '\"') { - auto end = str.find('\"', 1); - if(end != std::string::npos) { - output.push_back(str.substr(1, end - 1)); - str = str.substr(end + 1); - } else { - output.push_back(str.substr(1)); - str = ""; - } - - } else { - auto it = std::find_if(std::begin(str), std::end(str), find_ws); - if(it != std::end(str)) { - std::string value = std::string(str.begin(), it); - output.push_back(value); - str = std::string(it, str.end()); - } else { - output.push_back(str); - str = ""; - } - } - trim(str); - } - - return output; -} - -} // namespace detail -} // namespace CLI - -// From CLI/Error.hpp - -namespace CLI { - -// Use one of these on all error classes -#define CLI11_ERROR_DEF(parent, name) \ - protected: \ - name(std::string name, std::string msg, int exit_code) : parent(std::move(name), std::move(msg), exit_code) {} \ - name(std::string name, std::string msg, ExitCodes exit_code) \ - : parent(std::move(name), std::move(msg), exit_code) {} \ - \ - public: \ - name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ - name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} - -// This is added after the one above if a class is used directly and builds its own message -#define CLI11_ERROR_SIMPLE(name) \ - name(std::string msg) : name(#name, msg, ExitCodes::name) {} - -/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, -/// int values from e.get_error_code(). -enum class ExitCodes { - Success = 0, - IncorrectConstruction = 100, - BadNameString, - OptionAlreadyAdded, - FileError, - ConversionError, - ValidationError, - RequiredError, - RequiresError, - ExcludesError, - ExtrasError, - INIError, - InvalidError, - HorribleError, - OptionNotFound, - ArgumentMismatch, - BaseClass = 127 -}; - -// Error definitions - -/// @defgroup error_group Errors -/// @brief Errors thrown by CLI11 -/// -/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. -/// @{ - -/// All errors derive from this one -class Error : public std::runtime_error { - int exit_code; - std::string name{"Error"}; - - public: - int get_exit_code() const { return exit_code; } - - std::string get_name() const { return name; } - - Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) - : runtime_error(msg), exit_code(exit_code), name(std::move(name)) {} - - Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} -}; - -// Note: Using Error::Error constructors does not work on GCC 4.7 - -/// Construction errors (not in parsing) -class ConstructionError : public Error { - CLI11_ERROR_DEF(Error, ConstructionError) -}; - -/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) -class IncorrectConstruction : public ConstructionError { - CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) - CLI11_ERROR_SIMPLE(IncorrectConstruction) - static IncorrectConstruction PositionalFlag(std::string name) { - return IncorrectConstruction(name + ": Flags cannot be positional"); - } - static IncorrectConstruction Set0Opt(std::string name) { - return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); - } - static IncorrectConstruction ChangeNotVector(std::string name) { - return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); - } - static IncorrectConstruction AfterMultiOpt(std::string name) { - return IncorrectConstruction( - name + ": You can't change expected arguments after you've changed the multi option policy!"); - } - static IncorrectConstruction MissingOption(std::string name) { - return IncorrectConstruction("Option " + name + " is not defined"); - } - static IncorrectConstruction MultiOptionPolicy(std::string name) { - return IncorrectConstruction(name + ": multi_option_policy only works for flags and single value options"); - } -}; - -/// Thrown on construction of a bad name -class BadNameString : public ConstructionError { - CLI11_ERROR_DEF(ConstructionError, BadNameString) - CLI11_ERROR_SIMPLE(BadNameString) - static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } - static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } - static BadNameString DashesOnly(std::string name) { - return BadNameString("Must have a name, not just dashes: " + name); - } - static BadNameString MultiPositionalNames(std::string name) { - return BadNameString("Only one positional name allowed, remove: " + name); - } -}; - -/// Thrown when an option already exists -class OptionAlreadyAdded : public ConstructionError { - CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) - OptionAlreadyAdded(std::string name) - : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} - static OptionAlreadyAdded Requires(std::string name, std::string other) { - return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded); - } - static OptionAlreadyAdded Excludes(std::string name, std::string other) { - return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded); - } -}; - -// Parsing errors - -/// Anything that can error in Parse -class ParseError : public Error { - CLI11_ERROR_DEF(Error, ParseError) -}; - -// Not really "errors" - -/// This is a successful completion on parsing, supposed to exit -class Success : public ParseError { - CLI11_ERROR_DEF(ParseError, Success) - Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} -}; - -/// -h or --help on command line -class CallForHelp : public ParseError { - CLI11_ERROR_DEF(ParseError, CallForHelp) - CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} -}; - -/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. -class RuntimeError : public ParseError { - CLI11_ERROR_DEF(ParseError, RuntimeError) - RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} -}; - -/// Thrown when parsing an INI file and it is missing -class FileError : public ParseError { - CLI11_ERROR_DEF(ParseError, FileError) - CLI11_ERROR_SIMPLE(FileError) - static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } -}; - -/// Thrown when conversion call back fails, such as when an int fails to coerce to a string -class ConversionError : public ParseError { - CLI11_ERROR_DEF(ParseError, ConversionError) - CLI11_ERROR_SIMPLE(ConversionError) - ConversionError(std::string member, std::string name) - : ConversionError("The value " + member + "is not an allowed value for " + name) {} - ConversionError(std::string name, std::vector results) - : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} - static ConversionError TooManyInputsFlag(std::string name) { - return ConversionError(name + ": too many inputs for a flag"); - } - static ConversionError TrueFalse(std::string name) { - return ConversionError(name + ": Should be true/false or a number"); - } -}; - -/// Thrown when validation of results fails -class ValidationError : public ParseError { - CLI11_ERROR_DEF(ParseError, ValidationError) - CLI11_ERROR_SIMPLE(ValidationError) - ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} -}; - -/// Thrown when a required option is missing -class RequiredError : public ParseError { - CLI11_ERROR_DEF(ParseError, RequiredError) - RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} - static RequiredError Subcommand(size_t min_subcom) { - if(min_subcom == 1) - return RequiredError("A subcommand"); - else - return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", - ExitCodes::RequiredError); - } -}; - -/// Thrown when the wrong number of arguments has been received -class ArgumentMismatch : public ParseError { - CLI11_ERROR_DEF(ParseError, ArgumentMismatch) - CLI11_ERROR_SIMPLE(ArgumentMismatch) - ArgumentMismatch(std::string name, int expected, size_t recieved) - : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + - ", got " + std::to_string(recieved)) - : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + - ", got " + std::to_string(recieved)), - ExitCodes::ArgumentMismatch) {} - - static ArgumentMismatch AtLeast(std::string name, int num) { - return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required"); - } - static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { - return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); - } -}; - -/// Thrown when a requires option is missing -class RequiresError : public ParseError { - CLI11_ERROR_DEF(ParseError, RequiresError) - RequiresError(std::string curname, std::string subname) - : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} -}; - -/// Thrown when an excludes option is present -class ExcludesError : public ParseError { - CLI11_ERROR_DEF(ParseError, ExcludesError) - ExcludesError(std::string curname, std::string subname) - : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} -}; - -/// Thrown when too many positionals or options are found -class ExtrasError : public ParseError { - CLI11_ERROR_DEF(ParseError, ExtrasError) - ExtrasError(std::vector args) - : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " - : "The following argument was not expected: ") + - detail::rjoin(args, " "), - ExitCodes::ExtrasError) {} -}; - -/// Thrown when extra values are found in an INI file -class INIError : public ParseError { - CLI11_ERROR_DEF(ParseError, INIError) - CLI11_ERROR_SIMPLE(INIError) - static INIError Extras(std::string item) { return INIError("INI was not able to parse " + item); } - static INIError NotConfigurable(std::string item) { - return INIError(item + ": This option is not allowed in a configuration file"); - } -}; - -/// Thrown when validation fails before parsing -class InvalidError : public ParseError { - CLI11_ERROR_DEF(ParseError, InvalidError) - InvalidError(std::string name) - : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { - } -}; - -/// This is just a safety check to verify selection and parsing match - you should not ever see it -/// Strings are directly added to this error, but again, it should never be seen. -class HorribleError : public ParseError { - CLI11_ERROR_DEF(ParseError, HorribleError) - CLI11_ERROR_SIMPLE(HorribleError) -}; - -// After parsing - -/// Thrown when counting a non-existent option -class OptionNotFound : public Error { - CLI11_ERROR_DEF(Error, OptionNotFound) - OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} -}; - -/// @} - -} // namespace CLI - -// From CLI/TypeTools.hpp - -namespace CLI { - -// Type tools - -// We could check to see if C++14 is being used, but it does not hurt to redefine this -// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h) -// It is not in the std namespace anyway, so no harm done. - -template using enable_if_t = typename std::enable_if::type; - -template struct is_vector { static const bool value = false; }; - -template struct is_vector> { static bool const value = true; }; - -template struct is_bool { static const bool value = false; }; - -template <> struct is_bool { static bool const value = true; }; - -namespace detail { -// Based generally on https://rmf.io/cxx11/almost-static-if -/// Simple empty scoped class -enum class enabler {}; - -/// An instance to use in EnableIf -constexpr enabler dummy = {}; - -// Type name print - -/// Was going to be based on -/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template -/// But this is cleaner and works better in this case - -template ::value && std::is_signed::value, detail::enabler> = detail::dummy> -constexpr const char *type_name() { - return "INT"; -} - -template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> -constexpr const char *type_name() { - return "UINT"; -} - -template ::value, detail::enabler> = detail::dummy> -constexpr const char *type_name() { - return "FLOAT"; -} - -/// This one should not be used, since vector types print the internal type -template ::value, detail::enabler> = detail::dummy> -constexpr const char *type_name() { - return "VECTOR"; -} - -template ::value && !std::is_integral::value && !is_vector::value, - detail::enabler> = detail::dummy> -constexpr const char *type_name() { - return "TEXT"; -} - -// Lexical cast - -/// Integers / enums -template ::value || std::is_enum::value, detail::enabler> = detail::dummy> -bool lexical_cast(std::string input, T &output) { - try { - output = static_cast(std::stoll(input)); - return true; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - return false; - } -} - -/// Floats -template ::value, detail::enabler> = detail::dummy> -bool lexical_cast(std::string input, T &output) { - try { - output = static_cast(std::stold(input)); - return true; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - return false; - } -} - -/// String and similar -template ::value && !std::is_integral::value && !std::is_enum::value, - detail::enabler> = detail::dummy> -bool lexical_cast(std::string input, T &output) { - output = input; - return true; -} - -} // namespace detail -} // namespace CLI - -// From CLI/Split.hpp - -namespace CLI { -namespace detail { - -// Returns false if not a short option. Otherwise, sets opt name and rest and returns true -inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { - if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { - name = current.substr(1, 1); - rest = current.substr(2); - return true; - } else - return false; -} - -// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true -inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { - if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { - auto loc = current.find("="); - if(loc != std::string::npos) { - name = current.substr(2, loc - 2); - value = current.substr(loc + 1); - } else { - name = current.substr(2); - value = ""; - } - return true; - } else - return false; -} - -// Splits a string into multiple long and short names -inline std::vector split_names(std::string current) { - std::vector output; - size_t val; - while((val = current.find(",")) != std::string::npos) { - output.push_back(trim_copy(current.substr(0, val))); - current = current.substr(val + 1); - } - output.push_back(trim_copy(current)); - return output; -} - -/// Get a vector of short names, one of long names, and a single name -inline std::tuple, std::vector, std::string> -get_names(const std::vector &input) { - - std::vector short_names; - std::vector long_names; - std::string pos_name; - - for(std::string name : input) { - if(name.length() == 0) - continue; - else if(name.length() > 1 && name[0] == '-' && name[1] != '-') { - if(name.length() == 2 && valid_first_char(name[1])) - short_names.emplace_back(1, name[1]); - else - throw BadNameString::OneCharName(name); - } else if(name.length() > 2 && name.substr(0, 2) == "--") { - name = name.substr(2); - if(valid_name_string(name)) - long_names.push_back(name); - else - throw BadNameString::BadLongName(name); - } else if(name == "-" || name == "--") { - throw BadNameString::DashesOnly(name); - } else { - if(pos_name.length() > 0) - throw BadNameString::MultiPositionalNames(name); - pos_name = name; - } - } - - return std::tuple, std::vector, std::string>( - short_names, long_names, pos_name); -} - -} // namespace detail -} // namespace CLI - -// From CLI/Ini.hpp - -namespace CLI { -namespace detail { - -inline std::string inijoin(std::vector args) { - std::ostringstream s; - size_t start = 0; - for(const auto &arg : args) { - if(start++ > 0) - s << " "; - - auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace(ch, std::locale()); }); - if(it == arg.end()) - s << arg; - else if(arg.find(R"(")") == std::string::npos) - s << R"(")" << arg << R"(")"; - else - s << R"(')" << arg << R"(')"; - } - - return s.str(); -} - -struct ini_ret_t { - /// This is the full name with dots - std::string fullname; - - /// Listing of inputs - std::vector inputs; - - /// Current parent level - size_t level = 0; - - /// Return parent or empty string, based on level - /// - /// Level 0, a.b.c would return a - /// Level 1, a.b.c could return b - std::string parent() const { - std::vector plist = detail::split(fullname, '.'); - if(plist.size() > (level + 1)) - return plist[level]; - else - return ""; - } - - /// Return name - std::string name() const { - std::vector plist = detail::split(fullname, '.'); - return plist.at(plist.size() - 1); - } -}; - -/// Internal parsing function -inline std::vector parse_ini(std::istream &input) { - std::string name, line; - std::string section = "default"; - - std::vector output; - - while(getline(input, line)) { - std::vector items; - - detail::trim(line); - size_t len = line.length(); - if(len > 1 && line[0] == '[' && line[len - 1] == ']') { - section = line.substr(1, len - 2); - } else if(len > 0 && line[0] != ';') { - output.emplace_back(); - ini_ret_t &out = output.back(); - - // Find = in string, split and recombine - auto pos = line.find("="); - if(pos != std::string::npos) { - name = detail::trim_copy(line.substr(0, pos)); - std::string item = detail::trim_copy(line.substr(pos + 1)); - items = detail::split_up(item); - } else { - name = detail::trim_copy(line); - items = {"ON"}; - } - - if(detail::to_lower(section) == "default") - out.fullname = name; - else - out.fullname = section + "." + name; - - out.inputs.insert(std::end(out.inputs), std::begin(items), std::end(items)); - } - } - return output; -} - -/// Parse an INI file, throw an error (ParseError:INIParseError or FileError) on failure -inline std::vector parse_ini(const std::string &name) { - - std::ifstream input{name}; - if(!input.good()) - throw FileError::Missing(name); - - return parse_ini(input); -} - -} // namespace detail -} // namespace CLI - -// From CLI/Validators.hpp - -namespace CLI { - -/// @defgroup validator_group Validators -/// @brief Some validators that are provided -/// -/// These are simple `void(std::string&)` validators that are useful. They throw -/// a ValidationError if they fail (or the normally expected error if the cast fails) -/// @{ - -/// Check for an existing file -inline std::string ExistingFile(const std::string &filename) { - struct stat buffer; - bool exist = stat(filename.c_str(), &buffer) == 0; - bool is_dir = (buffer.st_mode & S_IFDIR) != 0; - if(!exist) { - return "File does not exist: " + filename; - } else if(is_dir) { - return "File is actually a directory: " + filename; - } - return std::string(); -} - -/// Check for an existing directory -inline std::string ExistingDirectory(const std::string &filename) { - struct stat buffer; - bool exist = stat(filename.c_str(), &buffer) == 0; - bool is_dir = (buffer.st_mode & S_IFDIR) != 0; - if(!exist) { - return "Directory does not exist: " + filename; - } else if(!is_dir) { - return "Directory is actually a file: " + filename; - } - return std::string(); -} - -/// Check for a non-existing path -inline std::string NonexistentPath(const std::string &filename) { - struct stat buffer; - bool exist = stat(filename.c_str(), &buffer) == 0; - if(exist) { - return "Path already exists: " + filename; - } - return std::string(); -} - -/// Produce a range validator function -template std::function Range(T min, T max) { - return [min, max](std::string input) { - T val; - detail::lexical_cast(input, val); - if(val < min || val > max) - return "Value " + input + " not in range " + std::to_string(min) + " to " + std::to_string(max); - - return std::string(); - }; -} - -/// Range of one value is 0 to value -template std::function Range(T max) { - return Range(static_cast(0), max); -} - -/// @} - -} // namespace CLI - -// From CLI/Option.hpp - -namespace CLI { - -using results_t = std::vector; -using callback_t = std::function; - -class Option; -class App; - -using Option_p = std::unique_ptr