Skip to content

Modern build: Docker, vcpkg and CMake, and a build that works on current Boost - #1

Merged
MagnaibayarMN merged 8 commits into
masterfrom
modern-build
Sep 12, 2026
Merged

MagnaibayarMN merged 8 commits into
masterfrom
modern-build

Conversation

@MagnaibayarMN

@MagnaibayarMN MagnaibayarMN commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Makes QuickFAST buildable again on a machine bought this decade, and turns "get QuickFAST compiling" from a day of archaeology into one command.

docker build -t quickfast .
docker run --rm quickfast -?

That is the whole setup. No MPC, no Boost, no Xerces-C, no environment variables, nothing installed on the host.

The problem this solves

QuickFAST's build instructions have not moved since 2017, and they were already pointing at the past then. setup.sh still defaults to this:

export BOOST_ROOT=~/boost/boost_1_38_0     # Boost 1.38 — February 2009
export BOOST_VERSION=boost-1_38
export XERCES_ROOT=~/xerces/xerces-c-3.0.1 # Xerces-C 3.0.1 — 2008
export XERCES_LIBNAME=xerces-c-3.0

So a newcomer has three ways to go, and until now all three ended badly:

Use the Boost your distribution ships. Every current distribution is on Boost 1.74 or later. The build dies immediately:

/usr/include/boost/asio/io_service.hpp:27:20: error: conflicting declaration
  'typedef class boost::asio::io_context boost::asio::io_service'

followed by a few hundred cascading errors. Nothing in the error text suggests the cause, and the cause is one line in AsioService_fwd.h that forward declares an Asio type Asio itself has redefined.

Build the Boost that setup.sh names. Boost 1.38 is from 2009 and does not compile with a current gcc or clang. Neither does Xerces-C 3.0.1. You are now debugging two dependencies before you have compiled a line of QuickFAST.

Find some Boost in between. There is a narrow window that works — and finding it means bisecting sixteen years of Boost releases, because Asio changed in at least four distinct ways over that span:

Boost What happens to QuickFAST
below 1.66 builds, if your compiler is old enough to build Boost itself
1.66 – 1.86 io_service became a typedef; QuickFAST's forward declaration conflicts with it
1.87 and later the io_service name is gone entirely, along with work, resolver::query and from_string
recent releases Asio's date_time based timers became opt-in, so deadline_timer disappears too

And that is only Boost. Xerces-C moved from 3.0 to 3.2, so the library name setup.sh exports is wrong as well, and MPC itself is a separate tool that has to be fetched and put on $PATH before any of this starts.

The result is that a library which is genuinely useful — a mature, fast, permissively licensed FAST decoder — is effectively unavailable to anyone who is not willing to spend a day on it. That is the barrier this PR removes.

What it does

Seven commits, each self-contained:

1. Build against current Boost releases. The actual source fixes, every one of them selected on BOOST_VERSION so that older Boost builds exactly as it did before. The root cause is the forward declaration described above; the rest follows from it — AsioService now offers the get_executor() that Asio's I/O objects ask for, and a new header, Communication/AsioCompatibility.h, spells make_address, post, restart and executor_work_guard the way the Boost being compiled against expects, so the call sites do not each carry a version test. TCPReceiver uses the results-range form of resolve, and the two burst-sender examples use steady_timer and bind_executor. Verified against Boost 1.83, 1.88 and 1.91.

2. An optional CMake build. find_package locates Boost and Xerces-C instead of a hand-edited script naming absolute paths. Installing exports a target, so a downstream project needs two lines:

find_package(QuickFAST REQUIRED)
target_link_libraries(app PRIVATE QuickFAST::QuickFAST)

3. A vcpkg manifest and CMake presets. This is what makes the build reproducible rather than merely possible. The manifest pins a vcpkg baseline by commit, so cmake --preset vcpkg builds against one exact Boost and one exact Xerces-C — the same ones on your laptop, on CI, and in two years' time. Version drift stops being something you manage and starts being something recorded in a file.

4. A Dockerfile. Multi-stage: vcpkg pinned to that same baseline builds the dependencies, QuickFAST and its tests are built and run, and the example programs land in a 121 MB runtime image. Boost from source is slow, so the vcpkg downloads and built packages live in BuildKit cache mounts — after the first build only QuickFAST itself recompiles.

5. A GitHub Actions workflow. Linux with gcc and clang, macOS, Windows with MSVC, the Docker image, and a small project built against the installed CMake package. One job regenerates the MPC makefiles, so the original build cannot rot unnoticed.

