Do dev, UAT and prod differences belong in the build configuration, or at runtime?
howto · cpp, cmake, configuration, deployment
At runtime. A build configuration (Debug, Release, sanitizers on) changes what the compiler emits; an environment (which endpoint, which key) changes nothing about the code. Read the environment once at startup into a struct, from an environment variable, a file or an argument, and keep one binary for all three.
They look like the same kind of choice, “which variant am I building”, and CMake offers a mechanism, CMAKE_BUILD_TYPE plus a preset, that is tempting to reuse. They are not the same axis. A build configuration is fixed the moment the build finishes, and a different one means a different binary. An environment leaves the binary correct in all three places, so it belongs to whatever channel the deployment already has:
struct EndpointConfig { std::string url = "https://dev.example.invalid/api"; // the default IS an environment
static EndpointConfig from_environment() { EndpointConfig c; if (const char* v = std::getenv("MYPLUGIN_ENDPOINT")) c.url = v; return c; }};
class Client {public: explicit Client(EndpointConfig config) : config_(std::move(config)) {} // read once, kept const std::string& endpoint() const { return config_.url; }private: EndpointConfig config_;};Building a second binary per environment, with the endpoint baked in through target_compile_definitions, is what produces the “I switched presets and nothing changed” symptom: a preset’s cache variables are written into CMakeCache.txt at configure time, so rebuilding without a fresh configure changes nothing. The fix is not a cache workaround. It is not encoding an environment as a build configuration at all.
The one CMake-level choice that is a build configuration here is whether debug, sanitizer and release flags differ between environments. That is what CMakePresets.json is for, and Visual Studio and VS Code both read it natively. Commit it; anything local, such as a generator only one machine has, goes in CMakeUserPresets.json, which stays out of version control, the split C# keeps between appsettings.json and appsettings.Development.json.
In C# this is ASPNETCORE_ENVIRONMENT plus appsettings.{Environment}.json bound through IConfiguration: one build, the environment selected by what is loaded at startup, never by what dotnet build was asked for.