Skip to content

Choosing a Practical Toolchain

Toolchain choice is an engineering decision over target platforms, team familiarity, dependency shape, IDE needs, reproducibility, and distribution. Start with the smallest setup that expresses the real constraints and can grow without replacing every mental model.

A Windows desktop application

Use Visual Studio with MSVC and the Windows SDK. Choose either native MSBuild projects for deeply Windows-specific applications or CMake presets opened by Visual Studio when portability or non-IDE CI matters. Use vcpkg or an organizational NuGet/vcpkg workflow for native dependencies. Add clang-cl as a second diagnostic compiler when valuable.

This provides the batteries-included experience: installer-managed toolsets, debugger, profiler, tests, CMake/Ninja, and SDK integration. Still version the build description and avoid developer-only property changes.

A portable C++ library

Use target-oriented CMake, C++17 or C++20 according to consumers, and CI across GCC, Clang/libc++ where feasible, MSVC, and Apple Clang. Use Conan or vcpkg when binary variants and registries matter; use system packages only where deployment policy owns them. Install/export the library and run an external consumer test.

Keep the ABI promise explicit. A source-distributed template library and a stable shared library have different compatibility obligations.

A small Unix C utility

Direct compiler commands are enough while learning and for a one-file program. A concise GNU Make build is appropriate when the target environment already standardizes Make and dependencies are simple. Use pkg-config for system libraries. Add CMake only when cross-platform generation, installation, testing, or dependency integration solves a concrete need.

An embedded or freestanding project

Start from the silicon/board vendor's supported compiler, SDK, linker scripts, startup code, flashing tools, and debugger probe. Wrap those commands in CMake or Make only after understanding the vendor flow. Cross-toolchain files, memory maps, section placement, and hardware-in-the-loop tests matter more than desktop package-manager fashion.

Avoid assuming the full standard library, filesystem, threads, exceptions, RTTI, or dynamic allocation are available or desirable. Record the supported subset.

A large organization or monorepo

CMake may remain suitable, but Bazel, Buck2, Meson, or an internal build system can add hermeticity, distributed caching, and large-graph performance. Migration cost, IDE support, generated code, foreign-language integration, and package publication deserve prototypes and measurements.

Do not introduce a large-scale build system to solve one missing compiler flag.

A learning repository

Build the same small program four ways:

  1. direct compiler commands;
  2. Make with generated header dependencies;
  3. CMake with Ninja;
  4. CMake opened in Visual Studio, Xcode, or CLion.

The repetition reveals that source files, definitions, include requirements, link relationships, and runtime dependencies remain constant while orchestration changes.

A selection checklist

  • What operating systems, architectures, and ABIs must ship?
  • Is the deliverable an application, source library, binary library, firmware, or plugin?
  • Which compiler/SDK is authoritative for each target?
  • How are dependencies acquired, locked, cached, and patched?
  • Must consumers use a particular IDE or build system?
  • What CI configurations prove support?
  • What is the install/deployment story?
  • Which parts must work offline or under cross-compilation?

Pick defaults, document exceptions, and reassess when constraints change.

Optional prompts

Explain: Why might Make be better than CMake for one small Unix-only C utility?

Answer: If the platform and dependencies are fixed, a short explicit graph may be easier to understand and maintain; CMake's generation and package model may solve no current problem.

Explain: Why begin an embedded project with the vendor workflow?

Answer: It encodes target compiler, SDK, startup objects, linker script, flashing, and debug probe details. Replacing it before understanding those inputs risks losing essential platform behavior.

Further reference