I need to update item metadata values. It's easy to add to the value:
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>FOO;BAR;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
However, what I need to do is remove part of the value. Ideally something like this would work, but it doesn't:
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions.Replace('FOO;',''))</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
Is there any way to accomplish this in MSBuild 4?
I was trying to do the same thing, and while I couldn't figure out how to strip definitions out of the string, I did discover an additional property: UndefinePreprocessorDefinitions.
<ItemDefinitionGroup>
<ClCompile>
<UndefinePreprocessorDefinitions>FOO</UndefinePreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
This will cancel out a previous definition of FOO. It might look a bit silly to pass -DFOO -UFOO to the compiler instead of nothing at all, but it works just as well.
In a subsequent ItemDefinitionGroup, you can create a copy of the current metadata then call Replace on that:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Dump" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemDefinitionGroup>
<SomeItem>
<SomeMetaData>foo,bar,baz</SomeMetaData>
</SomeItem>
</ItemDefinitionGroup>
<ItemGroup>
<SomeItem Include="one;two" />
</ItemGroup>
<ItemDefinitionGroup>
<SomeItem>
<!-- Remove "bar" -->
<SomeMetaData>$([System.String]::Copy('%(SomeMetaData)').Replace('bar',''))</SomeMetaData>
</SomeItem>
</ItemDefinitionGroup>
<Target Name="Dump">
<Message Text="SomeItem.SomeMetaData: #(SomeItem -> '%(Identity)=%(SomeMetaData)') " />
</Target>
</Project>
Here's the output when run with MSBuild 14:
> MSBuild .\foo.proj
Microsoft (R) Build Engine version 14.0.25420.1
Copyright (C) Microsoft Corporation. All rights reserved.
Build started 2/17/2017 7:09:48 PM.
Project "D:\temp\mb\foo.proj" on node 1 (default targets).
Dump:
SomeItem.SomeMetaData: one=foo,,baz;two=foo,,baz
Done Building Project "D:\temp\mb\foo.proj" (default targets).
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.03
Related
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.
I am writing an msbuild script to deploy multiple targets. I am trying to reuse some elements and am getting an unexpected behavior.
When I run this proj file:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="All" DependsOnTargets="DeployTarget1;DeployTarget2">
</Target>
<Target Name="DeployTarget1">
<PropertyGroup>
<RootDir>.\source1</RootDir>
</PropertyGroup>
<ItemGroup>
<DeployFiles Include="$(RootDir)\**\*.dll" Exclude="$(RootDir)\bin\C_*.xml" />
</ItemGroup>
<Copy SourceFiles="#(DeployFiles)" DestinationFiles="#(DeployFiles->'D:\deploytest\dest1\%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
<Target Name="DeployTarget2">
<PropertyGroup>
<RootDir>.\source2</RootDir>
</PropertyGroup>
<ItemGroup>
<DeployFiles Include="$(RootDir)\**\*.dll" Exclude="$(RootDir)\bin\C_*.xml" />
</ItemGroup>
<Copy SourceFiles="#(DeployFiles)" DestinationFiles="#(DeployFiles->'D:\deploytest\dest2\%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
</Project>
DeployTarget1 copies the files from the source1 directory recursively to dest1 as I expect.
DeployTarget2 copies both source1 and source2 to dest2, this is not what I expected.
D:\deploytest>msbuild /t:All test.proj
Microsoft (R) Build Engine version 4.0.30319.17929
[Microsoft .NET Framework, version 4.0.30319.18052]
Copyright (C) Microsoft Corporation. All rights reserved.
Build started 8/20/2013 2:37:17 PM.
Project "D:\deploytest\test.proj" on node 1 (All target(s)).
DeployTarget1:
Copying file from ".\source1\test1.dll" to "D:\deploytest\dest1\test1.dll".
copy /y ".\source1\test1.dll" "D:\deploytest\dest1\test1.dll"
DeployTarget2:
Copying file from ".\source1\test1.dll" to "D:\deploytest\dest2\test1.dll".
copy /y ".\source1\test1.dll" "D:\deploytest\dest2\test1.dll"
Copying file from ".\source2\test2.dll" to "D:\deploytest\dest2\test2.dll".
copy /y ".\source2\test2.dll" "D:\deploytest\dest2\test2.dll"
Done Building Project "D:\deploytest\test.proj" (All target(s)).
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.06
Does anyone know why this happens?
Can you point me to documentation on this "feature"?
ItemGroups are cummulative. Each time an item group if referenced, items are added to the existing group. If you want to have explicit groups, you either need to give them a unique name or you need to clear them before usage.
Use <DeployFiles Remove="**/*">
See also: https://stackoverflow.com/a/7915992/736079
I have a property group which includes a property for the build_number which is being passed in from TeamCity as solely the Build Counter. The build number format being set in TeamCity as simply {0} for the counter.
<PropertyGroup>
<Major>10</Major>
<Minor>1</Minor>
<Build>$(BUILD_NUMBER)</Build>
<Release>0</Release>
...
</PropertyGroup>
The Major, Minor and Release properties are then updated from values in a file in source control.
So that TeamCity logs the build as the full 4 part build reference (not just the counter), I set it thus:
<TeamCitySetBuildNumber BuildNumber="$(Major).$(Minor).$(Build).$(Release)" />
However, now when I reference my $(Build) property, it's now set to the 4 part build reference, and any property I have made which makes reference to $(BUILD_NUMBER) prior to setting using TeamCitySetBuildNumber also gets overwritten with the 4 part reference.
NB I've also changed it with a system message:
<Message Text="##teamcity[buildNumber '$(Major).$(Minor).$(Build).$(Release)']" />
but the overall effect is the same.
How Can I refer to the build counter (only) AFTER I have set the BuildNumber above?
If you're using a project file, you could try calling the TeamCitySetBuildNumber command in the AfterBuild section of the *.vbproj or *.csproj file:
<Target Name="AfterBuild">
<TeamCitySetBuildNumber BuildNumber="$(Major).$(Minor).$(Build).$(Release)" />
</Target>
If you're using a solution file, I'd create a *.proj file that calls your solution file and then after that call the TeamCitySetBuildNumber command (not sure if you can call the TeamCitySetBuildNumber command within the target like this though...):
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="SetBuildNumber">
<PropertyGroup>
<Major>10</Major>
<Minor>1</Minor>
<Build>$(BUILD_NUMBER)</Build>
<Release>0</Release>
</PropertyGroup>
<Target Name="Build">
<Message Text="Build task called... " Importance="high"/>
<MSBuild Projects="$(teamcity_build_checkoutDir)\your_solution.sln" Properties="Configuration=Release"/>
</Target>
<Target Name="SetBuildNumber" DependsOnTargets="Build">
<Message Text="Setting build number back to TeamCity... " Importance="high"/>
<TeamCitySetBuildNumber BuildNumber="$(Major).$(Minor).$(Build).$(Release)" />
</Target>
</Project>
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
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>