I have three closely related applications that are build from the same source code - let's say APP_A, APP_B, and APP_C. APP_C is a superset of APP_B which in turn is a superset of APP_A.
So far I've been using a preprocessor define to specify the application being built, which has worked like this.
// File: app_defines.h
#define APP_A 0
#define APP_B 1
#define APP_C 2
My IDE build options then specify (for example)
#define APPLICATION APP_B
... and in source code, I will have things like
#include "app_defines.h"
#if APPLICATION >= APP_B
// extra features for APPB and APP_C
#endif
However, I shot myself in the foot this morning and wasted far to much time by simply omitting the line to #include "app_defines.h" from one file. Everything compiled fine, but the application crashed with AVs at startup.
I'd like to know what a better way of handling this would be. Previously, This would normally one of the few times when I'd consider #define could be used (in C++, anyway), but I still goofed up badly and the compiler didn't protect me.
You don't always have to force inheritance relationships in applications that share a common code base. Really.
There's an old UNIX trick where you tailor the behavior of you application based on argv[0], ie, the application name. If I recall correctly (and it's been 20 years since I looked at it), rsh and rlogin are/were the same command. You simply do runtime configuration based on the value of argv[0].
If you want to stick with build configuration, this is the pattern that is typically used. Your build system/makefile defines a symbol on the command like, APP_CONFIG to be a non-zero value then you have a common include file with the configuration nuts and bolts.
#define APP_A 1
#define APP_B 2
#ifndef APP_CONFIG
#error "APP_CONFIG needs to be set
#endif
#if APP_CONFIG == APP_A
#define APP_CONFIG_DEFINED
// other defines
#endif
#if APP_CONFIG == APP_B
#define APP_CONFIG_DEFINED
// other defines
#endif
#ifndef APP_CONFIG_DEFINED
#error "Undefined configuration"
#endif
This pattern enforces that the configuration is command line defined and is valid.
What you are trying to do seems very similar to "Product lines". Carnigie Melon University has an excellent page on the pattern here: http://www.sei.cmu.edu/productlines/
This is basically a way to build different versions of one piece of software with different capabilities. If you imagine something like Quicken Home/Pro/Business then you are on track.
While that may not be exactly what you attempting, the techniques should be helpful.
It sounds to me that you might look at modularizing your code into separately-compiled elements, building the variants from a selection of common modules and a variant-specific top-level (main) module.
Then control which ones of these parts go into a build by which header files are used in compiling the top level and which .obj files you include into the linker phase.
You might find this a bit of a struggle at first. In the long run you should have a more reliable and verifiable construction and maintenance process. You should also be able to do better testing without worrying about all the #if variations.
I'm hoping that your application is not terribly large just yet and unraveling a modularization of its functions won't have to deal with a big ball of mud.
At some point you might need run-time checks to verify that the build used consistent components for the application configuration you intended, but that can be figured out later. You can also achieve some compile-time consistency checking, but you'll get most of that with header files and signatures of entry points into the subordinate modules that go into a particular combination.
This is the same game whether you are using C++ classes or operating pretty much at the C/C++ common-language level.
If you're using C++, shouldn't your A, B, and C applications inherit from a common ancestor? That would be the OO way to solve the problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With