How to execute a target when generic files exist - build

I am using msbuild for my project and need to have a target execute only if, in a certain locations, a whatever text file exists.
My file is:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MYFolder>C:\myfolder\</MYFolder>
</PropertyGroup>
<ItemGroup>
<TXTFiles Include="$(MYFolder)*.scss"/>
</ItemGroup>
<Target Condition="???"></Target>
</Project>
What I tried
This:
Condition="!Exists('#(TXTFiles ->'%(FullPath)')')"
But it does not work.
This:
Condition="!Exists('#(TXTFiles)')"
But it does not work.
What condition can I use?

You can try make condition on the count of items and execute target if count is greater than zero or non-equal to zero.
Condition=" '0' != '#(TXTFiles->Count())' "
or
Condition=" '0' < '#(TXTFiles->Count())' "
Symbol < must be escaped in xml.

Related

Default user-specific properties

I'm working on a project that requires a property named $(LibPath) to be set. This value should be set by a plugin, however it is saved in the projectname.vcxproj.user file. This is a problem for when someone has this file removed (through git clean for example) or someone new starting on the project.
In order to have a default value, I've created a Property Sheet which is impoted in the project file at the very top.
<PropertyGroup Label="UserMacros">
<LibPath Condition="'$(LibPath)' == ''">PathToLib</LibPath>
</PropertyGroup>
<ItemGroup>
<BuildMacro Include="LibPath">
<Value>$(LibPath)</Value>
</BuildMacro>
</ItemGroup>
It now works for everyone whose path matches the default. When it doesn't, they can change it from within VS by going to Property Manager -> Properties Sheet file -> User Macros.
Now the remaining issue is that when this path doesn't match and needs to be changed, the value inside the Propety Sheet is changed too.
So my question: Is there a way to define a property that can be edited from within VS, and that is then saved in the vcxproj.user file (or any other user-specific file which overrides the default value)?
Is there a way to define a property that can be edited from within VS that is saved in the vcxproj.user file?
AFAIK, I am afraid the reason why you can not edit the defined property in Visual Studio when that path does not match is that the vcxproj.user file is imported after than the Propety Sheet.
If we create a property sheet, we will get following import file TestSheet.props in the project file:
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="TestSheet.props" />
</ImportGroup>
The vcxproj.user file is imported by following Import:
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
This file is under the TestSheet.props in the project file:
In this case, MSBuild will use the value of $(LibPath) in the vcxproj.user file replace the value in the propety sheet. So it only works for everyone whose path matches the default, if you change the value by going to Property Manager -> Properties Sheet file -> User Macros, the value will be overwrite by the next import file Microsoft.Cpp.targets.
To resolve this issue, you can comment all the <Import Project="TestSheet.props" />lines in the project file, and add this line after import Microsoft.Cpp.targets file:
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="TestSheet.props" />
With this setting, if you change the value in the User Macros, this value will overwrite the value in the vcxproj.user file.

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.

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

MSBuild RegexMatch not matching

I have the following
<RegexMatch Input="$(Configuration)" Expression="^.*?(?=\.)">
<Output ItemName="Theme" TaskParameter="Output" />
</RegexMatch>
My configuration variable is as follows Theme.Environment
So "Default.Debug"
or "Yellow.Release"
I would like to get the first portion in to a varaible called theme.
I have tested this regex and it works in stand alone regex testers
^.*?(?=\.)
but not when used in my build file.
I am echoing the variable out so that i can see the output
<Exec Command="echo $(Theme)"/>
<Exec Command="echo $(Configuration)"/>
Ideas?
If you should use MSBuild Community tasks for that - check this line: <Output PropertyName="Theme" TaskParameter="Output" />
you should use PropertyName="Theme" if you want to refer it like $(Theme) later.
ItemName will create items set, not property.
But it's much simplier to use MSBuild 4.0 inline functions than Msbuild community tasks for that concrete task. Your code will looks like this (adopt for your script):
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTarget="Play">
<PropertyGroup>
<Configuration>Yellow.Release</Configuration>
</PropertyGroup>
<Target Name="Play">
<PropertyGroup>
<Theme>$([System.Text.RegularExpressions.Regex]::Match($(Configuration), `^.*?(?=\.)`))</Theme>
</PropertyGroup>
<Message Text="$(Theme)" />
<Message Text="$(Configuration)" />
</Target>
</Project>
Just realised that RegexMatch doenst return the matched string but rather returns the entire string if matched.
basically it called IsMatch method not Match method
Have re written as a RegexReplace
<RegexReplace Input="$(Configuration)" Expression="\..*" Replacement="" Count="1">
<Output ItemName="Theme" TaskParameter="Output" />
</RegexReplace>
After that it still wasnt working and then i realised i was doing
$(Theme)
Should have been
#(Theme)

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.