MSBuild: Update the C++ PreprocessorDefinitions with my custom target - c++

I'm trying to create a custom MSBuild target to automatically fetching the SVN revision number into some define, say MY_VERSION. So, my target file (get-svn-info.targets):
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="GetSvnRevision" BeforeTargets="PrepareForBuild">
<Exec Command="svn info --show-item revision" ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="SvnRevisionNum" />
</Exec>
<ItemGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);MY_VERSION=$(SvnRevisionNum);</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
</ItemGroup>
<Message Text="MY_VERSION=$(SvnRevisionNum)" Importance="high"></Message>
</Target>
</Project>
And finally, modified the project file:
<Project ...
<Import Project="get-svn-info.targets" />
...
</Project>
But compiler says that the 'MY_VERSION': undeclared identifier here
std::cout << "Hello World!\n" << std::to_string(MY_VERSION) << std::endl;
What am I doing wrong? Why does PreprocessorDefinitions still have the old value? Thanks
Build log output (a lot of unimportant? lines skipped):
...
1>Target "GetSvnRevision" in file "D:\ConsoleApplication1\get-svn-info.targets":
1> Using "Exec" task from assembly "Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
1> Task "Exec"
1> Task Parameter:ConsoleToMSBuild=True
1> Task Parameter:Command=svn info --show-item revision
1> svn info --show-item revision
1> 30
1> Output Property: SvnRevisionNum=30
1> Done executing task "Exec".
1> Task "Message"
1> Task Parameter:Importance=high
1> Task Parameter:Text=MY_VERSION=30
1> MY_VERSION=30
1> Done executing task "Message".
1>Done building target "GetSvnRevision" in project "ConsoleApplication1.vcxproj".
...
1>Target "ClCompile" in file "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Microsoft\VC\v160\Microsoft.CppCommon.targets":
1> Task "CL"
...
1> Task Parameter:
1> Sources=
1> ConsoleApplication1.cpp
1> PreprocessorDefinitions=WIN32;_DEBUG;_CONSOLE;_UNICODE;UNICODE;
...
1> Task Parameter:
1> PreprocessorDefinitions=
1> WIN32
1> _DEBUG
1> _CONSOLE
1> _UNICODE
1> UNICODE
...
1> D:\ConsoleApplication1\ConsoleApplication1.cpp(9,53): error C2065: 'MY_VERSION': undeclared identifier
1> The command exited with code 2.
1> Done executing task "CL" -- FAILED.
1>Done building target "ClCompile" in project "ConsoleApplication1.vcxproj" -- FAILED.
1>
1>Done building project "ConsoleApplication1.vcxproj" -- FAILED.
...

The syntax for the ItemDefinitionGroup is incorrect. An ItemDefinitionGroup should not be within an ItemGroup.
The ItemDefinitionGroup will add the changed PreprocessorDefinitions as a default for items in the ClCompile ItemGroup.
But ItemDefinitionGroup can't be used within a Target.
But we can still alter the metadata of Items in the ClCompile ItemGroup.
I would suggest making two targets.
<Target Name="GetSvnRevision" BeforeTargets="PrepareForBuild">
<Exec Command="svn info --show-item revision" ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="SvnRevisionNum" />
</Exec>
...
</Target>
<Target Name="ApplySvnRevision" BeforeTargets="ClCompile">
<ItemGroup>
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);MY_VERSION=$(SvnRevisionNum);</PreprocessorDefinitions>
</ClCompile>
</ItemGroup>
</Target>
'GetSvnRevision' can be performed early. But 'ApplySvnRevision' should be done just before compiling.

This solution works, but maybe there is a more elegant solution? After all, the MY_VERSION can be used not only in one file ConsoleApplication1.cpp?
Thanks
<Target Name="GetSvnRevision" BeforeTargets="PrepareForBuild">
<Exec Command="svn info --show-item revision" ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="SvnRevisionNum" />
</Exec>
<ItemGroup>
<ClCompile Remove="ConsoleApplication1.cpp" />
<ClCompile Include="ConsoleApplication1.cpp">
<PreprocessorDefinitions>%(PreprocessorDefinitions);MY_VERSION=$(SvnRevisionNum);</PreprocessorDefinitions>
</ClCompile>
</ItemGroup>
<Message Text="ME_VERSION=$(SvnRevisionNum)" Importance="high"></Message>

Related

Build issues after updating to visual studio 15.8.5

