Thursday, December 7, 2023

Visual Studio conditional compile any file contents

The MSBuild tools and Visual Studio provide a variety of techniques for conditionally compiling source code based upon the active configuration. The #if preprocessor directives allow lines of code to included or excluded based upon defined symbols. The Condition element in project files allows whole files to be included or excluded from the build.

Using #if is particularly useful, but it only works inside C# source code files. Over recent years I have increasingly wanted to apply conditional compilation to other types of project text files such as .html, .js, .txt, etc, which need slightly different contents according to the active configuration. Sometimes I have to manually alter the contents of the files before publishing from Visual Studio to different hosts with different configurations. Editing the files manually is tedious and error-prone. In late 2023 I finally got fed-up with this and found a way of automatically editing the contents of arbitrary text files in a build, based upon the configuration.

Note that if the contents of the text files differed significantly for different configuration builds then it could be better to maintain separate files and use Condition to include the desired files. Unfortunately for me, usually only a few lines might change in the text files, so a #if technique would be preferable.

A skeleton sample C# solution is available which demonstrates all the techniques and tricks required to make this work. There are comments prefixed with a ▅ character (easy to see!) in all important parts of the solution files to explain what is happening. Full source is available from this Azure DevOps repository:

ConditionBuildDemo

In a nutshell, the key trick to making this work is to use a T4 template (a .tt file) to generate each text file that must be customised in some way for different build configurations. I feel this is a little bit clumsy, but in consolation, this is exactly why T4 templates were invented and they also blend smoothly into Visual Studio projects.

Another trick is to pass the name of the current $(Configuration) into the templates so they can be used in the generation logic. The rather obscure <T4ParameterValues> project element can be used for that.

Yet another trick is to make the templates generate when the project starts to build, not just when the template files change. The <Touch> and <TransformOnBuild> projects elements help with that.

There are a few more small technical details than I haves skipped for brevity, but the comments in the solution files explain everything.