MSBuild Project: Get item list from another project and print foreach - msbuild

For example, there are two projects:
Main.Proj
<MyCustomItemHa Include="path1"/>
<MyCustomItemHa Include="path2"/>
<MyCustomItemHa Include="path3"/>
And there is a separate project
Secondary.Proj
<Target Name="Printtt">
** How can I execute <Message here for each of paths imported above? **
** To Get output equivalent to: **
** <Message Text="path1" /> **
** <Message Text="path2" /> **
** <Message Text="path3" /> **
** for each MyCustomItemHa from Main.Proj **
</Target>

MSBuild is a declarative language and has no loops. There is no foreach loop in MSBuild.
You can use task batching - see MSBuild batching.
As an example, the code
<ItemGroup>
<Fruit Include="Apple" />
<Fruit Include="Banana" />
</ItemGroup>
<Target Name="DisplayFruit">
<Message Text="%(Fruit.Identity)" />
</Target>
will display
Apple
Banana
Two separate projects can't 'see' each other and can't get an ItemGroup from each other. But you can create a common file that defines the ItemGroup and each project can Import the common file.

Related

MSBuild - can I compile all solutions in child directories?

Is there a way in MSBuild to compile all solutions in folders, and sub-folders, and sub... under a specified parent?
We have a bunch of sample programs we ship with our library. I want to add to the build process that we know they all compile.
You can create own targets for restore and build operations. For example:
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" InitialTargets="EntryRestore" DefaultTargets="EntryMSBuild">
<ItemGroup>
<SolutionFile Include="./**/*.sln"/>
</ItemGroup>
<Target Name="EntryRestore">
<Message Text="-----Entry-----" Importance="high"/>
<Message Text=" Restore " Importance="high"/>
<Message Text="-----Entry-----" Importance="high"/>
<MSBuild Projects="%(SolutionFile.Identity)" Targets="restore"/>
</Target>
<Target Name="EntryMSBuild">
<Message Text="-----Entry-----" Importance="high" />
<Message Text=" Build " Importance="high" />
<Message Text="-----Entry-----" Importance="high" />
<MSBuild Projects="%(SolutionFile.Identity)" Targets="build" />
</Target>
</Project>
Item SolutionFile will contains paths for all .sln files that located in current directory and its subdirectories. You also may define path in CLI and perform searching relative it.
<ItemGroup>
<SolutionFile Include="$(MyDir)/**/*.sln"/>
</ItemGroup>
MSBuild task launches MSBuild for performing specified targets. In our cas it are restore and build. It task used 'batching' that allow iterate over items by metadata.
&"D:\Visual Studio\MSBuild\15.0\Bin\msbuild.exe" .\Make.targets
MSBuild targets | Item element | Item metadata in task batching | MSBuild task

Multiple UsingTask in csprojfile

I have two files that I want to configure by environment: App.config and ApplicationInsights.config. I have created the files App.Debug.config and ApplicationINsights.Debug.config and added the following tasks to the csproj file:
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="exists('app.$(Configuration).config')">
<TransformXml Source="app.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="app.$(Configuration).config" />
<ItemGroup>
<AppConfigWithTargetPath Remove="app.config" />
<AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
<TargetPath>$(TargetFileName).config</TargetPath>
</AppConfigWithTargetPath>
</ItemGroup>
</Target>
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="exists('ApplicationInsights.$(Configuration).config')">
<Message Text="Transforming app insights config file to $(OutputPath)\ApplicationInsights.config" Importance="high" />
<TransformXml Source="ApplicationInsights.config" Transform="ApplicationInsights.$(Configuration).config" Destination="$(OutputPath)\ApplicationInsights.config" />
</Target>
Both tasks work when they are the only task in the file, but when both are included only the second transform is executed. I have tried giving the tasks different Names, but to no avail. What can I do to get both tasks to run?
You have to give the two tasks different Names and then hook into the existing AfterCompile target:
<Target Name="SomeUniqueName1" AfterTargets="AfterCompile" …>
…
</Target>
<Target Name="SomeUniqueName2" AfterTargets="AfterCompile" …>
…
</Target>
The <UsingTask> only needs to be there once to define the imported TransformXml task.

