From 3a7b1843397810b41c2585e46fcf85100bba9f51 Mon Sep 17 00:00:00 2001 From: Magnaibayar Ganzorig Date: Fri, 11 Sep 2026 20:02:31 -0400 Subject: [PATCH 1/3] Build against current Boost releases Boost 1.66 reorganized Asio and Boost 1.87 removed what it had deprecated, so QuickFAST has not compiled against a current Boost for some years. These are the changes needed to build with Boost 1.66 through at least 1.91, with every replacement selected on BOOST_VERSION so that older Boost releases build exactly as they did before. The library: * AsioService_fwd.h forward declared boost::asio::io_service as a class. From Boost 1.66 on it is a typedef for io_context, so that declaration conflicts with Asio's own and every use of the type fails. The name is now taken from Asio itself; Boost 1.87 and later, which dropped the name entirely, get a typedef for io_context. * The Asio I/O objects replaced their io_service& constructors with a template that asks its argument for an executor, so the implicit cast on AsioService is no longer enough to construct a socket or a resolver from one. AsioService now forwards get_executor() to the io_service it wraps. * A new header, Communication/AsioCompatibility.h, spells the four remaining interfaces the way the Boost release being compiled against expects: ip::address::from_string is now make_address, io_service::post is now the free function post, io_service::reset is now restart, and io_service::work is now executor_work_guard. The call sites ask it rather than repeating a version test each time. * The iterator-returning resolver::resolve(query) in TCPReceiver became resolve(host, service) returning a results range. The examples: * boost::asio::strand became a template, so the two burst senders name the class they want, io_service::strand, and reach it through dispatch and bind_executor rather than the strand's own wrap and dispatch. * Asio's date_time based timers are opt-in in recent releases, so those two examples use steady_timer and a chrono duration instead of deadline_timer and a posix_time duration. * basic_socket became a private base of the socket stream buffer, so FileToTCP moves an accepted socket into its stream instead of accepting into the stream's buffer. * Compare the FILE* returned by std::fopen against 0 rather than ordering it against 0, which gcc rejects. Verified on Ubuntu 24.04 (gcc 13, Boost 1.83), Ubuntu 25.10 (gcc 15, Boost 1.88) and against Boost 1.91 built by vcpkg: the library, all six example programs and all 112 unit test cases build and pass in each. No MPC file, setup script or build flag is changed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017BnyHwonMsq7k6uQxWpVGF --- src/Communication/AsioCompatibility.h | 88 +++++++++++++++++++ src/Communication/AsioService.h | 20 ++++- src/Communication/AsioService_fwd.h | 19 ++++ src/Communication/AsynchSender.cpp | 4 +- src/Communication/AsynchSender.h | 2 +- src/Communication/MulticastReceiver.h | 6 +- src/Communication/MulticastSender.h | 2 +- src/Communication/TCPReceiver.h | 14 +++ .../FileToMulticast/FileToMulticast.cpp | 20 +++++ .../FileToMulticast/FileToMulticast.h | 11 ++- src/Examples/FileToTCP/FileToTCP.cpp | 9 +- src/Examples/FileToTCP/FileToTCP.h | 1 + .../InterpretApplication.cpp | 2 +- .../PCapToMulticast/PCapToMulticast.cpp | 17 +++- .../PCapToMulticast/PCapToMulticast.h | 12 ++- 15 files changed, 212 insertions(+), 15 deletions(-) create mode 100644 src/Communication/AsioCompatibility.h diff --git a/src/Communication/AsioCompatibility.h b/src/Communication/AsioCompatibility.h new file mode 100644 index 00000000..bbad0de6 --- /dev/null +++ b/src/Communication/AsioCompatibility.h @@ -0,0 +1,88 @@ +// Copyright (c) 2009, 2010, 2011 Object Computing, Inc. +// All rights reserved. +// See the file license.txt for licensing information. +// +#ifdef _MSC_VER +# pragma once +#endif +#ifndef ASIOCOMPATIBILITY_H +#define ASIOCOMPATIBILITY_H +#ifndef QUICKFAST_HEADERS +#error Please include preferably as a precompiled header file. +#endif //QUICKFAST_HEADERS + +#include "AsioService_fwd.h" +#include +#include + +namespace QuickFAST +{ + namespace Communication + { + // Boost 1.66 replaced several of the Asio interfaces QuickFAST is written + // against, and Boost 1.87 removed the originals. Each helper below spells + // its operation the way the Boost release being compiled against expects, + // so that the call sites do not have to. + + /// @brief Convert a dotted IP address to an asio address + /// @param address is the address in dotted notation + inline boost::asio::ip::address makeAddress(const std::string & address) + { +#if BOOST_VERSION >= 106600 + return boost::asio::ip::make_address(address); +#else + return boost::asio::ip::address::from_string(address); +#endif + } + + /// @brief Post a completion handler to an io service + /// @param ioService is the service that will run the handler + /// @param handler is the handler to be posted + template + inline void postHandler( + boost::asio::io_service & ioService, + CompletionHandler handler) + { +#if BOOST_VERSION >= 106600 + boost::asio::post(ioService, handler); +#else + ioService.post(handler); +#endif + } + + /// @brief Keeps an io service running while it has no work to do + /// + /// io_service::work was replaced by executor_work_guard in Boost 1.66 and + /// removed in Boost 1.87. +#if BOOST_VERSION >= 106600 + typedef boost::asio::executor_work_guard< + boost::asio::io_context::executor_type> WorkGuard; +#else + typedef boost::asio::io_service::work WorkGuard; +#endif + + /// @brief Create a work guard for an io service + /// @param ioService is the service to be kept alive + /// @returns a new work guard; the caller owns it + inline WorkGuard * makeWorkGuard(boost::asio::io_service & ioService) + { +#if BOOST_VERSION >= 106600 + return new WorkGuard(boost::asio::make_work_guard(ioService)); +#else + return new WorkGuard(ioService); +#endif + } + + /// @brief Prepare an io service to be run again after it has stopped + /// @param ioService is the service to be restarted + inline void restartService(boost::asio::io_service & ioService) + { +#if BOOST_VERSION >= 106600 + ioService.restart(); +#else + ioService.reset(); +#endif + } + } +} +#endif // ASIOCOMPATIBILITY_H diff --git a/src/Communication/AsioService.h b/src/Communication/AsioService.h index 4f619156..bd84fb76 100644 --- a/src/Communication/AsioService.h +++ b/src/Communication/AsioService.h @@ -7,7 +7,7 @@ #endif #ifndef ASIOSERVICE_H #define ASIOSERVICE_H -#include "AsioService_fwd.h" +#include "AsioCompatibility.h" #include #include #include @@ -87,7 +87,7 @@ namespace QuickFAST /// should be called after joinThreads before calling run*, poll*, etc. again. void resetService() { - ioService_.reset(); + restartService(ioService_); stopping_ = false; } @@ -100,12 +100,26 @@ namespace QuickFAST return ioService_; } +#if BOOST_VERSION >= 106600 + /// @brief the executor of the underlying io_service + /// + /// Boost 1.66 replaced the io_service& constructors of the Asio I/O + /// objects with a template that asks its argument for an executor. The + /// implicit cast above is no longer enough for an AsioService to be + /// passed where an io_service used to be accepted, so the question is + /// forwarded to the io_service being wrapped. + boost::asio::io_context::executor_type get_executor() + { + return ioService_.get_executor(); + } +#endif // BOOST_VERSION >= 106600 + ///@brief Post a completion handler for later processing (usually in a different thread) /// @param handler is the handler to be posted template void post(CompletionHandler handler) { - ioService_.post(handler); + postHandler(ioService_, handler); } /// @brief Attempt to determine how many threads are available to ASIO diff --git a/src/Communication/AsioService_fwd.h b/src/Communication/AsioService_fwd.h index dc2f2354..a3f01ab7 100644 --- a/src/Communication/AsioService_fwd.h +++ b/src/Communication/AsioService_fwd.h @@ -11,6 +11,24 @@ #error Please include preferably as a precompiled header file. #endif //QUICKFAST_HEADERS +// Boost 1.66 turned io_service into a typedef for io_context, so the class +// declaration QuickFAST used to make here now conflicts with the one Asio +// provides. From that release on, take the declaration from Boost itself; +// io_service.hpp only pulls in io_context, not all of Asio. +#include +#if BOOST_VERSION >= 108700 +// Boost 1.87 removed the io_service name altogether. +# include +namespace boost +{ + namespace asio + { + typedef io_context io_service; + } +} +#elif BOOST_VERSION >= 106600 +# include +#else // forward declare io_service without including // boost header namespace boost @@ -20,6 +38,7 @@ namespace boost class io_service; } } +#endif // BOOST_VERSION >= 106600 namespace QuickFAST { diff --git a/src/Communication/AsynchSender.cpp b/src/Communication/AsynchSender.cpp index 0261302f..669918f3 100644 --- a/src/Communication/AsynchSender.cpp +++ b/src/Communication/AsynchSender.cpp @@ -15,7 +15,7 @@ AsynchSender::AsynchSender( : Sender(recycler) , name_(name) , ioService_() - , keepAlive_(new boost::asio::io_service::work(ioService_)) + , keepAlive_(makeWorkGuard(ioService_)) { //std::cout << "Asynch Sender {" << (void *)this << "} keeping ioService " << (void*) &ioService_ << " alive." << std::endl; } @@ -27,7 +27,7 @@ AsynchSender::AsynchSender( : Sender(recycler) , name_(name) , ioService_(ioService) - , keepAlive_(new boost::asio::io_service::work(ioService_)) + , keepAlive_(makeWorkGuard(ioService_)) { // std::cout << "Asynch Sender {" << (void *)this << "} keeping shared ioService " << (void*) &ioService_ << " alive." << std::endl; } diff --git a/src/Communication/AsynchSender.h b/src/Communication/AsynchSender.h index 5eb02c25..b2393c0b 100644 --- a/src/Communication/AsynchSender.h +++ b/src/Communication/AsynchSender.h @@ -111,7 +111,7 @@ namespace QuickFAST /// needed for output-type service which may have nothing to write at the moment, unlike /// input-type services which should always have an outstanding read or an active handler /// callback. - boost::scoped_ptr keepAlive_; + boost::scoped_ptr keepAlive_; }; } } diff --git a/src/Communication/MulticastReceiver.h b/src/Communication/MulticastReceiver.h index cb9a9993..4aa4d423 100644 --- a/src/Communication/MulticastReceiver.h +++ b/src/Communication/MulticastReceiver.h @@ -36,10 +36,10 @@ namespace QuickFAST ) : parent_(parent) , name_(name) - , listenInterface_(boost::asio::ip::address::from_string(listenInterfaceIP)) + , listenInterface_(makeAddress(listenInterfaceIP)) , portNumber_(portNumber) - , multicastGroup_(boost::asio::ip::address::from_string(multicastGroupIP)) - , bindAddress_(boost::asio::ip::address::from_string(bindIP)) + , multicastGroup_(makeAddress(multicastGroupIP)) + , bindAddress_(makeAddress(bindIP)) , endpoint_(listenInterface_, portNumber) , socket_(ioService) , joined_(false) diff --git a/src/Communication/MulticastSender.h b/src/Communication/MulticastSender.h index f0c7be18..bcbb3f99 100644 --- a/src/Communication/MulticastSender.h +++ b/src/Communication/MulticastSender.h @@ -62,7 +62,7 @@ namespace QuickFAST ///@brief Prepare the sender to be used bool initializeSender() { - multicastAddress_ = boost::asio::ip::address::from_string(sendAddress_); + multicastAddress_ = makeAddress(sendAddress_); endpoint_ = boost::asio::ip::udp::endpoint(multicastAddress_, portNumber_); socket_.open(endpoint_.protocol()); return true; diff --git a/src/Communication/TCPReceiver.h b/src/Communication/TCPReceiver.h index 6ddb4363..8b45ebc3 100644 --- a/src/Communication/TCPReceiver.h +++ b/src/Communication/TCPReceiver.h @@ -60,12 +60,26 @@ namespace QuickFAST bool ok = true; // generate a collection of possible endpoints for this host:port boost::asio::ip::tcp::resolver resolver(ioService_); +#if BOOST_VERSION >= 106600 + // resolver::query and the iterator-returning resolve() were replaced + // in Boost 1.66 and removed in Boost 1.87. + boost::asio::ip::tcp::resolver::results_type endpoints = + resolver.resolve(hostName_, port_); + boost::asio::ip::tcp::resolver::results_type::const_iterator iterator = + endpoints.begin(); + boost::asio::ip::tcp::resolver::results_type::const_iterator endIterator = + endpoints.end(); + + // then iterate thru the collection until we find one that works. + boost::system::error_code error; +#else boost::asio::ip::tcp::resolver::query query( hostName_, port_); boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query); // then iterate thru the collection until we find one that works. boost::system::error_code error; boost::asio::ip::tcp::resolver::iterator endIterator; +#endif // BOOST_VERSION >= 106600 bool connected = false; while(!connected && iterator != endIterator) { diff --git a/src/Examples/FileToMulticast/FileToMulticast.cpp b/src/Examples/FileToMulticast/FileToMulticast.cpp index 4f714958..2c4e93f8 100644 --- a/src/Examples/FileToMulticast/FileToMulticast.cpp +++ b/src/Examples/FileToMulticast/FileToMulticast.cpp @@ -263,8 +263,18 @@ FileToMulticast::run() << "Largest is " << bufferSize_ << " bytes." << std::endl; } +#if BOOST_VERSION >= 106600 + // strand::wrap and strand::dispatch were replaced in Boost 1.66. + boost::asio::dispatch(strand_, boost::bind(&FileToMulticast::sendBurst, this)); +#else +#if BOOST_VERSION >= 106600 + // strand::dispatch now takes the handler itself, already bound. + boost::asio::dispatch(strand_, boost::bind(&FileToMulticast::sendBurst, this)); +#else strand_.dispatch( strand_.wrap(boost::bind(&FileToMulticast::sendBurst, this))); +#endif // BOOST_VERSION >= 106600 +#endif // BOOST_VERSION >= 106600 StopWatch lapse; this->ioService_.run(); unsigned long sendLapse = lapse.freeze(); @@ -302,9 +312,19 @@ FileToMulticast::sendBurst() // set the next timeout if(sendMicroseconds_ != 0) { +#if BOOST_VERSION >= 106600 + timer_.expires_after(std::chrono::microseconds(sendMicroseconds_)); +#else timer_.expires_from_now(boost::posix_time::microseconds(sendMicroseconds_)); +#endif // BOOST_VERSION >= 106600 timer_.async_wait( +#if BOOST_VERSION >= 106600 + // strand::wrap was replaced by bind_executor in Boost 1.66. + boost::asio::bind_executor( + strand_, boost::bind(&FileToMulticast::sendBurst, this)) +#else strand_.wrap(boost::bind(&FileToMulticast::sendBurst, this)) +#endif // BOOST_VERSION >= 106600 ); } diff --git a/src/Examples/FileToMulticast/FileToMulticast.h b/src/Examples/FileToMulticast/FileToMulticast.h index d6d62fff..60d4fab4 100644 --- a/src/Examples/FileToMulticast/FileToMulticast.h +++ b/src/Examples/FileToMulticast/FileToMulticast.h @@ -9,6 +9,7 @@ #include #include #include +#include namespace QuickFAST{ namespace Examples{ @@ -64,8 +65,16 @@ namespace QuickFAST{ bool verbose_; Communication::AsioService ioService_; - boost::asio::strand strand_; + // boost::asio::strand became a template in Boost 1.66; the class this + // example uses is spelled io_service::strand in every release. + boost::asio::io_service::strand strand_; +#if BOOST_VERSION >= 106600 + // Asio's date_time based timers became opt-in, so the example uses the + // chrono based timer that is always available. + boost::asio::steady_timer timer_; +#else boost::asio::deadline_timer timer_; +#endif // BOOST_VERSION >= 106600 Application::CommandArgParser commandArgParser_; FILE * dataFile_; diff --git a/src/Examples/FileToTCP/FileToTCP.cpp b/src/Examples/FileToTCP/FileToTCP.cpp index 9ed52088..8d2a6187 100644 --- a/src/Examples/FileToTCP/FileToTCP.cpp +++ b/src/Examples/FileToTCP/FileToTCP.cpp @@ -121,12 +121,19 @@ FileToTCP::run() for (size_t count = 0; count < sendCount_ || sendCount_ == 0; ++count) { - tcp::iostream stream; if(verbose_) { std::cout << "Listening" << std::endl; } +#if BOOST_VERSION >= 106600 + // Boost 1.66 made basic_socket a private base of the stream buffer, so + // the accepted socket is moved into the stream instead of accepted into + // the stream's buffer. + tcp::iostream stream(acceptor.accept()); +#else + tcp::iostream stream; acceptor.accept(*stream.rdbuf()); +#endif // BOOST_VERSION >= 106600 if(verbose_) { std::cout << "Accepting" << std::endl; diff --git a/src/Examples/FileToTCP/FileToTCP.h b/src/Examples/FileToTCP/FileToTCP.h index 6bca9e08..48d322bb 100644 --- a/src/Examples/FileToTCP/FileToTCP.h +++ b/src/Examples/FileToTCP/FileToTCP.h @@ -5,6 +5,7 @@ #ifndef FILETOTCP_H #define FILETOTCP_H #include +#include #include #include diff --git a/src/Examples/InterpretApplication/InterpretApplication.cpp b/src/Examples/InterpretApplication/InterpretApplication.cpp index 9e908541..2185b1d2 100644 --- a/src/Examples/InterpretApplication/InterpretApplication.cpp +++ b/src/Examples/InterpretApplication/InterpretApplication.cpp @@ -252,7 +252,7 @@ InterpretApplication::run() "r" #endif ); - if(bufferFile <= 0) + if(bufferFile == 0) { std::cerr << "Can't open file " << bufferFilename_ << std::endl; return -1; diff --git a/src/Examples/PCapToMulticast/PCapToMulticast.cpp b/src/Examples/PCapToMulticast/PCapToMulticast.cpp index c08d11f2..b58dd7d3 100644 --- a/src/Examples/PCapToMulticast/PCapToMulticast.cpp +++ b/src/Examples/PCapToMulticast/PCapToMulticast.cpp @@ -166,7 +166,7 @@ PCapToMulticast::applyArgs() } ok = ok && pcapReader_.open(dataFileName_.c_str());// for debugging dump to->, &std::cout); - multicastAddress_ = boost::asio::ip::address::from_string(sendAddress_); + multicastAddress_ = Communication::makeAddress(sendAddress_); endpoint_ = boost::asio::ip::udp::endpoint(multicastAddress_, portNumber_); socket_.open(endpoint_.protocol()); std::cout << "Opening multicast group: " << endpoint_.address().to_string() << ':' << endpoint_.port() << std::endl; @@ -189,8 +189,13 @@ PCapToMulticast::run() std::cout << " Configuring multicast: " << multicastAddress_ << '|' << sendAddress_ << ':' << portNumber_ << std::endl; } +#if BOOST_VERSION >= 106600 + // strand::dispatch now takes the handler itself, already bound. + boost::asio::dispatch(strand_, boost::bind(&PCapToMulticast::sendBurst, this)); +#else strand_.dispatch( strand_.wrap(boost::bind(&PCapToMulticast::sendBurst, this))); +#endif // BOOST_VERSION >= 106600 StopWatch lapse; this->ioService_.run(); unsigned long sendLapse = lapse.freeze(); @@ -228,9 +233,19 @@ PCapToMulticast::sendBurst() // set the next timeout if(sendMicroseconds_ != 0) { +#if BOOST_VERSION >= 106600 + timer_.expires_after(std::chrono::microseconds(sendMicroseconds_)); +#else timer_.expires_from_now(boost::posix_time::microseconds(sendMicroseconds_)); +#endif // BOOST_VERSION >= 106600 timer_.async_wait( +#if BOOST_VERSION >= 106600 + // strand::wrap was replaced by bind_executor in Boost 1.66. + boost::asio::bind_executor( + strand_, boost::bind(&PCapToMulticast::sendBurst, this)) +#else strand_.wrap(boost::bind(&PCapToMulticast::sendBurst, this)) +#endif // BOOST_VERSION >= 106600 ); } diff --git a/src/Examples/PCapToMulticast/PCapToMulticast.h b/src/Examples/PCapToMulticast/PCapToMulticast.h index fe97b25b..44038e9e 100644 --- a/src/Examples/PCapToMulticast/PCapToMulticast.h +++ b/src/Examples/PCapToMulticast/PCapToMulticast.h @@ -8,8 +8,10 @@ #define PCAP_SUPPORT_IS_HEREx #include #include +#include #include #include +#include namespace QuickFAST{ namespace Examples{ @@ -66,8 +68,16 @@ namespace QuickFAST{ boost::asio::ip::address multicastAddress_; boost::asio::ip::udp::endpoint endpoint_; boost::asio::ip::udp::socket socket_; - boost::asio::strand strand_; + // boost::asio::strand became a template in Boost 1.66; the class this + // example uses is spelled io_service::strand in every release. + boost::asio::io_service::strand strand_; +#if BOOST_VERSION >= 106600 + // Asio's date_time based timers became opt-in, so the example uses the + // chrono based timer that is always available. + boost::asio::steady_timer timer_; +#else boost::asio::deadline_timer timer_; +#endif // BOOST_VERSION >= 106600 Application::CommandArgParser commandArgParser_; // FILE * dataFile_; From 37094f683ced45479fc5b576efd57a70c45003b3 Mon Sep 17 00:00:00 2001 From: Magnaibayar Ganzorig Date: Fri, 11 Sep 2026 21:49:40 -0400 Subject: [PATCH 2/3] Fix narrowing conversions in the presence map test testPresenceMap initializes arrays of uchar from char literals. Where char is signed -- x86 and x86_64, as opposed to ARM -- '\xFF' is -1, and C++11 made that a narrowing conversion inside a braced initializer, so gcc and clang reject it. Writing the bytes as integer literals says what was meant and is correct on either sign of char. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017BnyHwonMsq7k6uQxWpVGF --- src/Tests/testPresenceMap.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tests/testPresenceMap.cpp b/src/Tests/testPresenceMap.cpp index 21139f58..50dd1d68 100644 --- a/src/Tests/testPresenceMap.cpp +++ b/src/Tests/testPresenceMap.cpp @@ -19,7 +19,7 @@ BOOST_AUTO_TEST_CASE(testPmapDecoding) BOOST_CHECK(!pmap.checkNextField()); } - uchar oneBytePMAP[] = {'\xFF'}; + uchar oneBytePMAP[] = {0xFF}; pmap.setRaw(oneBytePMAP, 1); for(size_t n = 0; n < 7; ++n) // The first 7 should be present @@ -43,7 +43,7 @@ BOOST_AUTO_TEST_CASE(testPmapDecoding) BOOST_CHECK(!pmap.checkNextField()); } - uchar everyOtherOne[] = {'\x55', '\xAA'}; + uchar everyOtherOne[] = {0x55, 0xAA}; BOOST_CHECK_EQUAL(sizeof(everyOtherOne), 2); pmap.setRaw(everyOtherOne, sizeof(everyOtherOne)); size_t ones = 0; From b4602ff881344a6270566167cb036c5f33c1aff3 Mon Sep 17 00:00:00 2001 From: Magnaibayar Ganzorig Date: Fri, 11 Sep 2026 19:47:26 -0400 Subject: [PATCH 3/3] Add an optional CMake build QuickFAST is built with MPC, which asks the user to install MPC itself and to point setup.sh or setup.cmd at a Boost and a Xerces-C tree by hand. This adds a CMake build alongside it for people who would rather let find_package locate the dependencies, and so that QuickFAST can be consumed by the many projects that expect a CMake package. Nothing about the MPC build changes: no .mpc, .mpb, .mwc, .features or setup script is touched, and both builds work from the same working copy. The library target collects the same five directories the MPC project does (Application, Codecs, Common, Communication, Messages) and globs them for the same reason MPC lists directories rather than files, so that the two builds do not drift apart as sources are added. The definitions that src/Common/QuickFAST_Export.h expects are supplied for shared and for static builds, and the precompiled header the MPC build uses is kept. Tests and examples are off by default: cmake -S . -B build -DQUICKFAST_BUILD_TESTS=ON -DQUICKFAST_BUILD_EXAMPLES=ON cmake --build build ctest --test-dir build --output-on-failure The unit tests find their XML templates through $QUICKFAST_ROOT, so ctest sets it for them. Installing exports a QuickFAST::QuickFAST target, letting a downstream project build against QuickFAST with nothing more than: find_package(QuickFAST REQUIRED) target_link_libraries(app PRIVATE QuickFAST::QuickFAST) Headers install under include/QuickFAST so that the include style used throughout the sources -- #include -- keeps working without putting directories named Common or Messages on a consumer's include path. Verified on Ubuntu 24.04 (gcc 13, Boost 1.83, Xerces-C 3.2) and Ubuntu 25.10 (gcc 15, Boost 1.88): static and shared builds, all 112 unit test cases, all six examples, and a separate consumer project built against the installed package. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017BnyHwonMsq7k6uQxWpVGF --- .gitignore | 8 ++ CMakeLists.txt | 167 +++++++++++++++++++++++++++++++++ cmake/QuickFASTConfig.cmake.in | 13 +++ src/Examples/CMakeLists.txt | 35 +++++++ src/Tests/CMakeLists.txt | 37 ++++++++ 5 files changed, 260 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/QuickFASTConfig.cmake.in create mode 100644 src/Examples/CMakeLists.txt create mode 100644 src/Tests/CMakeLists.txt diff --git a/.gitignore b/.gitignore index ea7935f1..98804cd9 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,14 @@ Makefile* *.sln make +# CMake +/build*/ +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +CTestTestfile.cmake +CMakeUserPresets.json + # Artifacts *.swp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..52a5fc56 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,167 @@ +# Copyright (c) 2009, Object Computing, Inc. +# All rights reserved. +# See the file license.txt for licensing information. +# +# Optional CMake build for QuickFAST. +# +# This is an *addition* to the existing MPC build, not a replacement: no MPC +# file, setup script or source file is modified by this build system, and both +# builds can be used side by side from the same working copy. +# +# Quick start: +# cmake -S . -B build -DQUICKFAST_BUILD_TESTS=ON +# cmake --build build +# ctest --test-dir build --output-on-failure +# +# Boost and Xerces-C are located with find_package, so they can come from the +# platform packages, from a local build or from a package manager. + +cmake_minimum_required(VERSION 3.16) + +project(QuickFAST + VERSION 1.5.0 + DESCRIPTION "An implementation of the FAST protocol for native C++" + HOMEPAGE_URL "https://github.com/objectcomputing/quickfast" + LANGUAGES CXX) + +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + +option(QUICKFAST_BUILD_TESTS "Build the QuickFAST unit tests" OFF) +option(QUICKFAST_BUILD_EXAMPLES "Build the QuickFAST example programs" OFF) +option(QUICKFAST_USE_PCH "Use a precompiled header (mirrors the MPC build)" ON) + +# QuickFAST predates C++11. C++11 is the lowest standard that current Boost +# releases support, so it is used as the floor rather than the compiler default. +if(NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) +endif() + +# CMake 3.30 removed its own FindBoost in favour of the config file Boost +# itself installs. Ask for that behaviour where the policy exists. +if(POLICY CMP0167) + cmake_policy(SET CMP0167 NEW) +endif() + +find_package(Boost REQUIRED COMPONENTS system thread date_time) +find_package(XercesC REQUIRED) + +# --------------------------------------------------------------------------- +# The QuickFAST library +# +# The MPC build collects whole directories (see src/QuickFAST.mpc), so globbing +# is used here to keep the two builds from drifting apart when files are added. +# --------------------------------------------------------------------------- +file(GLOB_RECURSE QUICKFAST_SOURCES CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/src/Application/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Codecs/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Common/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Communication/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Messages/*.cpp) + +file(GLOB_RECURSE QUICKFAST_HEADERS CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/src/Application/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/Codecs/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/Common/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/Communication/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/Messages/*.h) + +add_library(QuickFAST ${QUICKFAST_SOURCES} ${QUICKFAST_HEADERS}) +add_library(QuickFAST::QuickFAST ALIAS QuickFAST) + +target_include_directories(QuickFAST PUBLIC + $ + $) + +target_link_libraries(QuickFAST PUBLIC + Boost::system + Boost::thread + Boost::date_time + XercesC::XercesC) + +set_target_properties(QuickFAST PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + OUTPUT_NAME QuickFAST) + +# src/Common/QuickFAST_Export.h decides between dllexport, dllimport and +# nothing. These definitions give it the same answers the MPC build does. +if(BUILD_SHARED_LIBS) + target_compile_definitions(QuickFAST PRIVATE QUICKFAST_BUILD_DLL) +else() + target_compile_definitions(QuickFAST PUBLIC QUICKFAST_HAS_DLL=0) +endif() + +if(MSVC) + target_compile_definitions(QuickFAST PUBLIC + BOOST_DATE_TIME_NO_LIB + BOOST_REGEX_NO_LIB + _WIN32_WINNT=0x0601) + # QuickFAST predates several of the warnings current MSVC emits; the + # precompiled header already disables the ones the authors reviewed. + target_compile_options(QuickFAST PRIVATE /wd4267 /wd4244) +else() + # FieldInstructionInteger generates spurious warnings based on + # signed/unsigned template arguments (see QuickFASTApplication.mpb). + target_compile_options(QuickFAST PRIVATE -Wtype-limits) +endif() + +if(QUICKFAST_USE_PCH) + target_precompile_headers(QuickFAST PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src/Common/QuickFASTPch.h) +endif() + +# --------------------------------------------------------------------------- +# Installation and the QuickFASTConfig.cmake package +# --------------------------------------------------------------------------- +install(TARGETS QuickFAST + EXPORT QuickFASTTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +# Headers are installed under a QuickFAST/ prefix so that the include style used +# throughout the sources -- #include -- keeps working without +# putting directories named Common or Messages on a consumer's include path. +foreach(module Application Codecs Common Communication Messages) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/${module} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/QuickFAST + FILES_MATCHING PATTERN "*.h") +endforeach() + +install(EXPORT QuickFASTTargets + FILE QuickFASTTargets.cmake + NAMESPACE QuickFAST:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/QuickFAST) + +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/QuickFASTConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/QuickFASTConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/QuickFAST) + +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/QuickFASTConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/QuickFASTConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/QuickFASTConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/QuickFAST) + +# --------------------------------------------------------------------------- +# Optional components +# --------------------------------------------------------------------------- +if(QUICKFAST_BUILD_TESTS) + enable_testing() + add_subdirectory(src/Tests) +endif() + +if(QUICKFAST_BUILD_EXAMPLES) + add_subdirectory(src/Examples) +endif() diff --git a/cmake/QuickFASTConfig.cmake.in b/cmake/QuickFASTConfig.cmake.in new file mode 100644 index 00000000..c4ccfcd2 --- /dev/null +++ b/cmake/QuickFASTConfig.cmake.in @@ -0,0 +1,13 @@ +# Copyright (c) 2009, Object Computing, Inc. +# All rights reserved. +# See the file license.txt for licensing information. +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +find_dependency(Boost COMPONENTS system thread date_time) +find_dependency(XercesC) + +include("${CMAKE_CURRENT_LIST_DIR}/QuickFASTTargets.cmake") + +check_required_components(QuickFAST) diff --git a/src/Examples/CMakeLists.txt b/src/Examples/CMakeLists.txt new file mode 100644 index 00000000..0b67e7fc --- /dev/null +++ b/src/Examples/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright (c) 2009, 2010, 2011, Object Computing, Inc. +# All rights reserved. +# See the file license.txt for licensing information. +# +# The QuickFAST example programs (see src/Examples/Examples.mpc). + +# Code shared by all of the examples; QuickFASTExample.mpb folds these sources +# into every example program, so a static helper library is used here instead. +file(GLOB QUICKFAST_EXAMPLES_SUPPORT CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/Examples/*.cpp) + +add_library(QuickFASTExamplesSupport STATIC ${QUICKFAST_EXAMPLES_SUPPORT}) +target_include_directories(QuickFASTExamplesSupport PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(QuickFASTExamplesSupport PUBLIC QuickFAST) + +if(QUICKFAST_USE_PCH) + target_precompile_headers(QuickFASTExamplesSupport PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/Examples/ExamplesPch.h) +endif() + +foreach(example + InterpretApplication + TutorialApplication + PerformanceTest + FileToTCP + FileToMulticast + PCapToMulticast) + file(GLOB ${example}_SOURCES CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/${example}/*.cpp) + add_executable(${example} ${${example}_SOURCES}) + target_link_libraries(${example} PRIVATE QuickFASTExamplesSupport) + if(QUICKFAST_USE_PCH) + target_precompile_headers(${example} REUSE_FROM QuickFASTExamplesSupport) + endif() +endforeach() diff --git a/src/Tests/CMakeLists.txt b/src/Tests/CMakeLists.txt new file mode 100644 index 00000000..32832d9c --- /dev/null +++ b/src/Tests/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright (c) 2009, Object Computing, Inc. +# All rights reserved. +# See the file license.txt for licensing information. +# +# The QuickFAST unit tests (see the *test project in src/QuickFAST.mpc). + +find_package(Boost REQUIRED COMPONENTS unit_test_framework filesystem system thread date_time) + +file(GLOB QUICKFAST_TEST_SOURCES CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) + +add_executable(QuickFASTTest ${QUICKFAST_TEST_SOURCES}) + +target_link_libraries(QuickFASTTest PRIVATE + QuickFAST + Boost::unit_test_framework + Boost::filesystem) + +# Boost.Test wants BOOST_TEST_DYN_LINK when it is a shared library, and must +# not have it when it is static: the definition decides whether the test +# runner's main() comes from the library or from the test binary. +get_target_property(quickfast_boost_test_type Boost::unit_test_framework TYPE) +if(quickfast_boost_test_type STREQUAL "SHARED_LIBRARY") + target_compile_definitions(QuickFASTTest PRIVATE BOOST_TEST_DYN_LINK) +endif() + +if(QUICKFAST_USE_PCH) + target_precompile_headers(QuickFASTTest PRIVATE + ${CMAKE_SOURCE_DIR}/src/Common/QuickFASTPch.h) +endif() + +add_test(NAME QuickFASTTest COMMAND QuickFASTTest) + +# Several tests locate their XML templates through $QUICKFAST_ROOT rather than +# through the working directory, so the variable is supplied to ctest here. +set_tests_properties(QuickFASTTest PROPERTIES + ENVIRONMENT QUICKFAST_ROOT=${CMAKE_SOURCE_DIR})