views:

1340

answers:

4

I have a solution I'm trying to get to build on TFS. I want to update the versions of all appropriate files, and I've been stuck trying to get this done. There are plenty of links on how to do it, but none of them work for me, due to one little issue... Scope.

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="DesktopBuild" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
    <Target Name="DesktopBuild">
     <CallTarget Targets="GetFiles" />

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

    <Target Name="GetFiles">
     <ItemGroup>
      <CSFiles Include="**\AssemblyInfo.cs" />
     </ItemGroup>
     <Message Text="CSFiles: '@(CSFiles)'" />
    </Target>
</Project>

My tree looks like this:

  • test.proj
  • application.sln
  • application (Folder)
    • main.cs
    • Properties (Folder)
      • AssemblyInfo.cs

When I run "c:\Windows\Microsoft.NET\Framework\v3.5\MSBuild.exe test.proj" from the solution folder... I get the following output:

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3074]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 7/6/2009 3:54:10 PM.
Project "D:\src\test.proj" on node 0 (default targets).
  CSFiles: 'application\Properties\AssemblyInfo.cs'
DesktopBuild:
  CSFiles: ''
Done Building Project "D:\src\test.proj" (default targets).


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

Time Elapsed 00:00:00.04

So, how can I make my ItemGroup have global scope? All the Targets files used by the compiler and TeamBuild do this same thing, and theirs all seem to be global... I don't understand why this isn't working for me.

Any help?

+4  A: 

Have you tried using DependsOnTarget rather than CallTarget? It could be that CallTarget is causing the scope issue.

technophile
OK, this seems to be giving me some more info. I can change the scoping of the ItemGroup by using DependsOn, but it doesn't seem consistent. It seems the siblings of targets called with dependson have access to the itemgroup, but the parent never does.So weird. Are there docs anywhere showing the scope rules? I couldn't find them, and I guess now I have to rewrite my files using complex dependecies rather than calls.This is so crazy.
Christopher Karper
I haven't seen any. Have you tried just moving the ItemGroup outside the Targets entirely? Is there a reason you don't want to do that?
technophile
Or initialize it outside to empty (hack with Include="*.WontExist")
Thomas G. Mayfield
I have to do the ItemGroup in a Target, since in it's in a TFS Build, I ned to pull the files AfterGet... (I'm using BeforeCompile, but whatever)And Thomas, I tried that, doesn't work. It appends to the global variable for the locally scoped instance. Whether you use an existing file or not.
Christopher Karper
A: 

We do something similar in our build. We pass the version as a command line parameter.

In our TFSBuild.proj we set the version to 0.0.0.0 if no version was supplied:

<!--Our assembly version. Pass it in from the command prompt like this: /property:Version=1.0.0.0-->
<PropertyGroup>
    <Version>0.0.0.0</Version>
</PropertyGroup>

<PropertyGroup>
    <!--Used to ensure there is a newline before our text to not break the .cs files if there is no newline at the end of the file.-->
    <newLine>%0D%0A</newLine>

Then we do this:

<Target Name="BeforeCompile">
    <!--Update our assembly version. Pass it in from the command prompt like this: /property:Version=1.0.0.0-->

    <!--attrib needs to be run first to remove the read only attribute since files from tfs are read only by default.-->
    <Exec Command='attrib -R $(SolutionRoot)\Source\Project\GlobalAssemblyInfo.cs'  />

    <WriteLinesToFile File="$(SolutionRoot)\Source\Project\GlobalAssemblyInfo.cs"
          Lines='$(newLine)[assembly: AssemblyVersion("$(Version)")]'/>

</Target>
David Silva Smith
Just FYI, Exists doesn't do what that code seems to expect it to do. Exists checks to see if the named file exists, not whether the named variable exists. You probably want Condition="'$(Version)'==''" instead. See http://msdn.microsoft.com/en-us/library/7szfhaft.aspx
technophile
This technophile guy is a smart one. :-) What he said. The doc standard way is to surround with single quotes and put plenty of whitespace around...<PropertyGroup Condition=" '$(Version)' == '' ">Also, this method isn't awesome for me. I prefer the MSBuild community tasks methods to attrib and regex update the files. My issue is in building the file list to update, as I have dynamic locations. Thanks though. :-)
Christopher Karper
Thanks Chris. :) We're in the middle of doing a bunch of work on our CI environment, so this is all very fresh in my mind. ;)
technophile
Good catch Techno. It appears the command line values passed aren't able to be overridden in PropertyGroups.
David Silva Smith
@David I'm not sure what you mean. You can do what you originally intended (default it if it's not passed on the command line) like so:<PropertyGroup> <Version Condition=" '$(Version)' == '' ">0.0.0.0</Version></PropertyGroup>What that will do is check to see if the Version property was passed in and, if not, set it to 0.0.0.0. If you leave the Condition attribute off, it will unconditionally override whatever ws passed on the command line.
technophile
+4  A: 

The previous commenter was correct, you should change this to use DependsOnTargets instead of using the CallTarget task. What you are seeing is a bug not a scoping inssue. The way to avoid this bug is to use DependsOnTargets (which is a much better approach anywayz).

Sayed Ibrahim Hashimi
A: 

As said, you should use DependsOnTargets. I've done some research on MSBuild scope, you can find my results on my blog : http://blog.qetza.net/2009/10/23/scope-of-properties-and-item-in-an-msbuild-script/

The thing is there seems to be a global scope for the project and a local scope for the target . When entering the target, the global scope is copied and when exiting the target, the local scope is merged back. So a CallTarget will not get the modified local scope values but the DependsOnTargets will since the first target is exited before the entering the second target.

Guillaume Rouchon