How do I use the exact msbuild item "unexpanded wildcard expression" value - msbuild

I have the following MSBuild .proj file content:
<ItemGroup>
<Exclude Include="*2.*"></Exclude>
</ItemGroup>
<ItemGroup>
<!-- I have 3 files in the current directory: File1.cpp, File2.cpp and File3.cpp -->
<ModuleInclude Include="*.cpp" Exclude="#(Exclude)"></ModuleInclude>
<!-- I have 3 files in the Subfolder directory: eFile1.h, eFile2.h and eFile3.h -->
<ModuleInclude Include="Subfolder\*.h" Exclude="#(Exclude->'Subfolder\%(identity)')"></ModuleInclude>
</ItemGroup>
<Target Name="Default">
<Message Text="ModuleIncludes: %(ModuleInclude.identity)" />
<Message Text="Excluded Items: #(Exclude)" />
<Message Text="Excluded Subfolder Items: #(Exclude->'Subfolder\%(identity)')" />
</Target>
I see the following output:
ModuleIncludes: File1.cpp
ModuleIncludes: File3.cpp
ModuleIncludes: Subfolder\eFile1.h
ModuleIncludes: Subfolder\eFile2.h
ModuleIncludes: Subfolder\eFile3.h
Excluded Items: File2.cpp
Excluded Subfolder Items: Subfolder\File2.cpp
What I really need is to have the following Subfolder files included
ModuleIncludes: Subfolder\eFile1.h
ModuleIncludes: Subfolder\eFile3.h
The excluded subfolder items therefore should be:
Excluded Subfolder Items: Subfolder\eFile2.h
To be able to get such an output I would need the expression
Subfolder\*2.*
The syntax that I am using
#(Exclude->'Subfolder\%(identity)')
does not give me what I need.
What would the correct syntax be? Or is this not possible?

If you want to deal with wildcards as text, use properties instead of items:
<PropertyGroup>
<FileExcludes>*2.*</FileExcludes>
</PropertyGroup>
<ItemGroup>
<ModuleInclude Include="*.cpp" Exclude="$(FileExcludes)"></ModuleInclude>
<ModuleInclude Include="Subfolder\*.h" Exclude="Subfolder\$(FileExcludes)"></ModuleInclude>
</ItemGroup>
You can even use an exclude pattern here that will match regardless of the subfolder:
<PropertyGroup>
<FileExcludes>**\*2.*</FileExcludes>
</PropertyGroup>
<ItemGroup>
<ModuleInclude Include="*.cpp" Exclude="$(FileExcludes)"></ModuleInclude>
<ModuleInclude Include="Subfolder\*.h" Exclude="$(FileExcludes)"></ModuleInclude>
</ItemGroup>
If you really need it as a list to prepend non-local folders, use metadata items:
<PropertyGroup>
<FileExclude Include="2">
<Pattern>**\*2.*</Pattern>
</FileExclude>
</PropertyGroup>
<ItemGroup>
<ModuleInclude Include="*.cpp" Exclude="#(FileExclude)"></ModuleInclude>
<ModuleInclude Include="..\shared-folder\*.h" Exclude="#(FileExclude->'shared-folder\%(Pattern)')"></ModuleInclude>
</ItemGroup>

Related

Copy subfolders and files in msbuild

I'm unable to copy subfolders and files with this code:
<ItemGroup>
<Compile Include="C:\Test\Folder1\text.txt"/>
<Compile Include="C:\Test\text1.txt"/>
</ItemGroup>
<Copy SourceFiles="#(Compile)" DestinationFiles="#(Compile->'C:\Destination\%(RecursiveDir)%(Filename)%(Extension)')" />
I get this error: Could not find a part of the path.
How to copy C:\Test\ files and subfolders to C:\Destination\ with msbuild ?
Thanks in advance for your help.
In order for the RecursiveDir metadata to be populated, you must specify a recursive wildcard (double asterisks) in your items' paths. The ** wildcard will mark the relative point at which the RecursiveDir should be applied. In your example, it sounds like you'd want to add the ** wildcard after C:\Test, so your code would need to look like the following example:
<ItemGroup>
<Compile Include="C:\Test\**\Folder1\text.txt"/>
<Compile Include="C:\Test\**\text1.txt"/>
</ItemGroup>
<Copy SourceFiles="#(Compile)" DestinationFiles="#(Compile->'C:\Destination\%(RecursiveDir)%(Filename)%(Extension)')" />
Adding the wildcard as shown above will copy the files to the following locations:
C:\Destination\text1.txt
C:\Destination\Folder1\text.txt
The same as in response above, but without additional list transformation:
<ItemGroup>
<Compile Include="C:\Test\**\Folder1\text.txt"/>
<Compile Include="C:\Test\**\text1.txt"/>
</ItemGroup>
<Copy SourceFiles="#(Compile)" DestinationFolder="C:\Destination\%(RecursiveDir)" />

