Compiler Additional Options computed in a custom Target - msbuild

I have a msbuild custom Target and a Task computing a Value.
The Task will output the Value as Property.
This Property I would like to uses as Additional Option to the Compiler call.
But the Property is empty when used as Additional Option.
My *.targets File looks like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="GetBranchName_TASK" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
<ParameterGroup>
<sPath ParameterType="System.String" Required="true" />
<sBranchName ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
<![CDATA[
... some Code ...
]]>
</Code>
</Task>
</UsingTask>
<Target Name="GetBranchName_TARGET">
<GetBranchName_TASK sPath="$(MSBuildThisFileDirectory)">
<Output PropertyName="BranchName" TaskParameter="sBranchName" />
</GetBranchName_TASK>
<Message Importance="High" Text="BranchName = $(BranchName)" />
</Target>
<PropertyGroup>
<BuildDependsOn>
GetBranchName_TARGET;
$(BuildDependsOn);
</BuildDependsOn>
</PropertyGroup>
</Project>
My *.props File is like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Configuration">
... some Properties here ...
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="IRSGetBranchName.targets" />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalOptions>/DBRANCHNAME=$(BranchName) /DMORE=BAR</AdditionalOptions>
<ClCompile>
<ItemDefinitionGroup>
</Project>
This .props File then is imported into several .vcxproj
The Value printed as Message in my GetBranchName_TARGET is correct as expected (showing the correct TFS-Branch Name).
But when looking at Detailed Build Output, the Value seems empty:
1>ClCompile
1> ..\FOO.cpp
1> AdditionalOptions = /DBRANCHNAME= /DMORE=BAR
I tried for hours but found no solution and I really hope someone help whats wrong here ...
a) Is the Property BranchName not available globally? I tried to print the Property from other custom Targets and it worked well!
b) Or is the ClCompile.AdditionalOptions evaluated/build before my Target is excuted? In this case how can I re-evaluate?
c) ...
I'am very thankful for any Input.

You should be familiar with the msbuild evaluation process, as described here:
When the MSBuild engine begins to process a build file, it is evaluated in a top-down fashion in a multi-pass manner. These passes are described in order in the following list:
Load all environment and global properties, and toolset properties. In Microsoft Visual Studio 2010, for example, C++ defines several properties in the MSBuild 4.0 toolset.
Evaluate properties and process imports as encountered
Evaluate item definitions
Evaluate items
Evaluate using tasks
Start build and reading targets
So, in your case, the ItemDefinitionGroup for ClCompile has been evaluated before the GetBranchName_TARGET has been executed. So, it is empty by design.
In order to achieve the desired behavior, you should Add the following:
<Target Name="GetBranchName_TARGET">
<GetBranchName_TASK sPath="$(MSBuildThisFileDirectory)">
<Output PropertyName="BranchName" TaskParameter="sBranchName" />
</GetBranchName_TASK>
<Message Importance="High" Text="BranchName = $(BranchName)" />
<ItemGroup>
<ClCompile>
<AdditionalOptions>/DBRANCHNAME=$(BranchName) /DMORE=BAR</AdditionalOptions>
</ClCompile>
</ItemGroup>
</Target>
You can use a Condition attribute in the ClCompile in order to include only your sources, for example. Actually, what you are looking for is the feature to modify item metadata after it was declared.

Related

Get a property value out of an MSBuild Task

