This post is a continuation of a series related to product configuration management (PCM) patterns. This post discusses the implementation, features and drawbacks of the build-time PCM pattern. As with the code-time PCM pattern, security is the dominant benefit of the build-time PCM pattern. In fact, the results of the two patterns are virtually indistinguishable once past the build phase. The end-user has no ability to enable latent features, given that only those features included in the configuration will be present in the released product.
The build-time PCM pattern takes a slightly different approach to managing configurations than the code-time PCM pattern. Instead of duplicating the code base for each configuration and modifying it as needed, the build-time PCM pattern instead identifies the specific code segments that differ between configurations, and wraps those code segments within conditional preprocessor blocks. This allows the same code base to be recompiled with different preprocessor variables set to alter the compilation process such that different segments of code are included.
Using the example product Make-Me-Money from my previous post titled “Code-Time PCM Pattern“, I’ll explain how the build-time PCM pattern is implemented, and describe the required workload for managing configurations with this pattern. The following example code demonstrates how conditional compilation statements can be used to control the inclusion of two feature-related code segments.
// Add menu items to help menu AddHelpMenuItem("Make-Me-Money Help", MainHelpRoutine); AddHelpMenuItem("Register", RegistrationRoutine); #if defined(VIDEOS) AddHelpMenuItem("Financial Videos", ShowVideosRoutine); #endif #if defined(TUTORIALS) AddHelpMenuItem("Tutorials", ShowTutorialsRoutine); #endif AddHelpMenuItem("About Make-Me-Money", AboutRoutine);
Using the two pre-processor variables (VIDEOS and TUTORIALS), the same code base can now be compiled in four different ways as follows:
- Neither variable defined – product will not include video or tutorial functionality
- VIDEOS defined – product will include video functionality, but no tutorials
- TUTORIALS defined – product will include tutorial functionality, but no videos
- Both variables defined – product will include videos and tutorials
Currently, Product Management has only defined configurations that include both videos and tutorials (better and best configurations), or that include neither (good configuration). Given that, it would be possible to accomplish the objectives using a single variable as shown here:
// Add menu items to help menu AddHelpMenuItem("Make-Me-Money Help", MainHelpRoutine); AddHelpMenuItem("Register", RegistrationRoutine); #if defined(TRAINING_MATERIALS) AddHelpMenuItem("Financial Videos", ShowVideosRoutine); AddHelpMenuItem("Tutorials", ShowTutorialsRoutine); #endif AddHelpMenuItem("About Make-Me-Money", AboutRoutine);
In this case, a variable named TRAINING_MATERIALS was used to control the inclusion of both videos and tutorials. While this is sufficient to handle the current product configuration needs, it constrains future configuration possibilities. Using a single variable means that it is not possible to produce a configuration that includes videos but not tutorials, or vice-versa. If Product Management later decides that they want to create such a configuration, it will require a Software Engineer to go through the entire code base looking for instances of TRAINING_MATERIALS, and manually splitting up the code segments for the new VIDEOS and TUTORIALS variables.
It is relatively easy to use multiple variables in the first place, and doing so would avoid significant engineering work later to create different configurations. While in many cases, this is the best approach to take, there are some factors to consider when making such a choice. For instance, the financial videos and tutorials might be very closely related, or contain references to each other. In such cases, it would never make sense to separate them, so a single variable would be more appropriate. Also, keep in mind that each additional variable is another factor to be managed for every configuration. For a large, complex product, the number of such configuration variables could reach hundreds or even thousands if their creation is not closely monitored and controlled. This would inevitably create a large burden on those managing the configuration settings.
Here are some guidelines that can be used to help determine when and how to define configuration variables:
- Name configuration variables according to the optional functionality that they control.
- Do not begin variable names with a negation like DISABLE_VIDEOS, as this leads to double-negatives when discussing such variables (eg. Please disable DISABLE_VIDEOS) or in source code (#if !defined(DISABLE_VIDEOS)).
- Do not define variables that represent currently defined configurations (a variable named BETTER is a poor choice, since the definition of the better configuration is likely to change over time).
- Do not group unrelated functionality under the same configuration variable.
- Do not separate interdependent code segments under different configuration variables when possible.
- Attempt to minimize the expected amount of work when deciding whether to group code segments under one variable or separate them under multiple variables. If grouped under one variable, a significant amount of work might be involved to separate them later, so consider the likelihood of that. If split under multiple variables, a small amount of work will be involved in managing each additional variable for every configuration, which can add up over time.
When compared to the code-time PCM pattern, the primary advantage of the build-time PCM pattern is that it allows a single copy of the code base to be used for all configurations. This is a tremendous advantage that can drastically reduce the amount of work required to set up and manage the various configurations. It also shifts some of the work away from Software Engineers and toward Build Engineers. Having a single-code base can also streamline testing, reduce the number of duplicate bugs, and eliminate the need to integrate bug fixes to multiple branches.
In the process for implementing the code-time PCM pattern, roughly the same amount of work was involved in the creation of each new configuration. However, for the build-time PCM pattern, the first new configuration will require some preparation work that can be leveraged for later configurations. This preparation work involves the insertion of conditional preprocessor directives to include code segments based on the settings of certain configuration variables. Once this work is done for the first configuration, it need not be repeated for later configurations, although some additional work may be necessary if new configuration variables are needed.
Just like with the code-time PCM pattern, the next step in the process is to create the customized resources necessary for the new configurations. This includes graphics work to create new splash screens, documentation work to draft new versions of the help files, and UI resource work to re-brand for the new product names. Additional build and installer work will also be needed to incorporate these customized resources into the builds and installers for the respective configurations. These activities consume about 80 person-hours per product configuration.
Since a single code base is being used for all configurations with a small number of code segment differences between configurations, it is likely that the testing process can be better optimized to take advantage of this fact. The compiled results from the code-time and build-time PCM patterns should be the same. However, since the code-time PCM pattern uses physically separate copies of the code base, there is generally a lower level of confidence that they will be kept in sync. The build-time PCM pattern provides a better guarantee in this area, so test plans can be structured with one set of tests to cover common functionality, and another set to test configuration-specific functionality. Any bugs found in the common functionality tests will only need to be entered once. Those found in the configuration-specific tests will need to be entered against those configurations for which the functionality is included. For the purposes of this example, I’ll assume that the common functionality tests take 6 hours to complete and identify 90% of the bugs. The configuration-specific tests take 2 hours each to complete and identify 10% of the bugs.
With three product configurations, the number of bugs entered when using the build-time PCM pattern should be a little more than 1/3 of that for the code-time PCM pattern. While most of the reduction is due to the elimination of duplicate bugs, it still saves a significant amount of time related to bug management and tracking. Also, almost all of the bugs entered can be fixed in one place in the code base, and no integrations to other branches are required. This nearly eliminates the development work involved in managing configurations beyond the original set up of the preprocessor conditional statements.
Following is a summary of the effort involved in handling multiple product configurations using the build-time PCM pattern:
|Worker Time||Machine Time||Work Description|
|40 hours||Insert conditional preprocessor directives|
|40 hours||Sub-total – preparation effort|
|80 hours||Adding customized resources|
|10 hours||Test preparation|
|90 hours||Sub-total – configuration set up effort|
|4 hours||Time required to build configuration|
|40 hours||Testing time for 20 QA Engineers for each sweep|
|1 hour||Time for 20 Software Engineers to integrate bug fixes|
|41 hours||4 hours||Sub-total – Test/fix cycle|
|540 hours||40 hours||Total effort assuming 10 test/fix cycles|
While this is a significant improvement in the level of effort when compared to the code-time PCM pattern, it is still a large amount of work for managing product configurations. According to the above, the first configuration produced will require 540 hours of effort, while subsequent configurations will require about 500 hours since they don’t need to repeat the preparation effort. Overall, the build-time PCM pattern has much greater sustainability than the code-time PCM pattern with a significant reduction in effort for managing configurations. It is well suited for small to medium-sized products where the differences between the configurations affect a relatively small portion of the code base.