Does anyone have experience with this error?
Severity Code Description Project File Line Suppression State
Error The item "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\Microsoft\Microsoft.NET.Build.Extensions\net461\lib\Microsoft.Win32.Primitives.dll" in item list "ReferencePath" does not define a value for metadata "CopyLocal". In order to use this metadata, either qualify it by specifying %(ReferencePath.CopyLocal), or ensure that all items in this list define a value for this metadata. Batch_WPF
If I downgrade visual studio the issue goes away.
I've fixed this error by removing the following code in my .csproj file.
<Target Name="FixClickOnceDependencies" BeforeTargets="_DeploymentComputeClickOnceManifestInfo">
<ItemGroup>
<_noCopyRefs Include="#(ReferencePath)" Condition="'%(ReferencePath.Private)' == 'false'" />
<_noCopyRefsByFileName Include="#(_noCopyRefs->'%(FileName)')">
<OriginalItem>%(Identity)</OriginalItem>
</_noCopyRefsByFileName>
<_libByFileName Include="#(ReferenceCopyLocalPaths->'%(FileName)')">
<OriginalItem>%(Identity)</OriginalItem>
</_libByFileName>
<_overlappingRefByFileName Include="#(_noCopyRefsByFileName)" Condition="'#(_noCopyRefsByFileName)' == '#(_libByFileName)' AND '%(Identity)' != ''" />
<_overlappingLibByFileName Include="#(_libByFileName)" Condition="'#(_noCopyRefsByFileName)' == '#(_libByFileName)' AND '%(Identity)' != ''" />
<_overlappingRef Include="#(_overlappingRefByFileName->'%(OriginalItem)')" />
<_overlappingLib Include="#(_overlappingLibByFileName->'%(OriginalItem)')" />
</ItemGroup>
<ItemGroup Condition="'#(_overlappingRef)' != ''">
<ReferencePath Remove="#(_overlappingRef)" />
<ReferencePath Include="#(_overlappingLib)">
<Private>True</Private>
</ReferencePath>
</ItemGroup>
I added this code in order to fix an error with Click Once, as described here: https://github.com/dotnet/standard/issues/529

msbuild adding items to "Source Files"

I want to allow in my project template (c++) to add files with specific extension ".myext" and in msbuild I'll find the suitable header and ".cpp" files and add them accordingly to the and to but I need also to add these files to the "Source Files" filter after adding the items with the ".myext" extension.
this is how I find the files and add them accordingly :
<Target Name="FilterMyFiles">
<ItemGroup>
<Filtered Include="#(None)" Condition="'%(Extension)' == '.myext'" />
<SourceFiles Include="#(Filtered->'%(rootdir)%(directory)%(filename)_source.cpp')" />
<HeaderFiles Include="#(Filtered->'%(rootdir)%(directory)%(filename)_header.h')" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="%(HeaderFiles.Identity)" Condition="Exists('%(HeaderFiles.Identity)')" />
<ClCompile Include="%(SourceFiles.Identity)" Condition="Exists('%(SourceFiles.Identity)')" />
</ItemGroup>
</Target>
I tried to add some changes to the msbuild file (".vcxproj") and to the filter file (".vcxproj.filter") but nothing helped.

Is there a way to get build status as a property?

I have a ugly Teamcity build configuration using MSBuild. It executes custom application (test runner), which is using custom messaging to report test results to teamcity.
##teamcity[testStarted name='test1']
##teamcity[testFailed name='test1' message='failure message' details='message and stack trace']
Which show in teamcity in build overview and tests tab.
Teamcity recognizes failed tests and if any test fails, it marks the build as failed:
http://i.stack.imgur.com/Qz9UT.png
Later in the MSBuild target I would like to label cvs based on the test results.
Is there a way to get the build status (if it is failed, hanging, warning) as a property? something like %build.status%? The format does not matter - if its a string or number.
PS: I know that best solution to my problem would be to modify the application to return non-zero exit code if test fail.
TeamCty does not seem to expose this directly, but the status can be acquired using the REST api. Here is an example using curl; but you could also uwe PowserShell's Invoke-RestMethod for instance.
Here's the msbuild script that casues test failure I used for testing:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Test">
<Message Importance="high" Text="##teamcity[testStarted name='test1']" />
<Message Importance="high" Text="##teamcity[testFailed name='test1' message='failure message' details='message and stack trace']" />
</Target>
</Project>
Then the script that gets the current build's status, dumps it to a file, reads the file into an msbuild item and then uses regex to get the status out of it. You just have it to supply the tc_user and tc_password properties (or allow guest access) and change the url to match your server.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="GetBuildStatus">
<Target Name="RunCurl">
<PropertyGroup>
<MyTempFile>curl_out</MyTempFile>
</PropertyGroup>
<Exec Command="curl http://localhost/httpAuth/app/rest/builds/id:$(teamcity_build_id) -basic -u $(tc_user):$(tc_password) > $(MyTempFile)"/>
<ReadLinesFromFile File="$(MyTempFile)">
<Output TaskParameter="Lines" ItemName="CurlOutput"/>
</ReadLinesFromFile>
<Delete Files="$(MyTempFile)"/>
</Target>
<Target Name="GetBuildStatus" DependsOnTargets="RunCurl">
<PropertyGroup>
<CurlOutputFull>#(CurlOutput)</CurlOutputFull>
<BuildStatus>$([System.Text.RegularExpressions.Regex]::Match($(CurlOutputFull), `status="(\w*)"`).Groups[ 1 ].Value)</BuildStatus>
</PropertyGroup>
<Message Text="BuildStatus = $(BuildStatus)"/>
</Target>
</Project>
This prints:
BuildStatus = FAILURE