MSBuild: Add additional files to compile without altering the project file

After looking around I can't find a simple answer to this problem.
I am trying to create an MSBuild file to allow me to easily use SpecFlow and NUnit within Visual Studio 2010 express.
The file below is not complete this is just a proof of concept and it needs to be made more generic.
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<BuildDependsOn>
BuildSolution;
SpecFlow;
BuildProject;
NUnit;
</BuildDependsOn>
</PropertyGroup>
<PropertyGroup>
<Solution>C:\Users\Craig\Documents\My Dropbox\Cells\Cells.sln</Solution>
<CSProject>C:\Users\Craig\Documents\My Dropbox\Cells\Configuration\Configuration.csproj</CSProject>
<DLL>C:\Users\Craig\Documents\My Dropbox\Cells\Configuration\bin\Debug\Configuration.dll</DLL>
<CSFile>C:\Users\Craig\Documents\My Dropbox\Cells\Configuration\SpecFlowFeature1.feature.cs</CSFile>
</PropertyGroup>
<Target Name="Build" DependsOnTargets="$(BuildDependsOn)">
<Message Text="Build Started" Importance="high" />
<Message Text="Build Ended" Importance="high" />
</Target>
<Target Name="BuildSolution">
<Message Text="BuildSolution Started" Importance="high" />
<MSBuild Projects="$(Solution)" Properties="Configuration=Debug" />
<Message Text="BuildSolution Ended" Importance="high" />
</Target>
<Target Name="SpecFlow">
<Message Text="SpecFlow Started" Importance="high" />
<Exec Command='SpecFlow generateall "$(CSProject)"' />
<Message Text="SpecFlow Ended" Importance="high" />
</Target>
<Target Name="BuildProject">
<Message Text="BuildProject Started" Importance="high" />
<MSBuild Projects="$(CSProject)" Properties="Configuration=Debug" />
<Message Text="BuildProject Ended" Importance="high" />
</Target>
<Target Name="NUnit">
<Message Text="NUnit Started" Importance="high" />
<Exec Command='NUnit /run "$(DLL)"' />
<Message Text="NUnit Ended" Importance="high" />
</Target>
</Project>
The SpecFlow Task looks in the .csproj file and creates a SpecFlowFeature1.feature.cs.
I need to include this file when building the .csproj so that NUnit can use it.
I know I could modify (either directly or on a copy) the .csproj file to include the generated file but I'd prefer to avoid this.
My question is: Is there a way to use the MSBuild Task to build the project file and tell it to include an additional file to include in the build?
Thank you.
I found no way of doing it without editing the project file.
So I made an MSBuild file to:
Copy the project files
Run the copies through SpecFlow
Add the new .cs files to the copied projects
Compile the projects
Debug Run each of the compiled DLLs through NUnit
Clean up - Delete the copied projects
I've blogged about how to use it here:
http://learntdd.wordpress.com/2010/06/10/using-specflow-and-nunit-on-visual-studio-2010-express/
(It's version 1, I'd like to improve the script)
I couldn't think of any way to achieve without any modification to the .csproj file.
The approach I'd suggest would look like this.
In your .csproj you Import a container target file
...
<Import Project="SpecFlow.target" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
...
just above the CSharp.targets.
Specflow.targets would look like this
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Compile Include="#(Compile)" />
</ItemGroup>
</Project>
so it doesn't harm while building the project from VS.
You could then use the Output of your SpecFlow Exec and add it to the SpecFlow.targets file
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Compile Include="#(Compile)" />
<Compile Include="SpecFlowFeature1.feature.cs" />
</ItemGroup>
</Project>
...
and clean SpecFlow.targets after building your .csproj.

How to invoke the same msbuild target twice with different parameters from within msbuild project file itself

