views:

74

answers:

1

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"&gt;

  <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"&gt;
  <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>
+1  A: 

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 $(Root) $(BuildInstallRoot) $(SourceRoot)
    <_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>
Sayed Ibrahim Hashimi
Excellent - this is just what I was looking for.Looks like I'll need to pick up a copy of your book, too :)
Brian Gillespie