Can this MSBuild target be written more simply/elegantly? - msbuild

My MSBuild knowledge is a bit rusty. I wrote this working target today but I'm sure it can be reduced in size.
Can anyone see how to express this more simply please?
<Target Name="FolderX">
<PropertyGroup>
<Dest>$(StandardModelDir)\FolderX</Dest>
</PropertyGroup>
<ItemGroup>
<File Include="$(CustDir)\File1.sql">
<Dest>$(Dest)\Views--Alpha</Dest>
</File>
<File Include="$(CustDir)\File2.sql">
<Dest>$(Dest)\Views--Alpha</Dest>
</File>
<File Include="$(CustDir)\File3.sql">
<Dest>$(Dest)\Views--Bravo</Dest>
</File>
<File Include="$(CustDir)\File4.sql">
<Dest>$(Dest)\Views--Bravo</Dest>
</File>
</ItemGroup>
<Copy
SourceFiles="#(File)"
DestinationFolder="%(File.Dest)\.."
/>
</Target>
Note that the Files are not recursively stored in the source directory so I cannot use %(RecursiveDir)

You can use masks. In your case you can use file extension mask, *.sql
Example from MSDN: http://msdn.microsoft.com/en-us/library/3e54c37h.aspx
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<MySourceFiles Include="c:\MySourceTree\**\*.*"/>
</ItemGroup>
<Target Name="CopyFiles">
<Copy
SourceFiles="#(MySourceFiles)"
DestinationFiles="#(MySourceFiles->'c:\MyDestinationTree\%(RecursiveDir)%(Filename)%(Extension)')"
/>
</Target>
</Project>

Related

Execute a XCopy operation after all the projects build in 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>

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>

Copy a single file in MSBuild without using Exec or ItemGroup

Is there any way to do this? I just need to copy a single file and thought there may be some syntax for the SourceFiles parameter of the Copy task that means you don't need to define an ItemGroup beforehand, I'd rather stick with ItemGroup than use Exec though.
Copy files also takes a straight propertygroup as input:
<PropertyGroup>
<SourceFile>Some file</SourceFile>
</PropertyGroup>
<Copy SourceFiles="$(SourceFile)" DestinationFolder="c:\"/>
Or even just a string
<Copy SourceFiles="Pathtofile" DestinationFolder="c:\"/>
Just put the single file name as the value for "SourceFiles".
Easy-Peezey.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="AllTargetsWrapper" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WorkingCheckout>.</WorkingCheckout>
</PropertyGroup>
<Target Name="AllTargetsWrapper">
<CallTarget Targets="CopyItTarget" />
</Target>
<Target Name="CopyItTarget">
<Copy SourceFiles="c:\windows\system.ini" DestinationFolder="$(WorkingCheckout)\"/>
<Error Condition="!Exists('$(WorkingCheckout)\system.ini')" Text="No Copy Is Bad And Sad" />
</Target>
</Project>
For what it's worth, I needed to do the same thing, and wanted to put some version information in the file name. Here is how I did it for a project in $(SolutionDir) that references an executable created by another project in another solution that I can easily express the path to:
<Target Name="AfterBuild">
<GetAssemblyIdentity AssemblyFiles="$(SolutionDir)..\bin\$(Configuration)\SomeExectuable.exe">
<Output TaskParameter="Assemblies" ItemName="AssemblyVersions" />
</GetAssemblyIdentity>
<CreateProperty Value="$(TargetDir)$(TargetName)-%(AssemblyVersions.Version)$(TargetExt)">
<Output TaskParameter="Value" PropertyName="NewTargetPath" />
</CreateProperty>
<Copy SourceFiles="$(TargetPath)" DestinationFiles="$(NewTargetPath)" />
</Target>

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)" />

How to create copying items from property values?

Let's say I have a list of sub paths such as
<PropertyGroup>
<subPaths>$(path1)\**\*; $(path2)\**\*; $(path3)\file3.txt; </subPaths>
</PropertyGroup>
I want to copy these files from folder A to folder B (surely we already have all the sub folders/files in A). What I try was:
<Target Name="Replace" DependsOnTargets="Replace_Init; Replace_Copy1Path">
</Target>
<Target Name="Replace_Init">
<PropertyGroup>
<subPaths>$(path1)\**\*; $(path2)\**\*; $(path3)\file3.txt; </subPaths>
</PropertyGroup>
<ItemGroup>
<subPathItems Include="$(subPathFiles.Split(';'))" />
</ItemGroup>
</Target>
<Target Name="Replace_Copy1Path" Outputs="%(subPathItems.Identity)">
<PropertyGroup>
<src>$(folderA)\%(subPathItems.Identity)</src>
<dest>$(folderB)\%(subPathItems.Identity)</dest>
</PropertyGroup>
<Copy SourceFiles="$(src)" DestinationFiles="$(dest)" />
</Target>
But the Copy task didn't work. It doesn't translate the **\* to files. What did I do wrong? Please help!
I don't think you can do something like that.
$(subPathFiles.Split(';')) returns a property where value are separated by semicolon, so this call is useless.
If you want to keep this mechanism you should use the task StringToItemCol from MSBuild Extension Pack :
<Target Name="Replace_Init">
<PropertyGroup>
<subPaths>$(path1)\**\*; $(path2)\**\*; $(path3)\file3.txt; </subPaths>
</PropertyGroup>
<MsBuildHelper TaskAction="StringToItemCol"
ItemString="$(subPaths)" Separator=";">
<Output TaskParameter="OutputItems" ItemName="subPathItems "/>
</MsBuildHelper>
</Target>
Otherwise, you could directly pass items with folderA and subPaths embedded :
<ItemGroup>
<subPathIt Include="$(folderA)\$(path1)\**\*"/>
<subPathIt Include="$(folderA)\$(path2)\**\*"/>
<subPathIt Include="$(folderA)\$(path3)\file3.txt" Condition="Exists('$(path3)\file3.txt')"/>
</ItemGroup>
<Target Name="Replace_Copy1Path">
<Copy SourceFiles="#(subPathItems )"
DestinationFiles="$(folderB)\%(RecursiveDir)\%(Filename)%(Extension)" />
</Target>