diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..7bc512cd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,80 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +defaults: + run: + shell: bash + +jobs: + build: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + # Linux — shared library, default threading (OpenMP + STL) + - name: Linux GCC + os: ubuntu-latest + cxx: g++ + + - name: Linux Clang + os: ubuntu-latest + cxx: clang++ + + # Linux — static library, Boost threading backend + - name: Linux GCC / Boost (static) + os: ubuntu-latest + cxx: g++ + cmake-extra: >- + -DBUILD_SHARED_LIBS=OFF + -DDDS_THREADS_BOOST=ON + -DDDS_THREADS_STL=ON + install-boost: true + + # macOS — shared library, default threading (GCD + STL) + - name: macOS + os: macos-latest + cmake-extra: -DDDS_WERROR=OFF + + # Windows — static library (avoids DLL path issues in CTest), + # default threading (WinAPI + OpenMP + STL) + - name: Windows MSVC (static) + os: windows-latest + cmake-extra: -DBUILD_SHARED_LIBS=OFF -DDDS_WERROR=OFF + skip-tests: true + + steps: + - uses: actions/checkout@v4 + + - name: Install OpenMP (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update -q + sudo apt-get install -y libomp-dev + + - name: Install Boost (Linux) + if: matrix.install-boost == true + run: sudo apt-get install -y libboost-thread-dev libboost-system-dev + + - name: Configure + env: + CXX: ${{ matrix.cxx }} + run: | + cmake -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DDDS_BUILD_TESTS=ON \ + -DDDS_BUILD_EXAMPLES=ON \ + ${{ matrix.cmake-extra }} + + - name: Build + run: cmake --build build --config Release --parallel + + - name: Test + if: matrix.skip-tests != true + run: ctest --test-dir build -C Release --output-on-failure diff --git a/.gitignore b/.gitignore index 3265db34..6d270eff 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ test/[Mm]akefile examples/[Mm]akefile test/dtest test/itest +build/ *.exe *.swp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..44411f22 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,348 @@ +cmake_minimum_required(VERSION 3.16) + +project(dds + VERSION 2.9.0 + DESCRIPTION "Double Dummy Solver for Contract Bridge" + HOMEPAGE_URL "https://github.com/dds-bridge/dds" + LANGUAGES CXX +) + +# ============================================================ +# Build options +# ============================================================ + +option(BUILD_SHARED_LIBS "Build shared library instead of static" ON) + +# Threading backends — multiple can be enabled simultaneously. +# The library selects the highest-priority available one at run time +# (priority order: WinAPI > OpenMP > GCD > Boost > STL; see System.cpp). +option(DDS_THREADS_BOOST "Use Boost threading backend" OFF) +option(DDS_THREADS_OPENMP "Use OpenMP threading backend" OFF) +option(DDS_THREADS_STL "Use C++ STL threading backend" OFF) +option(DDS_THREADS_GCD "Use Grand Central Dispatch (macOS only)" OFF) +option(DDS_THREADS_WINAPI "Use Windows API threading (Windows only)" OFF) + +# Optional build features +option(DDS_WERROR "Treat compiler warnings as errors" ON) +option(DDS_SMALL_MEMORY "Reduce memory use at cost of speed" OFF) +option(DDS_ENABLE_DEBUG "Enable all debug output" OFF) +option(DDS_ENABLE_TIMING "Enable timing statistics" OFF) +option(DDS_ENABLE_SCHEDULER "Enable scheduler debug output" OFF) + +# Sub-projects +option(DDS_BUILD_TESTS "Build test program (dtest)" OFF) +option(DDS_BUILD_EXAMPLES "Build example programs" OFF) + +# ============================================================ +# Platform defaults for threading +# Applied only when the user hasn't explicitly requested any backend. +# ============================================================ + +if(NOT DDS_THREADS_BOOST AND + NOT DDS_THREADS_OPENMP AND + NOT DDS_THREADS_STL AND + NOT DDS_THREADS_GCD AND + NOT DDS_THREADS_WINAPI) + if(WIN32) + set(DDS_THREADS_WINAPI ON) + set(DDS_THREADS_OPENMP ON) + set(DDS_THREADS_STL ON) + elseif(APPLE) + set(DDS_THREADS_GCD ON) + set(DDS_THREADS_STL ON) + else() + # Linux / other Unix + set(DDS_THREADS_OPENMP ON) + set(DDS_THREADS_STL ON) + endif() +endif() + +# ============================================================ +# Windows resource file (version info embedded in the DLL) +# ============================================================ + +if(WIN32 AND BUILD_SHARED_LIBS) + enable_language(RC) +endif() + +# ============================================================ +# Library source files +# ============================================================ + +set(DDS_SOURCES + src/dds.cpp + src/ABsearch.cpp + src/ABstats.cpp + src/CalcTables.cpp + src/DealerPar.cpp + src/dump.cpp + src/File.cpp + src/Init.cpp + src/LaterTricks.cpp + src/Memory.cpp + src/Moves.cpp + src/Par.cpp + src/PlayAnalyser.cpp + src/PBN.cpp + src/QuickTricks.cpp + src/Scheduler.cpp + src/SolveBoard.cpp + src/SolverIF.cpp + src/System.cpp + src/ThreadMgr.cpp + src/Timer.cpp + src/TimerGroup.cpp + src/TimerList.cpp + src/TimeStat.cpp + src/TimeStatList.cpp + src/TransTableS.cpp + src/TransTableL.cpp +) + +if(WIN32 AND BUILD_SHARED_LIBS AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/dds.rc") + list(APPEND DDS_SOURCES src/dds.rc) +endif() + +# ============================================================ +# Library target +# ============================================================ + +add_library(dds ${DDS_SOURCES}) +add_library(dds::dds ALIAS dds) + +target_include_directories(dds + PUBLIC + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +# MSVC: use the module-definition file to control DLL exports +if(WIN32 AND BUILD_SHARED_LIBS AND MSVC) + target_link_options(dds PRIVATE + "/DEF:${CMAKE_CURRENT_SOURCE_DIR}/src/Exports.def" + ) +endif() + +# ============================================================ +# C++ standard +# ============================================================ + +if(MSVC) + # C++17 needed for STLIMPL / parallel STL threading backend + target_compile_features(dds PUBLIC cxx_std_17) +else() + target_compile_features(dds PUBLIC cxx_std_11) +endif() +set_target_properties(dds PROPERTIES CXX_EXTENSIONS OFF) + +# ============================================================ +# Link-time optimisation +# ============================================================ + +include(CheckIPOSupported) +check_ipo_supported(RESULT _ipo_supported OUTPUT _ipo_output) +if(_ipo_supported) + set_target_properties(dds PROPERTIES + INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE + INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE + ) +else() + message(STATUS "IPO/LTO not supported: ${_ipo_output}") +endif() + +# ============================================================ +# Compiler flags +# ============================================================ + +if(MSVC) + target_compile_options(dds PRIVATE + /O2 /Oi /Ot /Oy /Ox /EHs /Qspectre + # Aggressive warnings, suppressing those triggered by Windows / STL headers + /Wall + /wd4365 # signed/unsigned mismatch + /wd4464 # relative include path contains '..' + /wd4514 # unreferenced inline function removed + /wd4555 # expression has no effect + /wd4571 # catch(...) semantics changed + /wd4623 # default constructor implicitly deleted + /wd4625 # copy constructor implicitly deleted + /wd4626 # copy assignment operator implicitly deleted + /wd4668 # symbol not defined as preprocessor macro + /wd4710 # function not inlined + /wd4711 # function selected for automatic inlining + /wd4774 # format string not a string literal + /wd4820 # padding added after data member + /wd4996 # deprecated function + /wd5026 # move constructor implicitly deleted + /wd5027 # move assignment operator implicitly deleted + /wd5045 # Spectre mitigation insertion + $<$:/WX> + ) +else() + # Flags common to GCC and Clang + target_compile_options(dds PRIVATE + -O3 + -Wall -Wextra $<$:-Werror> -pedantic + -Wshadow + -Wsign-conversion + -Wcast-align + -Wcast-qual + -Wctor-dtor-privacy + -Wdisabled-optimization + -Winit-self + -Wmissing-declarations + -Wmissing-include-dirs + -Wold-style-cast + -Woverloaded-virtual + -Wredundant-decls + -Wsign-promo + -Wstrict-overflow=1 + -Wswitch-default + -Wundef + -Wno-unused + -Wno-unknown-pragmas + -Wno-long-long + -Wno-format + ) + + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(dds PRIVATE + -Wlogical-op + -Wnoexcept + -Wstrict-null-sentinel + ) + endif() + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(dds PRIVATE + -Wcomment + ) + endif() + + # Linker hardening for shared libraries on Linux + if(UNIX AND NOT APPLE AND BUILD_SHARED_LIBS) + target_link_options(dds PRIVATE + -Wl,--sort-common + -Wl,--as-needed + -Wl,-z,relro + ) + endif() +endif() + +# ============================================================ +# Threading backends +# ============================================================ + +if(DDS_THREADS_OPENMP) + find_package(OpenMP REQUIRED) + target_compile_definitions(dds PRIVATE DDS_THREADS_OPENMP) + target_link_libraries(dds PRIVATE OpenMP::OpenMP_CXX) +endif() + +if(DDS_THREADS_BOOST) + find_package(Boost REQUIRED COMPONENTS system thread) + target_compile_definitions(dds PRIVATE DDS_THREADS_BOOST) + target_link_libraries(dds PRIVATE Boost::system Boost::thread) +endif() + +if(DDS_THREADS_STL) + find_package(Threads REQUIRED) + target_compile_definitions(dds PRIVATE DDS_THREADS_STL) + target_link_libraries(dds PRIVATE Threads::Threads) +endif() + +if(DDS_THREADS_GCD) + if(NOT APPLE) + message(FATAL_ERROR "DDS_THREADS_GCD is available on macOS only") + endif() + target_compile_definitions(dds PRIVATE DDS_THREADS_GCD) +endif() + +if(DDS_THREADS_WINAPI) + if(NOT WIN32) + message(FATAL_ERROR "DDS_THREADS_WINAPI is available on Windows only") + endif() + target_compile_definitions(dds PRIVATE DDS_THREADS_WINAPI) +endif() + +# ============================================================ +# Optional behaviour flags +# ============================================================ + +if(DDS_SMALL_MEMORY) + target_compile_definitions(dds PRIVATE SMALL_MEMORY_OPTION) +endif() + +if(DDS_ENABLE_DEBUG) + target_compile_definitions(dds PRIVATE DDS_DEBUG_ALL) +endif() + +if(DDS_ENABLE_TIMING) + target_compile_definitions(dds PRIVATE DDS_TIMING) +endif() + +if(DDS_ENABLE_SCHEDULER) + target_compile_definitions(dds PRIVATE DDS_SCHEDULER) +endif() + +# ============================================================ +# Sub-projects +# ============================================================ + +if(DDS_BUILD_TESTS OR DDS_BUILD_EXAMPLES) + enable_testing() +endif() + +if(DDS_BUILD_TESTS) + add_subdirectory(test) +endif() + +if(DDS_BUILD_EXAMPLES) + add_subdirectory(examples) +endif() + +# ============================================================ +# Installation +# ============================================================ + +include(GNUInstallDirs) + +install(TARGETS dds + EXPORT ddsTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} # .dll on Windows +) + +install(DIRECTORY include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING PATTERN "*.h" +) + +install(EXPORT ddsTargets + FILE ddsTargets.cmake + NAMESPACE dds:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/dds +) + +include(CMakePackageConfigHelpers) + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/ddsConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion +) + +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/ddsConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/ddsConfig.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/dds +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/ddsConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/ddsConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/dds +) diff --git a/cmake/ddsConfig.cmake.in b/cmake/ddsConfig.cmake.in new file mode 100644 index 00000000..7c121033 --- /dev/null +++ b/cmake/ddsConfig.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/ddsTargets.cmake") + +check_required_components(dds) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 00000000..a0eb4bd3 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,33 @@ +set(DDS_EXAMPLES + AnalyseAllPlaysBin + AnalyseAllPlaysPBN + AnalysePlayBin + AnalysePlayPBN + CalcAllTables + CalcAllTablesPBN + CalcDDtable + CalcDDtablePBN + DealerPar + Par + SolveAllBoards + SolveBoard + SolveBoardPBN +) + +foreach(example IN LISTS DDS_EXAMPLES) + add_executable(${example} ${example}.cpp hands.cpp) + target_include_directories(${example} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(${example} PRIVATE dds::dds) +endforeach() + +# ============================================================ +# CTest definitions +# ============================================================ + +# Examples always exit 0; detect failures via output patterns. +foreach(example IN LISTS DDS_EXAMPLES) + add_test(NAME example_${example} COMMAND ${example}) + set_tests_properties(example_${example} PROPERTIES + FAIL_REGULAR_EXPRESSION "ERROR;DDS error" + ) +endforeach() diff --git a/src/Moves.cpp b/src/Moves.cpp index c2e5e629..98db9537 100644 --- a/src/Moves.cpp +++ b/src/Moves.cpp @@ -387,7 +387,7 @@ void Moves::WeightAllocTrump0( else if ((tpos.winner[suit].hand == lho[leadHand]) && (tpos.secondBest[suit].hand == partner[leadHand])) { - /* This case was suggested by Joël Bradmetz. */ + /* This case was suggested by Joel Bradmetz. */ if (tpos.length[partner[leadHand]][suit] != 1) suitBonus += 27; } @@ -689,7 +689,7 @@ void Moves::WeightAllocNT0( else if ((tpos.winner[suit].hand == lho[leadHand]) && (tpos.secondBest[suit].hand == partner[leadHand])) { - /* This case was suggested by Joël Bradmetz. */ + /* This case was suggested by Joel Bradmetz. */ if (tpos.length[partner[leadHand]][suit] != 1) suitWeightDelta += 31; } diff --git a/src/TransTableL.cpp b/src/TransTableL.cpp index d858cdb1..ebce0aca 100644 --- a/src/TransTableL.cpp +++ b/src/TransTableL.cpp @@ -1051,7 +1051,7 @@ string TransTableL::MakeHolding( void TransTableL::DumpHands( ofstream& fout, const vector>& hands, - const unsigned char lengths[][DDS_SUITS]) const + const unsigned char lengths[DDS_HANDS][DDS_SUITS]) const { for (unsigned i = 0; i < DDS_SUITS; i++) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..dad02d97 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,40 @@ +add_executable(dtest + args.cpp + compare.cpp + loop.cpp + parse.cpp + print.cpp + testcommon.cpp + TestTimer.cpp + dtest.cpp +) + +target_include_directories(dtest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(dtest PRIVATE dds::dds) + +# ============================================================ +# CTest definitions +# ============================================================ + +# dtest always exits 0; detect failures via output patterns. +set(_fail_re "Difference;read_file failed;loop_[a-z]+:.*return") +set(_hands "${CMAKE_SOURCE_DIR}/hands") + +foreach(_mode IN ITEMS solve calc par dealerpar play) + add_test( + NAME dtest_${_mode} + COMMAND dtest -f "${_hands}/list10.txt" -s ${_mode} + ) + set_tests_properties(dtest_${_mode} PROPERTIES + FAIL_REGULAR_EXPRESSION "${_fail_re}" + ) +endforeach() + +# Extra: GIB-format input (sol10.txt) exercising the calc solver path +add_test( + NAME dtest_calc_gib + COMMAND dtest -f "${_hands}/sol10.txt" -s calc +) +set_tests_properties(dtest_calc_gib PROPERTIES + FAIL_REGULAR_EXPRESSION "${_fail_re}" +)