Sunday, July 17, 2011

Different pre-build and post-build event scripts in C#


Custom build events are like a bucket of water – you don’t need one until you get some fire around. In most cases it is not necessary to do something special, for example, before or after your project is built. So that events are not widely known but they should, at least to tackle it properly in case of fire in your project. Luckily, this skill is not tough stuff.

There is only one. Woops...

Several years ago I had some experience with post-build events at C++. We composed dedicated scripts (actually BAT files) to arrange extra activity around successfully built binaries. Debug and release configurations were different a bit so we provided two versions of base script tuned according to debug and release builds appropriately. That was absolutely OK to fit our needs.
Recently, one of our C# projects turned us into situation where post-build event is a must. With no worries I started Visual Studio and went to “Build Events” tab. But wait, what’s that? “Configuration” combo box is disabled. There is no “Debug/Release/etc” content, just grayed “N/A” mark. But our script should be altered according to target configuration!
After a while, you may agree with Microsoft. It is a good thing to have one unified script instead of two different versions because with time given, the scripts will be more and more different. Hard to maintain, not handy to remember. The one and only source of allowed variations is “Macros” button. It contains several pre-defined variables, their actual values depend on current configuration:
    $(OutDir)
    $(ConfigurationName)
    $(TargetPath)
    ...etc...

Is it helpful? Well, umm… Just a bit. You need more macros. You need your own.

Hand-made macro? Piece of project!

First thing to do before creating new macro is to close your Visual Studio. Maybe, there is some GUI-driven way – I beg you send me a hint if you know. Until then, let open “.csproj” file in your favorite text editor. You would find a part of XML like that:

  ...
  <PropertyGroup condition=" '$(Configuration)$(Platform)' == 'Debug¦AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)$(Platform)' == 'Release¦AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
  </PropertyGroup>
  ...

What we see here is a definition of two configurations, Debug and Release, nothing special. Let find “OutputPath” tag in both configurations. You see? Here is an answer how to make your own macros: declare a tag with some unique name, assign different content for each configuration, save the project file. Apply each macro as separate tag if you need more than one:
  ...
  <PropertyGroup condition=" '$(Configuration)$(Platform)' == 'Debug¦AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
    <!-- Custom macros from here -->
    <MyPrefix>ForDebugOnly</MyPrefix>
    <MySuffix>DevelopersOnly</MySuffix>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)$(Platform)' == 'Release¦AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
    <!-- Custom macros from here -->
    <MyPrefix>ForReleaseOnly</MyPrefix>
    <MySuffix>ReleaseCandidate</MySuffix>
  </PropertyGroup>
  ...


How to use?


Now we can go back to Visual Studio and open our project as usual. Go to project properties, “Build Events” tab. From now, we can compose scripts for pre-build/post-build events with help of newly introduced variables as well as pre-defined ones:

    $(OutDir)
    $(ConfigurationName)
    $(TargetPath)
    ...
    $(MyPrefix)
    $(MySuffix)

Ok, it’s time to make an example.
1) Put following line into “Pre-build event command line” window:

    echo Special note: $(MyPrefix)

2) Put following line into “Post-build event command line” window:

    echo Build mode: $(MySuffix)

We’re ready to do the builds. Let build Debug configuration:


  ------ Build started: Project: BuildEventsExample, Configuration: Debug Any CPU ------
  Special note: ForDebugOnly
  BuildEventsExample -> C:\BuildEventsExample\bin\Debug\BuildEventsExample.exe
  Build mode: DevelopersOnly
  ========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========


And let build Release configuration:


  ------ Rebuild All started: Project: BuildEventsExample, Configuration: Release Any CPU ------
  Special note: ForReleaseOnly
  BuildEventsExample -> C:\BuildEventsExample\bin\Release\BuildEventsExample.exe
  Build mode: ReleaseCandidate
  ========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========


That’s all. It is up to your imagination to employ such macros to alter paths and credentials, delegating sub-script calls etc. Enjoy!

No comments:

Post a Comment