msbuild to copy to multiple locations defined in item metadata

I have an item with metadata I want to copy to perform some actions on and the result to occur in multiple locations,
for example, I want to copy the file to multiple locations:
<ItemGroup>
<MyItem Include="myFile.txt">
<Out>c:\blah;c:\test</Out>
</MyItem>
</ItemGroup>
how would I setup a target to create c:\blah and c:\test if they dont exist, then copy myFile.txt to c:\blah\myFile.txt and c:\test\myFile.txt
I also want to get the list of full output paths (c:\blah\myFile.txt and c:\test\myFile.txt) if I want to clean them during a clean.
If you dont want to change the structure of you ItemGroup, you need to handle that you have a nested ItemGroup (the MetaDataElement Out). Therefor you will need to batch the ItemGroup MyItem to the target and inside you can batch Out. I made a small example project:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="CopyFiles" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<ItemGroup>
<MyItem Include="myFile.txt">
<Out>c:\blah;c:\test</Out>
</MyItem>
<MyItem Include="myFile2.txt">
<Out>c:\blah2;c:\test2</Out>
</MyItem>
</ItemGroup>
<Target Name="CopyFiles"
Inputs="%(MyItem.Identity)"
Outputs="%(MyItem.Identity)\ignore_this.msg">
<PropertyGroup>
<File>%(MyItem.Identity)</File>
</PropertyGroup>
<ItemGroup>
<Folders Include="%(MyItem.Out)" />
</ItemGroup>
<Message Text="%(Folders.Identity)\$(File)" />
</Target>
</Project>
The Output will be:
Project "D:\TEMP\test.proj" on node 1 (default targets).
CopyFiles:
c:\blah\myFile.txt
c:\test\myFile.txt
CopyFiles:
c:\blah2\myFile2.txt
c:\test2\myFile2.txt
Done Building Project "D:\TEMP\test.proj" (default targets).
Build succeeded.
0 Warning(s)
0 Error(s)
What you want to do is a concept called MSBuild Batching.
It allows you to divide item lists into different batches and pass each of those batches into a task separately.
<Target Name="CopyFiles">
<ItemGroup Label="MyFolders">
<Folder Include="c:\blah" />
<Folder Include="C:\test" />
</ItemGroup>
<Copy SourceFiles="myFile.txt" DestinationFolder="%(Folder.Identity)\">
<Output TaskParameter="CopiedFiles" ItemName="FilesCopy" />
</Copy>
</Target>
How about this:
<Target Name="CopyFiles">
<!--The item(s)-->
<ItemGroup>
<MyItem Include="myFile.txt"/>
</ItemGroup>
<!--The destinations-->
<ItemGroup>
<MyDestination Include="c:\blah"/>
<MyDestination Include="c:\test"/>
</ItemGroup>
<!--The copy-->
<Copy SourceFiles="#(MyItem)" DestinationFolder="%(MyDestination.FullPath)" />
<ItemGroup>
<FileWrites Include="%(MyDestination.FullPath)\*" />
</ItemGroup>
<!--The output -->
<Message Text="FileWrites: #(FileWrites)" Importance="high"/>
</Target>

Add Custom Metadata to Already-defined ItemGroup from Another ItemGroup

I have the following:
<ItemGroup>
<Files Include="C:\Versioning\**\file.version" />
<ItemGroup>
<ReadLinesFromFile File="%(Files.Identity)">
<Output TaskParameter="Lines" ItemName="_Version"/>
</ReadLinesFromFile>
where each file.version file contains simply one line which is - you guessed it - a version of the form Major.Minor.Build.Revision.
I want to be able to associate each item in the Files ItemGroup with its _Version by adding the latter as metadata, so that I can do something like:
<Message Text="%(Files.Identity): %(Files.Version)" />
and have MSBuild print out a nice list of file-version associations.
Is this possible?
This can be achieved by using target batching to add your Version member to the metadata. This involves moving your ReadLinesFromFile operation to its own target, using the #(Files) ItemGroup as an input.
This causes the target to be executed for each item in your ItemGroup, allowing you to read the contents (i.e. version number) from each individual file and subsequently update that item to add the Version metadata:
<Project DefaultTargets="OutputFilesAndVersions" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Files Include="C:\Versioning\**\file.version" />
</ItemGroup>
<Target Name="OutputFilesAndVersions" DependsOnTargets="RetrieveVersions">
<Message Text="#(Files->'%(Identity): %(Version)')" />
</Target>
<Target Name="RetrieveVersions" Inputs="#(Files)" Outputs="%(Files.Identity)">
<ReadLinesFromFile File="%(Files.Identity)">
<Output TaskParameter="Lines" PropertyName="_Version"/>
</ReadLinesFromFile>
<PropertyGroup>
<MyFileName>%(Files.Identity)</MyFileName>
</PropertyGroup>
<ItemGroup>
<Files Condition="'%(Files.Identity)'=='$(MyFileName)'">
<Version>$(_Version)</Version>
</Files>
</ItemGroup>
</Target>
</Project>

