Overwrite targets post build event of another targets file - visual-studio

I have a NuGet package that adds to my C# projects a targets file with that content for the post build event.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ThisIsMyTarget" AfterTargets="Build">
<Exec Command="ThisIsMyCommand.exe"/>
</Target>
</Project>
In less than 1% of the projects, I don't need this command, I need another command to be executed. Is it possible with the targets files to suspend a target from another targets file?
Something like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ThisIsMyRareTarget" Overwrite="ThisIsMyTarget" AfterTargets="Build">
<Exec Command="ThisIsMyRareCommand.exe"/>
</Target>
</Project>
I don't want to split my NuGet package only for the 1% of the project.

All the credit goes to #Perry Quian-MSFT. He had right concept.
I have created 2 targets files / NuGet packages.
The standard package using this targets file.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ThisIsMyTarget" AfterTargets="Build" Condition="'$(DontUseStandardCommand)'!='true'">
<Exec Command="ThisIsMyCommand.exe"/>
</Target>
</Project>
The rare case targets file looks like this.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ThisIsMyRareTarget" AfterTargets="Build">
<Exec Command="ThisIsMyRareCommand.exe"/>
</Target>
<PropertyGroup>
<DontUseStandardCommand>true</DontUseStandardCommand>
</PropertyGroup>
</Project>
This is working fine in my setup, also independent of the order of the import targets files in the project file.

Overwrite targets post build event of another targets file
Actually, if you want to use multiple target files into nuget package and then switch between different target files depending on your needs, you cannot get what you want.
And as this document said, when you want to add a target file into a project by nuget, you should make sure the name of the target file is the same as the name of the nuget package so that it will work.
So you can only use one target file named <package_id>.targets and set a condidtion in it to distinguish which project environment to use.
Solution
1) please put these all in your <package_id>.targets file:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Flag></Flag>
</PropertyGroup>
<Target Name="ThisIsMyTarget" AfterTargets="Build" Condition="'$(Flag)'!='true'">
<Exec Command="ThisIsMyCommand.exe"/>
</Target>
<Target Name="ThisIsMyRareTarget" AfterTargets="Build" Condition="'$(Flag)'=='true'">
<Exec Command="ThisIsMyRareCommand.exe"/>
</Target>
</Project>
2) then you pack your nuget project and when you install your nuget in other projects(less than 1% of the projects), you should define the property Flag to true on the end of the xxx.csproj file like this:
And less than 1% projects can use ThisIsMyRareTarget.exe. In other 99% projects, you should not define Flag property in xxx.csproj and it will automatic capture ThisIsMyCommand.exe.

Related

Msbuild pack ignores nuget packages with targets and task

I have ProjectA and ProjectB
ProjectA
Is a dotnet standard project with output and exe file to be used as as tool.
This generates a nuget package on build, using property and property to mark the package as a tool.
It also is marked as to auto exclude from projects which installs the nuget package, when they them also generate a nuget package.
<Project Sdk="Microsoft.Net.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<AssemblyName>ProjectA</AssemblyName>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IsTool>true</IsTool>
<DevelopmentDependency>true</DevelopmentDependency>
</PropertyGroup>
<ItemGroup>
<None Include="build\ProjectA.props" Pack="True" PackagePath="build" />
<None Include="build\net461\ProjectA.targets" Pack="True" PackagePath="build" />
</ItemGroup>
<PropertyGroup>
</Project>
ProjectA.targets
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<GetPackageVersionDependsOn>MyCustomTask;$(GetPackageVersionDependsOn)</GetPackageVersionDependsOn>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup>
<Target Name="MyCustomTask">
<!-- Idealy i would use a custom task to set PackageVersion to something different. Like 5.0.99-alpha1+102435 -->
<PropertyGroup>
<PackageVersion>5.0.99-alpha1+102435</PackageVersion>
</PropertyGroup>
</Target>
</Project>
ProjectB
Installs a reference to the nuget package created by ProjectA.
<PropertyGroup>
<TargetFrameworks>net461</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ProjectA" Version="1.0.0" PrivateAssets="all" />
</ItemGroup>
When building ProjectB, the following files are genearted in obj/ folder:
.csproj.nuget.g.props
.csproj.nuget.g.targets
These files include import to ProjectA targets and props from the nuget package cache at:
%userprofile%.nuget\projecta\1.0.0\build\ProjectA.targets etc
Msbuild -t:pack ignores these imports when running, and hence the build ProjectB never sets the PackageVersion to 5.0.99-alpha1+102435 as i would expect.
Adding the above content of ProjectA.targets directly into ProjectB.csproj works.
Anyone have any suggestions what I'm missing or if the might be an issue?
If you use *.targets to pack the nuget project into a nuget package based on that file, you should make this file recognized along with csproj file.
Based on the description, it seems that you used that targets file to pack your nuget project into a version 5.0.99.
So you should make your csproj to work along with that file like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<!--the path of the *.targets file-->
<Import Project="xxx\xxx.targets"/>
</Project>
Update 1
clean nuget caches or delete all files under C:\Users\xxx\.nuget\packages) and msbuild -t:pack ProjectB.csproj to check it.
Update 1
Instead, modify Project B.csproj file:
use <TargetFramework>net461</TargetFramework> instead of <TargetFrameworks>net461</TargetFrameworks>.
The multi-platform framework seems to break the performance of the targets file, I'm also curious why this is happening. But this is the tip. You should use that.