I have the following piece of msbuild code:
<PropertyGroup>
<DirA>C:\DirA\</DirA>
<DirB>C:\DirB\</DirB>
</PropertyGroup>
<Target Name="CopyToDirA"
Condition="Exists('$(DirA)') AND '#(FilesToCopy)' != ''"
Inputs="#(FilesToCopy)"
Outputs="#(FilesToCopy -> '$(DirA)%(Filename)%(Extension)')">
<Copy SourceFiles="#(FilesToCopy)" DestinationFolder="$(DirA)" />
</Target>
<Target Name="CopyToDirB"
Condition="Exists('$(DirB)') AND '#(FilesToCopy)' != ''"
Inputs="#(FilesToCopy)"
Outputs="#(FilesToCopy -> '$(DirB)%(Filename)%(Extension)')">
<Copy SourceFiles="#(FilesToCopy)" DestinationFolder="$(DirB)" />
</Target>
<Target Name="CopyFiles" DependsOnTargets="CopyToDirA;CopyToDirB"/>
So invoking the target CopyFiles copies the relevant files to $(DirA) and $(DirB), provided they are not already there and up-to-date.
But the targets CopyToDirA and CopyToDirB look identical except one copies to $(DirA) and the other - to $(DirB). Is it possible to unify them into one target first invoked with $(DirA) and then with $(DirB)?
Thanks.
You should be able to generate an ItemGroup containing the Dirs and then % on that.
<ItemGroup>
<Dirs Include="C:\DirA\;C:\DirB\">
</ItemGroup>
<Target Name="CopyFiles"
Condition="Exists('%(Dirs)') AND '#(FilesToCopy)' != ''"
Inputs="#(FilesToCopy)"
Outputs="#(FilesToCopy -> '%(Dirs)%(Filename)%(Extension)')">
<Copy SourceFiles="#(FilesToCopy)" DestinationFolder="%(Dirs)" />
</Target>
Or you can do 2 explicit calls:
<Target Name="CopyFiles">
<MsBuild Projects="$(MSBuildProjectFullPath)" Targets="CopyASetOfFiles" Properties="FilesToCopy=#(FilesToCopy);DestDir=$(DirA)" />
<MsBuild Projects="$(MSBuildProjectFullPath)" Targets="CopyASetOfFiles" Properties="FilesToCopy=#(FilesToCopy);DestDir=$(DirB)" />
</Target>
<Target Name="CopyASetOfFiles"
Condition="Exists('$(DestDir)') AND '#(FilesToCopy)' != ''"
Inputs="#(FilesToCopy)"
Outputs="#(FilesToCopy -> '$(DestDir)%(Filename)%(Extension)')">
<Copy SourceFiles="#(FilesToCopy)" DestinationFolder="$(DestDir)" />
</Target>
I haven't tested either syntax, but am relatively more confident of the second.
(The answer, if there is one, is in my Sayed Hashimi book on my desk - you'll have to wait until the first of:
Get the book
I get bored
Sayed finds this post and comes up with a brilliant tested answer)
As someone already mentiond the answer is batching.
Here are some links:
MSBuild Batching Part 1
MSBuild Batching Part 2
MSBuild Batching Part 3
MSBuild RE: Enforcing the Build Agent in a Team Build
Yes, what you want is called batching in MSBuild. The
;%(Dirs.Identity)
Defined in the Outputs will cause this task to be executed for each item in the Dirs ItemGroup.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="CopyFiles"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="3.5">
<ItemGroup>
<Dirs Include="C:\DirA" />
<Dirs Include="C:\DirB" />
</ItemGroup>
<Target Name="CopyFiles"
Inputs="#(FilesToCopy);#(Dirs)"
Outputs="#(FilesToCopy -> '%(Dirs.Identity)%(Filename)%(Extension)');%(Dirs.Identity)" >
<Message Text="%(Dirs.Identity)" />
</Target>
</Project>
Outputs:
Build started 8/19/2009 10:11:57 PM.
Project "D:\temp\test.proj" on node 0 (default targets).
C:\DirA
CopyFiles:
C:\DirB
Done Building Project "D:\temp\test.proj" (default targets).
Change the Message task to Copy task with the following condition and you are done:
Condition="Exists('%(Dirs.Identity)') AND '#(FilesToCopy)' != ''"

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