views:

88

answers:

2

Problem: an ItemGroups array isn't correctly build based on the value passed in the exclude attribute.

If you run this scrip it creates some sample file then tries to create an array called TheFiles based on the Include/Exclude attributes, problem is when the Exclude is anything other than hardcoded or a very simple property it gets it wrong.

The target DynamicExcludeList's incorrectly selects these files:
.\AFolder\test.cs;.\AFolder\test.txt

The target HardcodedExcludeList's correctly selects these files:
.\AFolder\test.txt

Any help much appreciated, this is driving me nuts.

(note its msbuild v4)

    <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Run">

      <Target Name="Run" >
        <CallTarget Targets="CreateSampleFiles" />
        <CallTarget Targets="DynamicExcludeList" />
        <CallTarget Targets="HardcodedExcludeList" />
      </Target>

      <Target Name="CreateSampleFiles" >
        <MakeDir Directories="AFolder" />
        <WriteLinesToFile Lines="Test" File="AFolder\test.cs" Overwrite="true" />
        <WriteLinesToFile Lines="Test" File="AFolder\test.txt" Overwrite="true" />
      </Target>

      <Target Name="DynamicExcludeList" >

        <PropertyGroup>
          <CommonFileExclusion>.\DIRECTORY_NAME_TOKEN\**\*.cs</CommonFileExclusion>
          <FinalExcludes>$(CommonFileExclusion.Replace('DIRECTORY_NAME_TOKEN', 'AFolder'))</FinalExcludes>
        </PropertyGroup>

        <Message Text="FinalExcludes: $(FinalExcludes)" />
        <ItemGroup>
          <TheFiles 
            Include=".\AFolder\**\*;" 
            Exclude="$(FinalExcludes)"
          />
        </ItemGroup>
        <Message Text="TheFiles: @(TheFiles)" />

      </Target>

      <Target Name="HardcodedExcludeList" >

        <PropertyGroup>
          <FinalExcludes>.\AFolder\**\*.cs</FinalExcludes>
        </PropertyGroup>

        <Message Text="FinalExcludes: $(FinalExcludes)" />
        <ItemGroup>
          <TheFilesWithHardcodedExcludes
            Include=".\AFolder\**\*;"
            Exclude="$(FinalExcludes)"
          />
        </ItemGroup>
        <Message Text="TheFilesWithHardcodedExcludes: @(TheFilesWithHardcodedExcludes)" />

      </Target>  
    </Project>

This is the output, note the differences between 'TheFiles' and 'TheFilesWithHardcodedExcludes'

PS C:\SVN\TrunkDeployment\TestMsBuild> msbuild .\Test.build.xml
Microsoft (R) Build Engine Version 4.0.30319.1
[Microsoft .NET Framework, Version 4.0.30319.1]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 8/10/2010 2:30:42 PM.
Project "C:\SVN\TrunkDeployment\TestMsBuild\Test.build.xml" on node 1 (default targets).
DynamicExcludeList:
  FinalExcludes: .\AFolder\**\*.cs
  TheFiles: .\AFolder\test.cs;.\AFolder\test.txt
HardcodedExcludeList:
  FinalExcludes: .\AFolder\**\*.cs
  TheFilesWithHardcodedExcludes: .\AFolder\test.txt
Done Building Project "C:\SVN\TrunkDeployment\TestMsBuild\Test.build.xml" (default targets).


Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:00.06

EDITS

I've updated the above script to use the CreateItem, however there is still an issue when the list of items to exclude contains more than 1 path (i.e. the value of CommonFileExclusion has changed):

    <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Run">

      <Target Name="Run" >
        <CallTarget Targets="CreateSampleFiles" />
        <CallTarget Targets="DynamicExcludeList" />
        <CallTarget Targets="HardcodedExcludeList" />
      </Target>

      <Target Name="CreateSampleFiles" >
        <MakeDir Directories="AFolder" />
        <WriteLinesToFile Lines="Test" File="AFolder\test.cs" Overwrite="true" />
        <WriteLinesToFile Lines="Test" File="AFolder\test.txt" Overwrite="true" />
        <WriteLinesToFile Lines="Test" File="AFolder\test.vb" Overwrite="true" />
      </Target>

      <Target Name="DynamicExcludeList" >

        <PropertyGroup>
          <CommonFileExclusion>.\DIRECTORY_NAME_TOKEN\**\*.cs;.\DIRECTORY_NAME_TOKEN\**\*.vb;</CommonFileExclusion>
          <FinalExcludes>$(CommonFileExclusion.Replace('DIRECTORY_NAME_TOKEN', 'AFolder'))</FinalExcludes>
        </PropertyGroup>

        <Message Text="FinalExcludes: $(FinalExcludes)" />
        <CreateItem Include=".\AFolder\**\*;"
                     Exclude="$(FinalExcludes)">
          <Output TaskParameter="Include" ItemName="TheFiles"/>
        </CreateItem>
        <Message Text="TheFiles: @(TheFiles)" />

      </Target>

      <Target Name="HardcodedExcludeList" >

        <PropertyGroup>
          <FinalExcludes>.\AFolder\**\*.cs;.\AFolder\**\*.vb</FinalExcludes>
        </PropertyGroup>

        <Message Text="FinalExcludes: $(FinalExcludes)" />
        <CreateItem Include=".\AFolder\**\*;"
                     Exclude="$(FinalExcludes)">
          <Output TaskParameter="Include" ItemName="TheFilesWithHardcodedExcludes"/>
        </CreateItem>
        <Message Text="TheFilesWithHardcodedExcludes: @(TheFilesWithHardcodedExcludes)" />

      </Target>
    </Project>