Can I build multiple configurations of a project within one solution configuration?

I would like to build the same project twice in the same solution configuration, varying some #define flags to toggle features. Both binaries will be deployed with different names.
The solutions that I know could work:
Add a solution configuration - But I will then need to build the solution twice, which I would prefer to avoid. Both project configurations will always be built.
Copy the project - But then I have the overhead of maintaining a new project when I only want to maintain a different configuration.
Batch build - I avoid using batch build as I use both devenv for local development and msbuild for continuous integration.
Any other ideas or suggestions?
Just figured out a way to do what you asked for. Create one msbuild file (I named mine multiple.proj) and add the script below.
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Choose>
<When Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<ItemGroup>
<ProjectToBuild Include="$(MSBuildProjectName).csproj">
<Properties>Configuration=Release</Properties>
</ProjectToBuild>
</ItemGroup>
</When>
</Choose>
<Target Name="BeforeBuild">
<Message Text="Building configuration $(Configuration)..." />
</Target>
<Target Name="AfterBuild">
<MSBuild Projects="#(ProjectToBuild)"/>
</Target>
</Project>
</type>
</this>
Import the script on your projects (csproj or vbproj):
<Import Project="..\multiple.proj" />
This script tells msbuild to build again your project with another configuration as an AfterBuild event. I used Debug/Release to make the example, but you can easily change the script to support other configurations, or make the decision to build again based on other variables.
Be careful because you're running two builds at once, so build errors can be harder to understand.
Hope this helps.

msbuild not working properly with tfs 2010 and importet Microsoft.TeamFoundation.Build.targets

i'm stuck with extending my msbuild project file and tfs 2010. What i want to achieve is to automatically generate the documentation for my source code after the build. I searched the web and found out, that the file Microsoft.TeamFoundation.Build.targets defines a lot of customizable targets for either desktop- or team-build. One of them is the GenerateDocumentation-Target which i want to use. The Problem i have is, that despite i imported this file, the overloaded targets are not invoked by msbuild. The header of my vcxproj-File looks as follows:
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
after this i include the team build targets file with the statement
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets"/>
At the very end of this project file i tried to overload some targets as suggested in the TeamFoundation target file:
<Target Name="GenerateDocumentation">
<Message Text="GenerateDocumentation invoked" Importance="high"/>
</Target>
<Target Name="BeforeBuild">
<Message Text="BeforeBuild invoked" Importance="high" />
</Target>
<Target Name="AfterBuild">
<Message Text="AfterBuild invoked" Importance="high" />
</Target>
but except the AfterBuild-Target neither the GenerateDocumentation nor BeforeBuild target is called for a local build nor a build with the build server. Am i'm missing something? Is the DefaultTarget="Build" correct? I tried to change this to DefaultTarget="DesktopBuild" but then calling msbuild resulted in a variety of errors (MSB4018). In the project file the target file $(VCTargetsPath)\Microsoft.Cpp.targets is imported too. When removing this import, the GenerateDocumentation target is called, but not the other ones (including ResourceCompile which i need too). Can i use both of them somehow?
Thanks in advance...
what about adding this at the end of your build task...
<CallTarget Targets="GenerateDocumentation"></CallTarget>
</Target>

Run other than the DefaultTarget for a project configuration under Visual Studio 2010

