I'm working on a reusable MSBuild Target that will be consumed by several other tasks. This target requires that several properties be defined. What's the best way to validate that properties are defined, throwing an Error if the are not?
Two attempts that I almost like:
<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="3.5" DefaultTarget="Release" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Release">
<Error
Text="Property PropA required"
Condition="'$(PropA)' == ''"/>
<Error
Text="Property PropB required"
Condition="'$(PropB)' == ''"/>
<!-- The body of the task -->
</Target>
</Project>
Here's an attempt at batching. It's ugly because of the extra "Name" parameter. Is it possible to use the Include attribute instead?
<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="3.5" DefaultTarget="Release" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Release">
<!-- MSBuild BuildInParallel="true" Projects="@(ProjectsToBuild)"/ -->
<ItemGroup>
<RequiredProperty Include="PropA"><Name>PropA</Name></RequiredProperty>
<RequiredProperty Include="PropB"><Name>PropB</Name></RequiredProperty>
<RequiredProperty Include="PropC"><Name>PropC</Name></RequiredProperty>
</ItemGroup>
<Error
Text="Property %(RequiredProperty.Name) required"
Condition="'$(%(RequiredProperty.Name))' == ''" />
</Target>
</Project>
Great question! I have written about this in depth in my book and in a blog post, Elements of Reusable MSBuild Scripts: Validation. My approach will cover properties and items.
Here is the run down. In the shared .targets file create a validation target, and this should be one of the first targets declared in the file so that users can easily locate it.
Properties
Inside the validation target define your properties like this:
<_RequiredProperties Include="Root">
<Value>$(Root)</Value>
</_RequiredProperties>
I place the name of the property in the include and its value inside of the Value
metadata.The reason why I do this is so that I can detect when Value
is blank and then I use the include value to report the name of the missing property back to the user.
Items
Inside the target place the required items inside of an item like:
<_RequiredItems Include="AllConfigurations">
<RequiredValue>@(AllConfigurations)</RequiredValue>
</_RequiredItems>
Similar to the properties, inside the include you place the name of the item and then the value to check inside of RequiredValue
metadata. In this example it just checks to ensure the the AllConfiguraitons
item is not empty. If you want to make sure that a given metadata value is specified on all items then do something like:
<_RequiredItems Include = "AllConfigurations.Configuration">
<RequiredValue>%(AllConfigurations.Configuration </RequiredValue>
</_RequiredItems>
If you want to make sure that a file exists then add the additional metadata, RequiredFilePath.
<_RequiredItems Include ="ProjectsToBuild">
<RequiredValue>%(ProjectsToBuild.Identity)</RequiredValue>
<RequiredFilePath>%(ProjectsToBuild.Identity)</RequiredFilePath>
</_RequiredItems>
Validation
Here is what you need to perform the validation
Complete example
Here is the full example
<Target Name="ValidateBuildSettings">
<ItemGroup>
<_RequiredProperties Include="Root">
<Value>$(Root)</Value>
</_RequiredProperties>
<_RequiredProperties Include="BuildInstallRoot">
<Value>$(BuildInstallRoot)</Value>
</_RequiredProperties>
<_RequiredProperties Include="SourceRoot">
<Value>$(SourceRoot)</Value>
</_RequiredProperties>
<!--
_RequiredItems is the item where required items should be placed.
The following metadata is significant:
REQUIRED METADATA:
Identity = This will basically be used to identify the specific required item
RequiredValue = This is the specific value that will be validated to exist
OPTIONAL METADATA
RequiredFilePath = Populate this with a path that should exists, if it is not empty
then it will be checked to exist on disk.
-->
<_RequiredItems Include="AllConfigurations">
<RequiredValue>@(AllConfigurations)</RequiredValue>
</_RequiredItems>
<_RequiredItems Include = "AllConfigurations.Configuration">
<RequiredValue>%(AllConfigurations.Configuration </RequiredValue>
</_RequiredItems>
<_RequiredItems Include ="ProjectsToBuild">
<RequiredValue>%(ProjectsToBuild.Identity)</RequiredValue>
<RequiredFilePath>%(ProjectsToBuild.Identity)</RequiredFilePath>
</_RequiredItems>
</ItemGroup>
<!-- Raise an error if any value in _RequiredProperties is missing -->
<Error Condition =" '%(_RequiredProperties.Value)'=='' "
Text=" Missing required property [%(_RequiredProperties.Identity)]" />
<!-- Raise an error if any value in _RequiredItems is empty -->
<Error Condition = " '%(_RequiredItems.RequiredValue)'=='' "
Text = " Missing required item value [%(_RequiredItems.Identity)] " />
<!-- Validate any file/directory that should exist -->
<Error Condition = " '%(_RequiredItems.RequiredFilePath)' != '' and !Exists('%(_RequiredItems.RequiredFilePath)') "
Text = " Unable to find expeceted path [%(_RequiredItems.RequiredFilePath)] on item [%(_RequiredItems.Identity)] " />
</Target>
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