My scenario is as follows.
I have a .targets file that does some stuff, for example,
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Some UsingTask here -->
<PropertyGroup>
<CustomProperty1>MyFirstValue</CustomProperty1>
<CustomProperty2>MySecondValue</CustomProperty2>
<Target Name="AfterResolveReferences">
<MSBuild Projects="MyProjectLocation\MyProject.csproj" Properties="CustomProperty1=$(CustomProperty1);CustomProperty2=$(CustomProperty2)" Targets="Rebuild" />
<Message Text="CodeGenerated = $(CodeGenerated)" />
</Target>
<Target Name="CodeGeneratedSpecificStuff" Condition="$(CodeGenerated) == 'true'">
<!-- Stuff happens -->
</Target>
<!-- More targets and bits and pieces -->
</Project>
And MyProject.csproj looks like this:
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<!-- Some property groups with some properties -->
<!-- Also Compile includes etc as expected in a .csproj file -->
<Target Name="BeforeBuild" DependsOnTargets="GenerateCode">
<ItemGroup Condition="$(CodeGenerated) == 'true'">
<Compile Include="MyGeneratedCode.cs" />
</ItemGroup>
</Target>
<Target Name="GenerateCode">
<MyCodeGenerationCustomTask MyOperationalProperty="$(CustomProperty1)">
<Output TaskParameter="CodeGenerated" PropertyName="CodeGenerated" />
</MyCodeGenerationCustomTask>
</Target>
</Project>
I am trying to bubble the "CodeGenerated" property that comes from my MyCodeGenerationCustomTask in the .csproj file to get out into my .targets file.
So far I've tried:
Passing in the "CodeGenerated" property as empty at the MSBuild call in the .targets file.
Specifying an "Output" element under the MSBuild task (it doesn't work as the MSBuild task doesn't have a "CodeGenerated" property...)
I have also tried setting "Outputs" on both the "BeforeBuild" target and on the "Project" element in the .csproj file to "$(CodeGenerated)" (MSBuild didn't complain about the existence of the Outputs tag on the Project element, so I thought I might as well give it a go), but the value does not bubble up to the .targets file. In the .targets file I did also change the MSBuild task to look more like this:
<MSBuild Projects="MyProjectLocation\MyProject.csproj" Properties="CustomProperty1=$(CustomProperty1);CustomProperty2=$(CustomProperty2)" Targets="Rebuild">
<Output TaskParameter="TargetOutputs" PropertyName="CodeGenerated" />
</MSBuild>
But as I would expect this just contained the list of generated files from the MSBuild task.
Important to note is the code generation is working fine, and the CodeGenerated property within the .csproj file functions correctly and conditionally includes the cs file for compilation.
Is this possible? Am I just hitting my head against a brick wall? Or am I missing some MSBuild magic?
I really want to avoid checking the specific .cs location at every level above for its existence.

MSBuild Command Line arguments length exceeds 8191 characters

I are using MSBUILD.exe to perform build for the application. as part of this I pass the required variables as command line arguments for MSBUILD.exe.
I have 2 files. service.xml and MyService.proj. Below line is present in service.xml file.
<installCommand name="MyService" cmd="msbuild.exe "MyService.proj" /p:{vairables}">
initially the length of the command was small and everything was fine as I was able to build my project but as the project size increased the number of parameters also increased, and now I am at a stage where the command line is displaying an error (Input line is too Long).
Upon some searching I found out that command line cannot be more than 8191 characters.
Can any one Suggest any alternatives for this.
What's installCommand?
MSBuild engine merges all system, user and process variables as well as parameters and properties into one big pool, so every property that you pass via {vairables} can be set first separatly. Keep in mind that properties pass via command line are global properties so environment variable equivalent will not override corresponding project property unless it has Condition=" '$(Foo)' == '' " on it.
http://msdn.microsoft.com/en-us/library/ms171458.aspx
You can set variables in an xml file, and pass the xml file-name to the msbuild script.
Here is a simple example that uses the MSBuildCommunityTasks.
Parameters.xml (contents below)
<?xml version="1.0" encoding="utf-8"?>
<parameters>
<setParameter name="LineNumber1" value="PeanutsAreCool" />
<setParameter name="LineNumber2" value="" />
</parameters>
MyMsbuildDef.proj (contents below)
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="AllTargetsWrapped">
<Import Project="$(MSBuildExtensionsPath32)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
<PropertyGroup>
<!-- Always declare some kind of "base directory" and then work off of that in the majority of cases -->
<WorkingCheckout>.</WorkingCheckout>
</PropertyGroup>
<Target Name="AllTargetsWrapped">
<CallTarget Targets="ReadXmlPeekValue" />
<CallTarget Targets="WriteXmlPeekValue" />
</Target>
<Target Name="ReadXmlPeekValue">
<!-- you do not need a namespace for this example, but I left it in for future reference -->
<XmlPeek Namespaces="<Namespace Prefix='peanutNamespace' Uri='http://schemas.microsoft.com/developer/msbuild/2003'/>"
XmlInputPath=".\Parameters.xml"
Query="/parameters/setParameter[#name='LineNumber1']/#value">
<Output TaskParameter="Result" ItemName="Peeked" />
</XmlPeek>
<Message Text="#(Peeked)"/>
<XmlPeek Namespaces="<Namespace Prefix='peanutNamespace' Uri='http://schemas.microsoft.com/developer/msbuild/2003'/>"
XmlInputPath=".\Parameters.xml"
Query="/parameters/setParameter[#name='LineNumber1']/#value">
<Output TaskParameter="Result" PropertyName="PeekedSingle" />
</XmlPeek>
<Message Text="PeekedSingle = $(PeekedSingle) "/>
</Target>
<Target Name="WriteXmlPeekValue" Condition=" '$(PeekedSingle)' != '' ">
<XmlPoke Namespaces="<Namespace Prefix='msb' Uri='http://schemas.microsoft.com/developer/msbuild/2003'/>"
XmlInputPath=".\Parameters.xml"
Query="/parameters/setParameter[#name='LineNumber2']/#value"
Value="$(PeekedSingle)" />
</Target>
</Project>
MyBatFile.bat (contents below)
set msBuildDir=%WINDIR%\Microsoft.NET\Framework\v4.0.30319
call %msBuildDir%\msbuild /target:AllTargetsWrapped "MyMsbuildDef.proj" /p:Configuration=Debug;FavoriteFood=Popeyes /l:FileLogger,Microsoft.Build.Engine;logfile=ZZZZZAllTargetsWrapped.log
set msBuildDir=

Filter Global MSBuild Items

Starting with a .csproj which defines various xml Content files.
Have code generation Target which takes some xml files (Target Inputs) and generate .cs files whose names are determined by transformation from the xml files (Target Outputs).
In order for MSBuild to determine whether the code building Target needs to run, it needs to inspect the Target Inputs and Outputs. Therefore I am assuming that those Target Inputs and Outputs must be global.
If that's incorrect, there should be another question about how to create a Target who's Outputs are based on Dynamic Items; tried it but the Target keeps being called.
If it's correct, then how to filter the Content at the global level ?
Specifically, I want to filter Content Items in the project so that only the one's in a specific directory are used. The Content Items will be added by other developers via the IDE.
This can be achieved using a Target which creates Dynamic Items, doing the filtering in the Condition attribute. That requires Target Batching, which isn't available globally. Using MSBuild 3.5 and Visual Studio 2008.
<?xml version="1.0" encoding="utf-8"?>
<Project
DefaultTargets="Show" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Content Include="badxml\somebadxml1.xml" />
<!-- Note xml\somexml2.xml exists on disk, it just isn't used in this project. -->
<Content Include="xml\somexml1.xml" />
<Content Include="xml\somexml3.xml" />
</ItemGroup>
<!-- Foo should only be defined for Content Items in the "xml" directory. -->
<ItemGroup>
<Foo Include="#(Content->'%(Filename)')"/>
<!-- The line below doesn't work -->
<!-- TestFilter.proj(10,10): error MSB4090: Found an unexpected character '%' at position 3 in condition " '%(Content.RelativeDir)'=='xml' ". -->
<!-- <Foo Condition=" '%(Content.RelativeDir)'=='xml' " Include="#(Content->'%(Filename)')"/> -->
</ItemGroup>
<Target Name="ShowContent">
<Message Text="Content: %(Content.Identity)" />
<Message Text="Content RelDir: %(Content.RelativeDir)" />
</Target>
<Target Name="ShowFoo">
<Message Text="Foo: %(Foo.Identity)" />
</Target>
<Target Name="Show">
<CallTarget Targets="ShowContent;ShowFoo" />
</Target>
</Project>
Why doesn't MSBuild ItemGroup conditional work in a global scope addresses the same issue but from the perspective of asking why this doesn't work, rather than looking for alternative approaches.
Filtering Item's Metadata in msbuild uses Dynamic Items in a Target, and a dummy Output name.
My best guess is that this can't be done without using Dynamic Items in a Target, and the workaround will be rather than using Items which require a Condition, to write out a file with a predefined name and use that as Output placeholder.
So it turns out my assumption was incorrect. It's perfectly acceptable to have Target Outputs which are based on dynamic items. It helps to remember that Targets are batched according to the Outputs definition.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="TestBatch"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Static Item declaration. -->
<ItemGroup>
<Bar Include="Static01">
<Data>Static01 Data</Data>
</Bar>
</ItemGroup>
<Target Name="PreBatchTarget">
<!-- Dynamic Item addition. -->
<ItemGroup>
<Bar Include="Dynamic01">
<Data>Dynamic01 Data</Data>
</Bar>
</ItemGroup>
</Target>
<Target Name="TestBatchTarget"
Outputs="%(Bar.Data)"
>
<Message Text="TestBatchTarget call" />
<Message Text="#(Bar)" />
</Target>
<Target Name="TestBatch"
DependsOnTargets="PreBatchTarget;TestBatchTarget"
>
</Target>
</Project>
msbuild /nologo DynamicTargetOutput.proj
Project "DynamicTargetOutput.proj" on node 0 (default targets).
TestBatchTarget call
Static01
TestBatchTarget:
TestBatchTarget call
Dynamic01
Done Building Project "DynamicTargetOutput.proj" (default targets).
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.09

Additional paths in msbuild script

How to specify additional assembly reference paths for the MSBuild tasks?
I have following script so far, but can't figure out how to specify additional search paths.
<ItemGroup>
<ProjectsToBuild Include="..\Main\Main.sln" />
</ItemGroup>
<!-- The follwing paths should be added to reference search paths for the build tasks -->
<ItemGroup>
<MyAddRefPath Include="$(MSBuildProjectDirectory)\..\..\Build\Lib1" />
<MyAddRefPath Include="$(MSBuildProjectDirectory)\..\..\Build\Lib2" />
</ItemGroup>
<MSBuild
Projects="#(ProjectsToBuild)"
Properties="Configuration=Debug;OutputPath=$(BuildOutputPath)">
</MSBuild>
UPDATE:
Please show one complete working script which invokes original project, such as an SLN with multiple additional reference paths.
No suggestions on how to improve the project structure please.
I know how to build a good structure, but now it's the task of building an existing piece of crap.
I have finaly figured out how to do it:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ProjectsToBuild Include="ConsoleApplication1\ConsoleApplication1.csproj" />
</ItemGroup>
<ItemGroup>
<AdditionalReferencePaths Include="..\Build\ClassLibrary1" />
<AdditionalReferencePaths Include="..\Build\ClassLibrary2" />
</ItemGroup>
<PropertyGroup>
<BuildOutputPath>..\Build\ConsoleApplication1</BuildOutputPath>
</PropertyGroup>
<Target Name="MainBuild">
<PropertyGroup>
<AdditionalReferencePathsProp>#(AdditionalReferencePaths)</AdditionalReferencePathsProp>
</PropertyGroup>
<MSBuild
Projects="ConsoleApplication1\ConsoleApplication1.csproj"
Properties="ReferencePath=$(AdditionalReferencePathsProp);OutputPath=$(BuildOutputPath)"
>
</MSBuild>
</Target>
The property you want to modify is AssemblySearchPaths. See the ResolveAssemblyReference task more information.
<Target Name="AddToSearchPaths">
<CreateProperty Value="x:\path\to\assemblies;$(AssemblySearchPaths)">
<Output PropertyName="AssemblySearchPaths" TaskParameter="Value" />
</CreateProperty>
</Target>
Making use of item groups, as in your example, it would look like:
<Target Name="AddToSearchPaths">
<CreateProperty Value="#(MyAddRefPath);$(AssemblySearchPaths)">
<Output PropertyName="AssemblySearchPaths" TaskParameter="Value" />
</CreateProperty>
</Target>
Looking in %WINDIR%\Microsoft.NET\Framework\v2.0.50727\Microsoft.Common.targets, you can see that the ResolveAssemblyReference Task is executed as part of the ResolveAssemblyReferences target. Thus, you want the newly added target to modify the AssemblySearchPaths property before ResolveAssemblyReferences is executed.
You've stated that you want to be able to modify the assembly search paths without modifying the project files directly. In order to accomplish that requirement you need to set an environment variable that will override the AssemblySearchPaths. With this technique you will need to provide every assembly reference path used by all the projects in the solutions. (Modifying the projects or copies of the projects would be easier. See final comments.)
One technique is to create a batch file that runs your script at sets the environment variable:
set AssemblySearchPaths="C:\Tacos;C:\Burritos;C:\Chalupas"
msbuild whatever.msbuild
Another way is to define a PropertyGroup in your custom msbuild file (otherwise known as the "hook" needed to make this work):
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ProjectsToBuild Include="..\Main\Main.sln" />
</ItemGroup>
<PropertyGroup>
<AssemblySearchPaths>$(MSBuildProjectDirectory)\..\..\Build\Lib1;$(MSBuildProjectDirectory)\..\..\Build\Lib2</AssemblySearchPaths>
</PropertyGroup>
<Target Name="Build">
<MSBuild Projects="#(ProjectsToBuild)" Properties="AssemblySearchPaths=$(AssemblySearchPaths);Configuration=Debug;OutputPath=$(OutputPath)" />
</Target>
</Project>
Now if it were me, and for whatever unexplained reason I couldn't modify the project files to include the updated references that I am going to build with, I would make copies of the project files, load them into the IDE, and correct the references in my copies. Synching the projects becomes a simple diff/merge operation which is automatic with modern tools like mercurial (heck I'm sure clearcase could manage it too).
...and remember that you don't need to use a target for this, you can use project-scoped properties or items, as...
<ItemGroup>
<MyAddRefPath Include="$(MSBuildProjectDirectory)\..\..\Build\Lib1" />
<MyAddRefPath Include="$(MSBuildProjectDirectory)\..\..\Build\Lib2" />
</ItemGroup>
<PropertyGroup>
<MyAddRefPath>$(MSBuildProjectDirectory)\..\..\Build\Lib3</MyAddRefPath>
<!-- add in the property path -->
<AssemblySearchPaths>$(MyAddRefPath);$(AssemblySearchPaths)</AssemblySearchPaths>
<!-- add in the item paths -->
<AssemblySearchPaths>#(MyAddRefPath);$(AssemblySearchPaths)</AssemblySearchPaths>
</PropertyGroup>
...and if you do need to do this in a target to pick up paths from a dynamically populated item group, use inline properties, not the CreateProperty task (if you are not stuck in v2.0)
<Target Name="AddToSearchPaths">
<PropertyGroup>
<!-- add in the item paths -->
<AssemblySearchPaths>#(MyDynamicAddRefPath);$(AssemblySearchPaths)</AssemblySearchPaths>
</PropertyGroup>
</Target>