How can one modify an ItemDefinitionGroup from an MSBuild target?

I have an msbuild script I wrote to compile Google Protocol Buffers files:
<ItemGroup>
<ProtocolBuffer Include="Whitelist.proto" />
<ProtocolBuffer Include="Whitelist2.proto" />
</ItemGroup>
<ItemDefinitionGroup>
<ProtocolBuffer>
<ProtoPath>$(ProjectDir)</ProtoPath>
</ProtocolBuffer>
</ItemDefinitionGroup>
<PropertyGroup>
<ProtoC>$([System.IO.Path]::GetFullPath($(ProjectDir)..\ThirdParty\protobuf-2.4.1\protoc.exe))</ProtoC>
<ProtoOutPath>$(IntDir)CompiledProtocolBuffers</ProtoOutPath>
</PropertyGroup>
<Target Name="CompileProtocolBuffers"
BeforeTargets="ClCompile"
Inputs="#(ProtocolBuffer)"
Outputs="#(ProtocolBuffer->'$(ProtoOutPath)\%(FileName).pb.cc');#(ProtocolBuffer->'$(ProtoOutPath)\%(FileName).pb.h')">
<MakeDir Directories="$(ProtoOutPath)" />
<Exec
Command=""$(ProtoC)" --proto_path="$([System.IO.Path]::GetDirectoryName(%(ProtocolBuffer.ProtoPath)))" --cpp_out="$(ProtoOutPath)" "%(ProtocolBuffer.FullPath)" --error_format=msvs"
/>
<ItemGroup>
<ClInclude Include="$(ProtoOutPath)\%(ProtocolBuffer.FileName).pb.h" />
<ClCompile Include="$(ProtoOutPath)\%(ProtocolBuffer.FileName).pb.cc">
<AdditionalIncludeDirectories>$(MSBuildThisDirectory)..\ThirdParty\protobuf-2.4.1\src</AdditionalIncludeDirectories>
<PrecompiledHeader></PrecompiledHeader>
<DisableSpecificWarnings>4244;4276;4018;4355;4800;4251;4996;4146;4305</DisableSpecificWarnings>
<PreprocessorDefinitions>GOOGLE_PROTOBUF_NO_RTTI</PreprocessorDefinitions>
<WarningLevel>Level3</WarningLevel>
</ClCompile>
</ItemGroup>
</Target>
This compiles the protocol buffers files perfectly, and adds them to the compiler's inputs (yay!). However, my other source files that want to include the .pb.h files need to know where these files got generated -- that generation location needs to be put on the include path.
Therefore, if and only if the user has included a <ProtocolBuffer item somewhere in their script, I want to add the generation location (in this case $(ProtoOutPath) to ClCompile's <AdditionalIncludeDirectories>.
Is that possible or do I need to make .cpp files that want to use these generated bits jump through hoops?
Read your question and thought "can't be that hard". Man, was I wrong. First I thought just putting a condition on it, but of course one can't use ItemGroups in toplevel conditions because of evaluation order. Then I figured it's also not possible to put an ItemDefinitionGroup in a target (cause there one can use conditions) and modify it there. Then I bonked my head on the keyboard a couple of times after I realized that's probably why you asked the question :] (btw you know including a nonexisting directory is not really a problem since the compiler will happily ignore it?)
Maybe there's a simpler solution, but lastly I figured: if nothing works, my favourite msbuild toy aka CodeTaskFactory must be able to fix it. It does (I hope, didn't fully test the result), but it's not straightforward at all. Here you go, make sure to invoke the Test target somewhere before the C++ build starts.
<!--Uncomment the below to define some ProtocolBuffers-->
<!--<ItemGroup>
<ProtocolBuffer Include="Whitelist.proto" />
<ProtocolBuffer Include="Whitelist2.proto" />
</ItemGroup>-->
<!--Suppose these are your default include files defined in your C++ project-->
<ItemDefinitionGroup Label="DefaultIncludes">
<ClCompile>
<AdditionalIncludeDirectories>/path/to/x;/path/to/y</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<!--Include at least one item so we can play with it-->
<ItemGroup>
<ClCompile Include="iamaninclude"/>
</ItemGroup>
<!--Use code to append to AdditionalIncludeDirectories-->
<UsingTask TaskName="AppendMetadata" TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<Append ParameterType="System.String" Required="true"/>
<ItemList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true"/>
<OutputItemList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
</ParameterGroup>
<Task>
<Code>
<![CDATA[
const string dirz = "AdditionalIncludeDirectories";
foreach( var item in ItemList )
{
var cur = item.GetMetadata( dirz );
item.SetMetadata( dirz, cur + ";" + Append );
}
OutputItemList = ItemList;
]]>
</Code>
</Task>
</UsingTask>
<!--Main target-->
<Target Name="Test">
<!--stage 1: copy the itemgroup, then clear it:
if an Output TaskParameter is an Itemgroup, apparently the content
gets appended to the group instead of replacing it.
Found no documentation about this whatsoever though???-->
<ItemGroup Condition="#(ProtocolBuffer) != ''">
<ClCompileCopy Include="#(ClCompile)"/>
<ClCompile Remove="#(ClCompile)"/>
</ItemGroup>
<!--stage 2: append 'ProtoBufIncludeDir' to AdditionalIncludeDirectories,
and append the result to the origiginal again-->
<AppendMetadata ItemList="#(ClCompileCopy)" Append="ProtoBufIncludeDir" Condition="#(ProtocolBuffer) != ''">
<Output ItemName="ClCompile" TaskParameter="OutputItemList"/>
</AppendMetadata>
<!--stage 3: use modified itemgroup-->
<Message Text="#(ClCompile->'%(Identity): %(AdditionalIncludeDirectories)')"/>
</Target>
This prints
iamaninclude: /path/to/x;/path/to/y
unless the ProtocolBuffer is not empty in which case it prints
iamaninclude: /path/to/x;/path/to/y;ProtoBufIncludeDir

VS2010 custom build tool for generating .h file

I'm trying to integrate a custom build tool in VS2010 that generates a .h-file from a source file. I've created a .xml, .targets and .props for the step. The XML is mostly copy-pasted from the MASM-file and ends with:
<ItemType Name="FOO" DisplayName="Foo compiler" />
<FileExtension Name="*.foo" ContentType="FOO" />
<ContentType Name="FOO" DisplayName="Foo compiler" ItemType="FOO" />
This maps all my .foo files to the Foo compiler that's defined in the .props:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml" />
<AvailableItemName Include="FOO">
<Targets>FooCompile</Targets>
</AvailableItemName>
</ItemGroup>
<UsingTask TaskName="FOO" TaskFactory="XamlTaskFactory" AssemblyName="Microsoft.Build.Tasks.v4.0">
<Task>$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml</Task>
</UsingTask>
<Target Name="FooCompile" BeforeTargets="$(FOOBeforeTargets)" AfterTargets="$(FOOAfterTargets)" Condition="'#(FOO)' != ''" Outputs="%(FOO.Outputs)" Inputs="%(FOO.Identity);%(FOO.AdditionalDependencies);$(MSBuildProjectFile)" DependsOnTargets="_SelectedFiles">
<Message Importance="High" Text="#(FOO)" />
<FOO Condition="'#(FOO)' != '' and '%(FOO.ExcludedFromBuild)' != 'true'"
CommandLineTemplate="%(FOO.CommandLineTemplate)"
OutputFileName="%(FOO.OutputFileName)"
Inputs="%(FOO.Identity)" />
</Target>
</Project>
When I compile my project it successfully identifies and compiles my foo files:
1>FooCompile:
1> apa.foo
1>FooCompile:
1> banan.foo
1>ClCompile:
1> test.cpp
1> main.cpp
My question is why does it print "FooCompile:" once for each file while the ClCompile doesn't? Is there any way to change this?
If I change a cpp file and build, I'll also get this output once for each file, which I want to avoid:
1>FooCompile:
1>Skipping target "FooCompile" because all output files are up-to-date with respect to the input files.
1>FooCompile:
1>Skipping target "FooCompile" because all output files are up-to-date with respect to the input files.
The FooCompile target is using "target batching" which causes the target to iterate once for each item in the array specified for the Outputs attribute, %(Foo). The ClCompile target on the other hand operates using the entire item array #(ClCompile).
You can alter the verbosity of the logger to avoid the messages, specify /v:minimal, but of course you may be filtering out other information as well.