How do I execute tasks in MSBUILD?

I'm trying to wrap my head around MSBuild.
I have a very simple script that does the following so far:
Builds a solution and places it in my drop location.
I have created a <Target> and in it I would like to copy files and from my source control location and drop them in the drop location as well.
Eventually the script will have to create the folders etc. For now I am just trying to copy one file over to see how this works.
The solution builds and is placed in the drop location but no files are copied. The build log makes no mention of this Target ever being run.
What am I missing?
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<Target Name="Build">
<Message Text="Building msbuildintro" />
</Target>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets" />
<ProjectExtensions>
<ProjectFileVersion>2</ProjectFileVersion>
<Description></Description>
<BuildMachine>hw-tfs-build02</BuildMachine>
</ProjectExtensions>
<PropertyGroup>
/* Properties here*/
</PropertyGroup>
<ItemGroup>
<SolutionToBuild Include="$(BuildProjectFolderPath)/HostASPX/mySolution.sln">
<Targets></Targets>
<Properties></Properties>
</SolutionToBuild>
<CommonFiles Include="$(SolutionRoot)\trunk\folder\Common\Shared\js\somefile.js"></CommonFiles>
</ItemGroup>
<ItemGroup>
<ConfigurationToBuild Include="Release|Any CPU">
<FlavorToBuild>Release</FlavorToBuild>
<PlatformToBuild>Any CPU</PlatformToBuild>
</ConfigurationToBuild>
</ItemGroup>
<Target Name="CopyCommonData">
<Message Text="Copy Common Data" />
<Copy SourceFiles="#(CommonFiles)"
DestinationFiles="$(DropLocation)\Common\somefile.js" />
</Target>
</Project>
Thanks!
OH I get it.. Target Names are not 'made up'. They must be a specific Target Name found here:
http://msdn.microsoft.com/en-us/library/aa337604.aspx

MSBuild Copy task not copying files the first time round

I created a build.proj file which consists of a task to copy files that will be generated after the build is complete. The problem is that these files are not copied the first time round and I have to run msbuild again on the build.proj so that the files can be copied. Please can anyone tell me whats wrong with the following build.proj file:
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
<SourcePath Condition="'$(SourcePath)' == ''">$(MSBuildProjectDirectory)</SourcePath>
<BuildDir>$(SourcePath)\build</BuildDir>
</PropertyGroup>
<ItemGroup>
<Projects
Include="$(SourcePath)\src\myApp\application.csproj">
</Projects>
</ItemGroup>
<Target Name="Build">
<Message text = "Building project" />
<MSBuild
Projects="#(Projects)"
Properties="Configuration=$(Configuration)" />
</Target>
<ItemGroup>
<OutputFiles Include ="$(MSBuildProjectDirectory)\**\**\bin\Debug\*.*"/>
</ItemGroup>
<Target Name="CopyToBuildFolder">
<Message text = "Copying build items" />
<Copy SourceFiles="#(OutputFiles)" DestinationFolder="$(BuildDir)"/>
</Target>
<Target Name="All"
DependsOnTargets="Build; CopyToBuildFolder"/>
</Project>
The itemgroups are evaluated when the script is parsed. At that time your files aren't there yet. To be able to find the files you'll have to fill the itemgroup from within a target.
<!-- SQL Scripts which are needed for deployment -->
<Target Name="BeforeCopySqlScripts">
<CreateItem Include="$(SolutionRoot)\04\**\Databases\**\*.sql">
<Output ItemName="CopySqlScript" TaskParameter="Include"/>
</CreateItem>
</Target>
This example creates the ItemGroup named "CopySqlScript" using the expression in the Include attribute.
Edit:
Now I can read your script: add the CreateItem tag within your CopyToBuildFolder target