MSBuild MSBuildCommunityTasks Task Time

I have a MSBuild project and I want the current date to be added to a zip file that I am creating.
I am using the MSBuildCommunityTasks.
<!-- Import the CommunityTasks Helpper -->
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
On the website http://msbuildtasks.tigris.org/ I can see a task called time. I have not been able to find doc on how to use Time.
In msbuild 4 you can now
$([Namespace.Type]::Method(..parameters…))
$([Namespace.Type]::Property)
$([Namespace.Type]::set_Property(value))
so I am using
$([System.DateTime]::Now.ToString(`yyyy.MMdd`))
those ticks around the format are backticks not '
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Deploy" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<!-- Include MSBuild tasks here -->
<ItemGroup>
<DefaultExclude Include="****" />
</ItemGroup>
<Target Name="Deploy" >
<Time Format="yyyy-MM-dd">
<Output TaskParameter="FormattedTime" PropertyName="buildDate" />
</Time>
<Message Text="Deploying ...."></Message>
<Copy SourceFiles="#(DeploymentFiles)" DestinationFolder="C:\CCNET\$(buildDate)\bin\" />
</Target>
</Project>
Maslow's answer is correct (I can't comment on it or I would); I would only add to it that you have to be careful when implicitly calling System.DateTime.Parse.
A parsed string value like $([System.DateTime]::Parse("1970-01-01T00:00:00.0000000Z") doesn't seem to end up with a Kind of DateTimeKind.Utc.
But you can use nested property functions to make it work; like this (to get the Unix timestamp):
$([System.DateTime]::UtcNow.Subtract($([System.DateTime]::Parse("1970-01-01T00:00:00.0000000Z").ToUniversalTime())).TotalSeconds.ToString("F0"))