MSBuild 4.0 Custom Task Walkabout Part 3
With the two new Workflow Activities in place, I first make two edits to the properties for WriteBuildMessage: 1) add some random text to Message simply so
the error icon goes away and, 2) set the Importance to High. This latter step is helpful so that the text will be written to the build log even when the logging
verbosity is set to its default Normal level. The alternative would be to increase log verbosity level but that will slow the build down and while in
play-around mode a quick turnaround is important:
Now to the MSBuild activity, the properties for which are a tad more complicated:
The one property that requires a value, at least in order to get rid of the error icon in the workflow designer, is the project that is going to be built.
It expects a string I'm going to set it to that AllInOneTargets.targets file that was used earlier in this walkabout:
VB
System.IO.Path.Combine(SourcesDirectory, "AllInOne\BuildProcessTemplates\CustomTasks\_targets\
AllInOneTargets.targets")
where SourcesDirectory should resolve to the build agent's local build directory at buildtime. With that change the .targets file also needs to be updated for
the new workflow scenario. In the earlier .csproj -> .targets process all that the latter file contained was a UsingTask element but it should now be fully populated so that the
ProduceHashManifest custom task is properly executed:
.targets
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="CTBinaries.ProduceHashManifest" AssemblyFile="..\_compiled\CTBinaries.dll" />
<Target Name="CustomAfterBuild" >
<ProduceHashManifest BinariesFolder="$(OutDir)" />
</Target>
</Project>
With that done, time to check in the process template .xaml...
DON’T FORGET TO CHECK IN YOUR BUILD PROCESS TEMPLATE
It is important to remember that your process template file is just a Workflow Activity XAML file checked in to version control. The build will use the
latest version of that file at run - time. However, both the build definition editor and the Queue Build dialog base what they display on the latest
version of the process template that is checked in to version control, not the version you have locally. While this is obvious, it is surprisingly easy
to forget. You will not be alone if you get caught on this as you start experimenting with customizing build processes. |
- pp501-2 of Professional Application Lifecycle Management with Visual Studio 2010 |
|
Verily, I can attest as to the truthiness of the above warning. To aid in the troubleshooting/testing of build process customization I would also
recommend keeping the 'Pending Changes' window of Visual Studio in view at all times. It may also make sense to set up the trigger for the
build-definition-under-test to Continuous Integration, as the most likely reason for checking in files during the play-around phase will be in order to
test updated Workflow etc. via a new build. |
|
(and AllInOneTargets.targets) and give it a go. And the go fails:
C:\Builds\1\AllInOne\AllInOne_ManualDef1\Sources\AllInOne\BuildProcessTemplates\CustomTasks\_targets\AllInOneTargets.targets
(37): The "ProduceHashManifest" task was not given a value for the required parameter "BinariesFolder".
MSBuild claims the BinariesFolder property, which was [Required] within the custom task .dll, was not passed in. The short reason why is that while the
$(OutDir) reference in the .targets file worked fine when evaluated in the .csproj, in the current flow that property has not been defined. I go looking for
another property that will work out and try a bunch by adding them to the CustomAfterBuild target in the .targets file:
.targets
...
<Message Text="outdir: $(OutDir)" Importance="High" />
<Message Text="BinariesRoot: $(BinariesRoot)" Importance="High" />
<Message Text="BuildDirectory: $(BuildDirectory)" Importance="High" />
<Message Text="MSBuild: $(MSBuild)" Importance="High" />
<Message Text="MSBuildBinPath: $(MSBuildBinPath)" Importance="High" />
etc.
but none of the ones I used evaluated to the correct path. Some had values that perhaps could be parsed down to the output directory value I need, and the
exact property probably does exist, but the issue also provides the opportunity to look at passing properties from the Workflow to the msbuild process. In
order to illustrate that process I will first take a step back and look at a few of the properties available in the MSBuild activity within the Workflow designer.
(some) Properties for MSBuild Workflow Activity
- DisplayName
- simple but very useful for debugging purposes. I will rename the current activity as 'MSBuild of AllInOneTargets'.
- LogFile and LogFileDropLocation
- Targets
- the current AllInOneTargets.targets file only contains a single Target, which msbuild will run since it would then be considered the default
target. But if there are multiple targets in that file I need to be able to specify the ones I want run by this Activity. The Expression Editor
indicates this property expects an 'IEnumerable<String>' and rather than try to figure out what form this would take in VB I go to an exising
MSBuild activity already embedded in the Workflow. From the current location I can go up a couple of levels and click on 'Run MSBuild for
Project'. There I see Targets is actually empty but TargetsNotLogged is defined in a similar manner and has a value of
New String() {"GetNativeManifest", "GetCopyToOutputDirectoryItems", "GetTargetPath"}. Back to 'my' Targets and set value
to New String() {"CustomAfterBuild"}, the name of the Target in AllInOneTargets.targets that holds the call to
ProduceHashManifest.
- CommandLineArguments
- And this is how I will pass the desired output directory value on to the .targets file, where it will then be passed on to the
ProduceHashManifest task (and .dll). That is the only command line modification I need right now so this property gets set to
String.Format("/p:OutputDirectory={0}", outputDirectory).
With so many types of properties floating around (and so many things being referred to as Properties) it can get very confusing as to what
text needs to be used where. This is where the WriteBuildMessage activity that was inserted just before the custom MSBuild comes in handy. Remove
the dummy text there and replace with: |
VB
"bldMessage1 CompilationStatus: " + BuildDetail.CompilationStatus.ToString() + " || " + _
"Binaries directory: " + BinariesDirectory + "sources directory: " + " || " + _
SourcesDirectory + "output directory: " + outputDirectory
|
Once the build has completed, going to 'View Log' from the Build Summary within Visual Studio IDE to see the results.
bldMessage1 CompilationStatus: Unknown || Binaries directory: C:\Builds\1\AllInOne\AllInOne_ManualDef1\Binaries
|| sources directory: C:\Builds\1\AllInOne\AllInOne_ManualDef1\Sources || output directory:
C:\Builds\1\AllInOne\AllInOne_ManualDef1\Binaries
|
The Workflow part of the next test has now been set up but the contents of AllInOneTargets.targets needs to be cleaned up to remove the debugging
Message tasks and configure ProduceHashManifest to expect the new OutputDirectory property that it is being passed on the command line:
.targets
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="CTBinaries.ProduceHashManifest" AssemblyFile="..\_compiled\CTBinaries.dll" />
<Target Name="CustomAfterBuild" >
<ProduceHashManifest BinariesFolder="$(OutputDirectory)" />
</Target>
</Project>
Confirm all relevant files have been checked into TFS and kick off a new build. This one succeeds and the expected hashManifest.txt is written to the build's
output directory.
One area worthy of further investigation concerns the details of the custom MSBuild activity, as recorded by the team build process. Within Visual Studio,
look at the details on the build that just suceeded and click 'View Log' from the Summary screen. Within the Activity Log is a section that relates directly
to the custom flow I inserted:
After the WriteBuildMessage output we can see how the DisplayName (MSBuild of AllInOneTargets) I used in the .xaml
helps delineate where the new MSBuild Activity customization began. The target name, CustomAfterBuild, appears
right below that and also in the actual MSBuild command line, passed
with the /t: parameter. Logfile is set to the name I entered, myTargetsLog.log, without further pathing
since it is being generated in the default log directory. Finally, OutputDirectory value is passed as a standard MSBuild
Property, using /p: syntax.
Next, Configure as an Inline Task...