6. A development container. The toolchain, vcpkg and gdb, ready for an editor that understands dev containers.

7. README build instructions. CMake, vcpkg, Docker and MPC, side by side.

What it does not do

The MPC build is untouched. No .mpc, .mpb, .mwc, .features or setup script is modified. Both builds work from the same working copy, and CI checks that the MPC project files still generate. This is an addition, not a migration — anyone with a working MPC setup keeps it.

Verification

Every claim above was built and run, not assumed:

Environment Boost Result
Ubuntu 24.04, gcc 13 1.83 (distribution) library, 6 examples, 112 test cases pass
Ubuntu 25.10, gcc 15 1.88 (distribution) library, 6 examples, 112 test cases pass
Docker image, vcpkg 1.91 (built from source) library, 6 examples, 112 test cases pass

Also checked: static and shared library builds, cmake --install followed by a separate consumer project linking QuickFAST::QuickFAST, and docker run --rm quickfast producing InterpretApplication's usage output.

The macOS and Windows CI jobs have not run yet — this PR is the first time they will.

Upstream

The two commits that matter most to everyone else are already proposed to the original project:

The remaining five are held back deliberately: that repository has not seen a commit since March 2017, and seven simultaneous pull requests would bury a maintainer rather than help one. If those two land, the rest follow.

🤖 Generated with Claude Code

https://claude.ai/code/session_017BnyHwonMsq7k6uQxWpVGF

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017BnyHwonMsq7k6uQxWpVGF
@MagnaibayarMN MagnaibayarMN changed the title Modern build Modern build: Docker, vcpkg and CMake, and a build that works on current Boost Sep 12, 2026
magnaimn and others added 4 commits September 11, 2026 21:49
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017BnyHwonMsq7k6uQxWpVGF
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 <Codecs/Decoder.h> -- 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017BnyHwonMsq7k6uQxWpVGF
Boost and Xerces-C still have to be installed before QuickFAST can be built.
This adds a vcpkg manifest so that vcpkg can build them instead, pinned to a
baseline so the result does not change when Boost publishes a new release,
and a presets file so that neither route needs a command line full of cache
variables:

  cmake --preset default    # Boost and Xerces-C from the platform
  cmake --preset vcpkg      # Boost and Xerces-C built by vcpkg

The manifest lists the Boost modules the sources actually include rather than
depending on Boost as a whole.  Boost.Test and Boost.Filesystem, which only
the unit tests use, are behind a "tests" feature.

Neither file affects a build that does not ask for it: without --preset and
without the vcpkg toolchain, find_package looks where it always did.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017BnyHwonMsq7k6uQxWpVGF
Builds QuickFAST, runs the unit tests and keeps the example programs in a
small runtime image, so that the project can be built and tried without
installing a toolchain, Boost or Xerces-C on the host:

  docker build -t quickfast .
  docker run --rm quickfast -?

Dependencies come from vcpkg at the baseline pinned in vcpkg.json.  Building
Boost from source is slow, so the downloads and the built packages are kept
in BuildKit cache mounts; after the first build only QuickFAST itself is
recompiled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017BnyHwonMsq7k6uQxWpVGF
magnaimn and others added 3 commits September 11, 2026 22:09
The repository has no automated build, so a change that breaks one platform
is only found when someone happens to build there.  This workflow builds and
tests QuickFAST on Linux with gcc and clang, on macOS and on Windows with
MSVC, builds the Docker image and runs it, and builds a small consuming
project against the installed CMake package so that the export stays usable.

The last job regenerates the MPC makefiles.  The CMake build is an addition,
not a replacement, and this keeps the MPC project files from quietly rotting.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017BnyHwonMsq7k6uQxWpVGF
Gives an editor that understands dev containers a ready toolchain: a C++
compiler, CMake, Ninja, gdb, perl for the MPC build, and vcpkg at the
baseline pinned in vcpkg.json.  QUICKFAST_ROOT is set for the unit tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017BnyHwonMsq7k6uQxWpVGF
The getting started wiki page assumes MPC and a hand-edited setup script.
This documents the CMake, vcpkg and Docker routes alongside it, so that the
first thing a reader finds is a build that works without any setup.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017BnyHwonMsq7k6uQxWpVGF
@MagnaibayarMN
MagnaibayarMN merged commit d7820c3 into master Sep 12, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants