Execute a XCopy operation after all the projects build in MSBuild - msbuild

I have a .proj file which is configured to execute a solution file which in turn build all the projects in the solution.
I want to add an XCopy operation which should copy the .dll files of all projects to another location only after all the projects build is completed.
I have tried with below, but it is not copying the dlls.
I am newbie in writing MSBuild tags, so it could be that I may be wrong in choosing this approach to write the task in this way.
Please provide a solution, if anyone knows.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition="'$(Configuration)' == 'Release|Debug'"/>
<Platform Condition="'$(Platform)' == ''">x64</Platform>
</PropertyGroup>
<ItemDefinitionGroup>
<SolutionToBuild>
<Properties>Configuration=$(Configuration);Platform=$(Platform)</Properties>
<Targets>Clean;Build</Targets>
</SolutionToBuild>
</ItemDefinitionGroup>
<ItemGroup>
<SolutionToBuild Include="..\Seg\Algorithms.sln" />
</ItemGroup>
<Target Name="Build" >
<MSBuild Projects="#(SolutionToBuild)" Targets="%(SolutionToBuild.Targets)" Properties="%(SolutionToBuild.Properties)" BuildInParallel="false" ContinueOnError="false" />
</Target>
<Target Name="Clean">
<MSBuild Projects="#(SolutionToBuild)" Targets="Clean" Properties="%(SolutionToBuild.Properties)" BuildInParallel="false" ContinueOnError="false" />
</Target>
<PropertyGroup>
<CopyDestination>..\Extern\Algo\bin\$(Configuration)\</CopyDestination>
<CopySource>..\Seg\Algorithms\$(Configuration)\DoBin\</CopySource>
</PropertyGroup>
<ItemGroup>
<FilesToCopy Include="$(CopySource)*.dll"/>
</ItemGroup>
<ItemGroup>
<CustomBuildStep Include ="#(FilesToCopy)">
<Message>Copying..</Message>
<Command> XCOPY %(Identity) $(CopyDestination) /f /y </Command>
</CustomBuildStep>
</ItemGroup>
<PropertyGroup>
<CustomBuildAfterTargets>Build</CustomBuildAfterTargets>
</PropertyGroup>
</Project>

Think of Targets as methods that are called. They run in sequence, so you just need to put your copy after the solution build:
<Target Name="Build">
<MSBuild Projects="#(SolutionToBuild)" Targets="%(SolutionToBuild.Targets)" Properties="%(SolutionToBuild.Properties)" BuildInParallel="false" ContinueOnError="false" />
<ItemGroup>
<FilesToCopy Include="..\Seg\Algorithms\$(Configuration)\DoBin\*.dll" />
</ItemGroup>
<Copy SourceFiles="#(FilesToCopy)" DestinationFolder="..\Extern\Algo\bin\$(Configuration)\" SkipUnchangedFiles="true" />
</Target>

Related

MSBuild: how to include generated classes into compilation?

I have the following MSBuild script:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
...
<BuildDependsOn>
NSwag;
$(BuildDependsOn)
</BuildDependsOn>
<!--<AfterTransform>NSwag</AfterTransform>-->
</PropertyGroup>
<ItemGroup>
...
</ItemGroup>
<Target Name="NSwag" BeforeTargets="BeforeBuild">
<Message Text="Generating C# client code via NSwag" Importance="high" />
<!-- ISSUE HERE -->
<Copy SourceFiles="..\..\MyClient.cs" DestinationFiles="Gen\MyClient.cs" />
</Target>
</Project>
The Target "NSwag" above is going to be used for code generation tool. But to simplify things, I use here just a file copy command.
The issue is that the .cs files added within this Target are not visible in the MSBuild compilation:
The type or namespace name 'MyClient' does not exist in the namespace 'MyNamespace'
NOTE: The issue occurs only if the file didn't exist in the destination folder.
NOTE: I was trying to mangle with the following but with no success so far:
<Target Name="RemoveSourceCodeDuplicates" BeforeTargets="BeforeBuild;BeforeRebuild" DependsOnTargets="UpdateGeneratedFiles">
<RemoveDuplicates Inputs="#(Compile)">
<Output TaskParameter="Filtered" ItemName="Compile"/>
</RemoveDuplicates>
</Target>
and
<Target Name="UpdateGeneratedFiles" BeforeTargets="BeforeBuild;BeforeRebuild" DependsOnTargets="NSwag">
<ItemGroup>
<Compile Include="Gen\MyClient.cs" Condition="!Exists('Gen\MyClient.cs')" />
</ItemGroup>
</Target>
What am I missing here?
I think I found a workaround for that - check and include the files first (UpdateGeneratedFiles target), then generate them (NSwag target). See the script below:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
...
<BuildDependsOn>
NSwag;
$(BuildDependsOn)
</BuildDependsOn>
</PropertyGroup>
<Target Name="NSwag" BeforeTargets="BeforeBuild;BeforeRebuild"
DependsOnTargets="UpdateGeneratedFiles">
<Message Text="Generating C# client code via NSwag" Importance="high" />
<Copy SourceFiles="..\..\MyClient.cs" DestinationFiles="Gen\MyClient.cs" />
</Target>
<Target Name="UpdateGeneratedFiles" BeforeTargets="BeforeBuild;BeforeRebuild" >
<ItemGroup>
<Compile Include="Gen\MyClient.cs" Condition="!Exists('Gen\MyClient.cs')" />
</ItemGroup>
</Target>
</Project>

How to extract directory from property?

I have a property GroupProj storing a full path name. How can I extract the directory of the property?
I have the following code, but it doesn't work as expected:
<PropertyGroup>
<GroupProj>C:\development\project\default.groupproj</GroupProj>
</PropertyGroup>
<Target Name="Default">
<Message Text="Echo: $(GroupProj->'%(RootDir)')" />
</Target>
I will describe my actual intention of doing so. Perhaps there is a way to do the job that I am not aware of.
I have a Delphi groupproj (MSBuild project) file, C:\development\project\default.groupproj:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Projects Include="project1.dproj">
<Dependencies/>
</Projects>
<Projects Include="project2.dproj">
<Dependencies/>
</Projects>
<Projects Include="project3.dproj">
<Dependencies/>
</Projects>
</ItemGroup>
...
</Project>
There are other 3 MSBuild files (project1.dproj, project2.dproj and project3.dproj) stored in same folder as default.groupproj.
I create a MSBuild project file (c:\test.targets):
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build" ToolsVersion="3.5">
<Import Project="$(GroupProj)" />
<Target Name="Build">
<MSBuild BuildInParallel="True" Projects="project1.dproj;project2.dproj;project3.dproj"/>
</Target>
</Project>
And execute as:
c:\> msbuild /p:GroupProj="C:\development\project\default.groupproj" test.targets
The execution shall fail as MSBuild can't find projectN.dproj file. The issue shall be the working directory isn't set to default.groupproj.
One straight solution come into my mind is to extract directory of $(GroupProj) and concat to there projectN.dproj file.
That's the whole story of my question.
Try something like this:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<GroupProj>C:\development\project\default.groupproj</GroupProj>
</PropertyGroup>
<Target Name="Build">
<CreateItem Include="$(GroupProj)">
<Output TaskParameter="Include" ItemName="ItemFromProp"/>
</CreateItem>
<Message Text="1. #(ItemFromProp -> '%(RootDir)%(Directory)')"/>
<Message Text="2. %(ItemFromProp.RootDir)%(ItemFromProp.Directory)"/>
<Message Text="3. %(ItemFromProp.Identity)"/>
<Message Text="4. %(ItemFromProp.FullPath)"/>
<Message Text="5. %(ItemFromProp.FileName)"/>
<Message Text="6. %(ItemFromProp.Extension)"/>
</Target>
</Project>
EDIT:
To build the projects in parallel try this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="GetGroupProjPath">
<ItemGroup>
<GroupProj Include="$(GroupProj)" />
<GroupProjPath Include="#(GroupProj->'%(Directory)')" />
</ItemGroup>
<PropertyGroup>
<GroupProjPath>#(GroupProjPath->'%(RootDir)%(Identity)')</GroupProjPath>
</PropertyGroup>
</Target>
<Import Project="$(GroupProj)" />
<Target Name="GetDProjs" DependsOnTargets="GetGroupProjPath">
<ItemGroup>
<DProjs Include="#(Projects->'$(GroupProjPath)%(FileName)%(Extension)')" />
</ItemGroup>
</Target>
<Target Name="Build" DependsOnTargets="GetDProjs">
<Message Text="#(DProjs)" />
</Target>
</Project>

How to give a different OutputPath per project per build configuration with MSBuild?

Multiple projects have to be build with one ore more configurations (debug/release/...).
The output of the build needs to be copied to a folder (BuildOutputPath).
There is a default BuildOutputFolder, but for some project you can indicate that the output needs to be put in a extra child folder.
For example:
Configuration are:
- debug
- release
The projects are:
Project1 (BuildOutputFolder)
Project2 (BuildOutputFolder)
Project3 (BuildOutputFolder\Child)
The end result should look like this:
\\BuildOutput\
debug\
project1.dll
project2.dll
Child\
Project3.dll
release\
project1.dll
project2.dll
Child\
Project3.dll
I got this far atm, but can't figure out how to override the OutputPath per project.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="Build" >
<ItemGroup>
<ConfigList Include="Debug" />
<ConfigList Include="Release" />
</ItemGroup>
<PropertyGroup>
<BuildOutputPath>$(MSBuildProjectDirectory)\BuildOutput\</BuildOutputPath>
</PropertyGroup>
<ItemGroup>
<Projects Include="project1.csproj" />
<Projects Include="project2.csproj" />
<Projects Include="project3.csproj" />
</ItemGroup>
<Target Name="Build">
<MSBuild Projects="#(Projects)"
BuildInParallel="true"
Properties="Configuration=%(ConfigList.Identity);OutputPath=$(BuildOutputPath)%(ConfigList.Identity)" />
</Target>
</Project>
How would you accomplish this in a MSBuild project file ?
Your'e attempting to call a task recursively in two different contexts. 2 configurations and 3 projects requires 6 calls to the build task. You need to layout the project in such a way that for each item in ConfigList a call is made multiplied by each item in Projects.
Also use ItemDefinitionGroup to set default shared properties:
<ItemGroup>
<ConfigList Include="Debug" />
<ConfigList Include="Release" />
</ItemGroup>
<ItemDefinitionGroup>
<Projects>
<BuildOutputPath>$(MSBuildProjectDirectory)\BuildOutput\</BuildOutputPath>
</Projects>
</ItemDefinitionGroup>
<ItemGroup>
<Projects Include="project1.csproj" />
<Projects Include="project2.csproj" />
<Projects Include="project3.csproj" >
<Subfolder>Child</Subfolder>
</Projects>
</ItemGroup>
<Target Name="Build">
<MSBuild Projects="$(MSBuildProjectFullPath)"
Targets="_BuildSingleConfiguration"
Properties="Configuration=%(ConfigList.Identity)" />
</Target>
<Target Name="_BuildSingleConfiguration">
<MSBuild Projects="#(Projects)"
BuildInParallel="true"
Properties="Configuration=$(Configuration);OutputPath=%(Projects.BuildOutputPath)$(Configuration)\%(Projects.Subfolder)" />
</Target>
</Project>
Try to do it using Project metadata
<ItemGroup>
<Projects Include="project1.csproj">
<ChildFolder/>
</Project>
<Projects Include="project2.csproj">
<ChildFolder/>
</Project>
<Projects Include="project3.csproj">
<ChildFolder>Child</ChildFolder>
</Project>
</ItemGroup>
<Target Name="Build">
<MSBuild Projects="#(Projects)"
BuildInParallel="true"
Properties="Configuration=%(ConfigList.Identity);OutputPath=$(BuildOutputPath)%(ConfigList.Identity)%(Project.ChildFolder)" />

Using MSBuild to buld a solution (.sln) with many projects in how can I make each project build into its own folder?

I am trying to create a simple build process for a quite complex (many projects) vs2010 solution.
I wish for a folder structure such as this
-Build
-Proj1
-proj1.exe
-proj1.dll
-Proj2
-proj2.exe
-proj2.dll
......
-Projn
-projn.exe
-projn.dll
What I am getting from my attempts below is
-Build
-proj1.exe
-proj1.dll
-proj2.exe
-proj2.dll
-projn.exe
-projn.dll
I currently have this as a .proj file. (see below)
This builds things fine, however it puts everything in the "build" folder that I specify. I want each project to be in its own seperate folder within that 'build' folder. How can I achive this?
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<BuildOutputDir>C:\Projects\BuildScripts\Build</BuildOutputDir>
<SolutionToCompile>PathToSolution.sln</SolutionToCompile>
</PropertyGroup>
<Target Name="Clean">
<RemoveDir Directories="$(BuildOutputDir)" />
</Target>
<Target Name="Compile">
<MakeDir Directories="$(BuildOutputDir)" />
<MSBuild Projects="$(SolutionToCompile)"
properties = "OutputPath=$(BuildOutputDir)" Targets="Rebuild" />
</Target>
<Target Name="Build" DependsOnTargets="Clean;Compile">
<Message Text="Clean, Compile"/>
</Target>
</Project>
I call the .proj with a simple bat
"%windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" /nologo externalBuild.proj /m:2 %*
pause
I have also tried a more complex version (copy and paste!) that looks more like it should work, but still puts things in a single folder.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="BuildAll" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ProjectsToBuild Include="path to solution folder\**\*proj" Exclude="$(MSBuildProjectFile)"/>
</ItemGroup>
<PropertyGroup>
<Configuration>CI</Configuration>
</PropertyGroup>
<Target Name="CoreBuild">
<MSBuild Projects ="#(ProjectsToBuild)"
ContinueOnError ="false"
Properties="Configuration=$(Configuration)">
<Output ItemName="OutputFiles" TaskParameter="TargetOutputs"/>
</MSBuild>
</Target>
<PropertyGroup>
<DestFolder>Build\</DestFolder>
</PropertyGroup>
<Target Name="CopyFiles">
<Copy SourceFiles="#(OutputFiles)"
DestinationFiles="#(OutputFiles->'$(DestFolder)%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
<Target Name="CleanAll">
<!-- Delete any files this process may have created from a previous execution -->
<CreateItem Include="$(DestFolder)\**\*exe;$(DestFolder)\**\*dll">
<Output ItemName="GeneratedFiles" TaskParameter="Include"/>
</CreateItem>
<Delete Files="#(GeneratedFiles)"/>
<MSBuild Projects="#(ProjectsToBuild)" Targets="Clean" Properties="Configuration=$(Configuration);"/>
</Target>
<PropertyGroup>
<BuildAllDependsOn>CleanAll;CoreBuild;CopyFiles</BuildAllDependsOn>
</PropertyGroup>
<Target Name="BuildAll" DependsOnTargets="$(BuildAllDependsOn)"/>
</Project>
Using devenv.com to build from the command line will do what you want. It will use the output directories specified in the project files. This is what we're using, because at the moment we don't need more control over the build mechanism.

Why won't the copy task item work in my msbuild script?

I have the following code in my msbuild script:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" InitialTargets="Build">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<SolutionName>CommTrac.Web\CommTrac.Web</SolutionName>
<SolutionFileName>$(SolutionName).csproj</SolutionFileName>
</PropertyGroup>
<Target Name="Build">
<Message Text="Building the solution"/>
<MSBuild Projects="$(SolutionFileName)" ContinueOnError="false" Properties="Configuration=$(Configuration)" />
</Target>
<Target Name="CopyOutput" DependsOnTargets="Build">
<ItemGroup>
<BinFolder Exclude="*.pdb" Include="$(ProjectDir)bin\**\*.*"/>
<BuildOutputFolder Include="C:\BuildOutput" />
</ItemGroup>
<Message Text="Copying from directory: $(BinFolder)"/>
<Copy SourceFiles="$(BinFolder)" DestinationFolder="$(BuildOutputFolder)"/>
</Target>
</Project>
For some reason, it will not copy the files to my output directory. I have tried all the similar
solutions with other questions that I have seen similar to this issue. Anyone have any ideas?
BindFolder and BuildOutputFolder are items, not properties. So you need to reference them using #(BindFolder) and #(BuildOutputFolder) instead of using '$'.