+1  A: 

In target DynamicExcludeList use CreateItem task instead of ItemGroup to dynamically generate your item :

<Target Name="DynamicExcludeList" >

  <PropertyGroup>
    <CommonFileExclusion>.\DIRECTORY_NAME_TOKEN\**\*.cs</CommonFileExclusion>
    <FinalExcludes>$(CommonFileExclusion.Replace('DIRECTORY_NAME_TOKEN', 'AFolder'))</FinalExcludes>
  </PropertyGroup>

  <Message Text="FinalExcludes: $(FinalExcludes)" />

  <CreateItem Include=".\AFolder\**\*;"
              Exclude="$(FinalExcludes)">
    <Output TaskParameter="Include" ItemName="TheFiles"/>
  </CreateItem>

  <Message Text="TheFiles: @(TheFiles)" />
</Target>

Theoretically ItemGroup and CreateItem are equivalent but I've seen case (dynamic situation) like this one when CreateItem must be use.

madgnome
In factn the properties and itemgroup are all evaluated when your script is parsed, before every target is run. This is why your itemgroups are not populated with what you thought but with what there was before your msbuild script get executed. You can read more about it here : http://blogs.msdn.com/b/msbuild/archive/2006/01/03/508629.aspx and http://www.sedodream.com/PermaLink,guid,dd6cb1db-c0e4-47f7-ad84-6e59ff6b03d0.aspx . A good way to avoid this problem is to use ItemGroup outside of targets (for static items) and createitem inside (for dynamic items)
Benjamin Baumann
Thanks for the explanation. But if you look the documentation on CreateItem (http://msdn.microsoft.com/en-us/library/s2y3e43x.aspx), you see that this task is deprecated, so you could think that ItemGroup inside a target would be evaluated dynamically like for CreateItem.
madgnome
Thanks for the feedback, I'm still having an issue (post in a sec), according this this: http://stackoverflow.com/questions/937681/createitem-vs-itemgroup, CreateItem is obsolete in 3.5
Keith
CreateItem is deprecated since .NET 3.5 but you can still use it. And it solves your problem
madgnome
it solved it in my initial sample, which was cut down for simplicity, I started to implement your fix but ran into the same issue once I added more paths to the value of CommonFileExclusion, if you still have any ideas it'd be fantastic (i updated my post with the issue above). Thanks for the help
Keith
I've looked at your another problem, but for this I've no clue. I guess there is a bug.
madgnome
I think this could be the very same problem. When MSBuild first parse your script it will evaluate CommonFileExclusion and FinalExcludes. So FinalExcludes=NULL because the directory AFolder does not exist yet. But if you hardcode the FinalExcludes (without setting a property) this should work. To solve your problem, you can change your propertyGroup to a CreateProperty Task as shown in http://msdn.microsoft.com/en-us/library/63ckb9s9.aspx. And beware, you are setting the value of FinalExcludes two times but MSBuild read your file only once!
Benjamin Baumann
I've tried using CreateProperty instead of propertyGroup but it doesn't work.
madgnome
I think the problem is using a property for a non scalar value. You must use an item for the FinalExcludes. I posted a version that works.
Benjamin Baumann
+1  A: 

Ok, I tried a little and I think the problem comes from the fact that you use a property which represent SCALAR value for multiple values. I would recommend batching and transforming (see http://scottlaw.knot.org/blog/?p=402 and http://msdn.microsoft.com/en-us/library/ms171476.aspx). For example the following code is working :

<Target Name="DynamicExcludeList" >
  <ItemGroup>
    <ExtensionsExcluded Include="cs;vb" />
  </ItemGroup>

  <CreateItem Include=".\AFolder\**\*"
          Exclude="@(ExtensionsExcluded->'.\AFolder\**\*.%(identity)')">
    <Output TaskParameter="Include" ItemName="TheFiles"/>
  </CreateItem>
  <Message Text="TheFiles: @(TheFiles)" />
</Target>
Benjamin Baumann
Thanks for the fix, its just one of those things that’s nasty in MSBuild (IMO) as it get more complicated the more excludes you want to add (e.g. some files in some directories, but not other etc), might looks for another solution.
Keith