I've a MSBuild target in my csproj to copy files and folders of my web application to a target path after build.
<Target Name="PublishToFileSystem" DependsOnTargets="PipelinePreDeployCopyAllFilesToOneFolder">
...
If I call MSBuild via command line with the target "PublishToFileSystem" everything works fine.
But now I want to "use" this target also for a special configuration in Visual Studio (like Release, Debug, ...).
How can I assign a configuration to another target than the DefaultTarget "Build" set in the project with DefaultTargets:
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
Thanks, Konrad
Try to use AfterBuild target instead of PublishToFileSystem:
<Target Name="AfterBuild" DependsOnTargets="PipelinePreDeployCopyAllFilesToOneFolder">
or check Overriding Predefined Targets on MSDN
If you want to do this for a specific solution configuration and you're suffering from ciruclar dependencies as I was, the easiest thing I could come up with is writing your own Target to use as default target. That target starts other targets based on a condition on the configuration.
<Target Name="CustomBuild">
<CallTarget Targets="SignAndroidPackage" Condition="'$(Configuration)' == 'UITest'"/>
<CallTarget Targets="Build" Condition="'$(Configuration)' != 'UITest'"/>
</Target>
And then simply change the Default target at the top of the project definition to that CustomBuild target.
<Project DefaultTargets="CustomBuild" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
Here's an approach that might suit your need: run a custom msbuild target from VisualStudio
(this is trick #78 in the book MSBuild Trickery)

Copy built assemblies (including PDB, .config and XML comment files) to folder post build

Is there a generic way I can get a post-build event to copy the built assembly, and any .config and any .xml comments files to a folder (usually solution relative) without having to write a post-build event on each project in a solution?
The goal is to have a folder that contains the last successful build of an entire solution.
It would be nice to use the same build solution over multiple solutions too, possibly enabling/ disabling certain projects (so don't copy unit tests etc).
Thanks,
Kieron
You can set common OutputPath to build all projects in Sln in one temp dir and copy required files to the latest build folder. In copy action you can set a filter to copy all dlls without "test" in its name.
msbuild.exe 1.sln /p:Configuration=Release;Platform=AnyCPU;OutputPath=..\latest-temp
There exists more complicated and more flexible solution. You can setup a hook for build process using CustomAfterMicrosoftCommonTargets. See this post for example.
Sample targets file can be like that:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);
PublishToLatest
</BuildDependsOn>
</PropertyGroup>
<Target Name="PreparePublishingToLatest">
<PropertyGroup>
<TargetAssembly>$(TargetPath)</TargetAssembly>
<TargetAssemblyPdb>$(TargetDir)$(TargetName).pdb</TargetAssemblyPdb>
<TargetAssemblyXml>$(TargetDir)$(TargetName).xml</TargetAssemblyXml>
<TargetAssemblyConfig>$(TargetDir)$(TargetName).config</TargetAssemblyConfig>
<TargetAssemblyManifest>$(TargetDir)$(TargetName).manifest</TargetAssemblyManifest>
<IsTestAssembly>$(TargetName.ToUpper().Contains("TEST"))</IsTestAssembly>
</PropertyGroup>
<ItemGroup>
<PublishToLatestFiles Include="$(TargetAssembly)" Condition="Exists('$(TargetAssembly)')" />
<PublishToLatestFiles Include="$(TargetAssemblyPdb)" Condition="Exists('$(TargetAssemblyPdb)')" />
<PublishToLatestFiles Include="$(TargetAssemblyXml)" Condition="Exists('$(TargetAssemblyXml)')" />
<PublishToLatestFiles Include="$(TargetAssemblyConfig)" Condition="Exists('$(TargetAssemblyConfig)')" />
<PublishToLatestFiles Include="$(TargetAssemblyManifest)" Condition="Exists('$(TargetAssemblyManifest)')" />
</ItemGroup>
</Target>
<Target Name="PublishToLatest"
Condition="Exists('$(LatestDir)') AND '$(IsTestAssembly)' == 'False' AND '#(PublishToLatestFiles)' != ''"
DependsOnTargets="PreparePublishingToLatest">
<Copy SourceFiles="#(PublishToLatestFiles)" DestinationFolder="$(LatestDir)" SkipUnchangedFiles="true" />
</Target>
</Project>
In that targets file you can specify any actions you want.
You can place it here "C:\Program Files\MSBuild\v4.0\Custom.After.Microsoft.Common.targets" or here "C:\Program Files\MSBuild\4.0\Microsoft.Common.targets\ImportAfter\PublishToLatest.targets".
And third variant is to add to every project you want to publish import of custom targets. See How to: Use the Same Target in Multiple Project Files

Resources