How to always execute a target in MSBuild - msbuild

I have an MSBuild file that manipulates the AssemblyInfo file before the application is compiled. At the end of the build, it restores the AssemblyInfo file. It does this by backing up the file, manipulating it, and then after build time, restoring the file.
This works fairly well except when an error occurs during the build. It then does not restore the original file. Is there a way I can tell MSBuild to execute a target at the end of a build no matter if it succeeded or failed?

Based on your last comment to the original question I would take another approach, and forget the approach you are currently taking. You should know that your version info doesn't have to be in the AssemblyInfo.cs file. It can be in any code file, just as long as you only have attributes AssemblyVersion and AssemblyFileVersion defined once each. With that being said what I would do is follow these steps:
Remove AssemblyVersion & AssemblyFileVersion from AssemblyInfo.cs
Create a new file, name it whatever you want want in my case I put it at Properties\VersionInfo.cs. Do not add this file to the project.
Edit the project file to include that file into the list of file to be compiled only when you want it
Let's expand a bit on #3. When you build a .NET project, the project itself is an MSBuild file. Inside that file you will find an item declared Compile. This is the list of files that will be sent to the compiler to be compiled. You can dynamically include/exclude files from that list. In you case you want to include the VersionInfo.cs file only if you are building on the build server (or whatever other condition you define). For this example I defined that condition to be if the project was building in Release mode. So for Release mode VersionInfo.cs would be sent to the compiler, and for other builds not. Here are the contents of VersionInfo.cs
VersionInfo.cs
[assembly: System.Reflection.AssemblyVersion("1.2.3.4")]
[assembly: System.Reflection.AssemblyFileVersion("1.2.3.4")]
In order to hook this into the build process you have to edit the project file. In that file you will find an element (maybe more than 1 depending on project type). You should add a target similar to the following there.
<Target Name="BeforeCompile">
<ItemGroup Condition=" '$(Configuration)'=='Release' ">
<Compile Include="Properties\VersionInfo.cs" />
</ItemGroup>
</Target>
Here what I've done here is to define a target, BeforeCompile, which is a well-known target that you can override. See this MSDN article about other similar targets. Basically this is a target which will always be called before the compiler is invoked. In this target I add the VersionInfo.cs to the Compile item only if the Configuration property is set to release. You could define that property to be whatever you wanted. For instance if you have TFS as your build server then it could be,
<Target Name="BeforeCompile">
<ItemGroup Condition=" '$(TeamFoundationServerUrl)'!='' ">
<Compile Include="Properties\VersionInfo.cs" />
</ItemGroup>
</Target>
Because we know that TeamFoundationServerUrl is only defined when building through TFS.
If you are building form the command line then something like this
<Target Name="BeforeCompile">
<ItemGroup Condition=" '$(IncludeVersionInfo)'=='true' ">
<Compile Include="Properties\VersionInfo.cs" />
</ItemGroup>
</Target>
And when you build the project just do msbuild.exe YourProject.proj /p:IncludeVersion=true. Note: this will not work when building a solution.

What about changing the problem:
Add a "template" AssemblyInfo.cs.template to version control that represents your "ideal" AssemblyInfo.cs with regex hooks in there
Before build, copy the template to the real and apply your regexes
Add some kind of subversion ignore for AssemblyInfo.cs (I'm no svn expert, but I'm pretty sure there is a way you can tell it to ignore certain files)
In the event that your devs need to add some kind of customization that would normally appear in an AssemblyInfo.cs (eg InternalsVisibleTo), then get them to add it to a different .cs file that IS checked in.
As a further refinement, combine Sayed's solution with mine and remove version info stuff from the actual AssemblyInfo.cs and have a VersionInfo.cs.template that is checked in, that creates a VersionInfo.cs in BeforeBuild.

I never used it, but from the documentation it seems that the OnError Element is useful to what you're trying to achieve.
Causes one or more targets to execute,
if the ContinueOnError attribute is
false for a failed task.

Related

Use $(SolutionName) in the MsBuild commandline parameters

In order to emulate the "PerProject" option in TFS 2013's XAML build in the new Build 2015 task based builds, I'd like to be able to pass the SolutionName to the msbuild commandline arguments without having to manually set it every time.
I'd like to do something like:
/p:OutputPath=$(Build.BinariesDirectory)\$(SolutionName)\
Where I'd like MsBuild to infer the $(SolutionName) parameter. But when passing this on the commandline, the new task runner will substitute the $(Build.BinariesDirectory) with the correct target path and leaves $(SolutionName) alone. Unfortunately MsBuild subsequently also leaves the property alone:
Copying file from "obj\Debug\TFSBuild.exe" to "bin\debug\$(SolutionName)\TFSBuild.exe".
TFSBuild -> b\$(SolutionName)\TFSBuild.exe
Copying file from "obj\Debug\TFSBuild.pdb" to "b\$(SolutionName)\TFSBuild.pdb".
I can't remember a way to pass a property to the commandline and have it do late-expansion... Any tips?
For those looking to emulate SingleFolder or AsConfigured, those are easy:
SingleFolder -> /p:OutputPath="$(Build.BinariesDirectory)"
Asconfigured -> don't pass OutputPath
PerProject -> /p:OutputPath="$(Build.BinariesDirectory)\HARDCODESOLUTIONNAME"
As I feared there doesn't seem to be a simple way to override a property from the commandline and "inject" the value of another property into it during the evaluation stage.
There are a few ways to get around it, but they're not ideal and certainly not universal for each language supported by MsBuild. A pity.
I've debugged the MsBuild targets files and found a solution to reproduce the old behaviour from the 2005/2008 era. Not entirely per solution, but it does redirect projects into a subfolder.
/p:GenerateProjectSpecificOutputFolder=true /p:OutDirWasSpecified=true
/p:OutputPath=$(Build.BinariesDirectory)
Normally, $(SolutionName) is defined when executing solution-level MSBuild pipelines, such as running dotnet restore in the root solution directory.
To make $(SolutionName) available for project-level MSBuild pipelines, add a Directory.Build.props file in the root of your solution with the following contents:
<Project>
<PropertyGroup>
<SolutionName Condition="'$(SolutionName)' == ''">
$([System.IO.Path]::GetFileNameWithoutExtension($([System.IO.Directory]::GetFiles("$(MSBuildThisFileDirectory)", "*.sln")[0])))
</SolutionName>
</PropertyGroup>
</Project>
Now $(SolutionName) will be defined even when executing project-level MSBuild pipelines.
This answer works best when there is exactly one solution file in the root of the solution directory. You'll need to massage the above a bit for other project structures.
Of course, you can also be lazy and specify the solution name directly, but this opens up the possibility of refactoring issues (need to remember to update this file if the solution name changes).
<Project>
<PropertyGroup>
<SolutionName Condition="'$(SolutionName)' == ''">
MySolutionName
</SolutionName>
</PropertyGroup>
</Project>
One solution is to mimic such 'late evaluation' yourself by altering OutputPath withing the projectfile. To do without manually changing each single project file you can use the CustomBeforeMicrosoftCSharpTargets extension point. Which is an fancy way of saying it is just a property which when found and pointing to an existing file, will lead that file to be imported somewhere before all the actual build logic. Here's the idea: create a file like paths.targets somewhere - either include it in source control or you can generate it on the fly as part of the build process. Contents:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutputPath Condition="'$(OutputPathBaseDir)'!=''">$(OutputPathBaseDir)\$(SolutionName)</OutputPath>
</PropertyGroup>
</Project>
So this just overrides OutputPath to some base dir + solutionname. Then if you build the solution like
msbuild my.sln /p:CustomBeforeMicrosoftCSharpTargets=paths.targets;
OutputPathBaseDir=$(Build.BinariesDirectory)
each project will import the paths.targets file and set output property to valueOfBinariesDirectory\my which I think is exactly what you are after.
You are right that TFS vNext build can't recognize $(SolutionName) in OutputPath, as $(SolutionName) doesn't list in the Predefined variables.
As an alternative, we may name the build definition with the solution name, then specify the MSBuild argument to: /p:OutputPath="$(Build.BinariesDirectory)\$(Build.DefinitionName)"in this way, we can get the output under the solution name.

MSBuild - Project-specific targets for solution does not work

I have a solution that has multiple projects in it, including a web application. I want MSBuild to execute "WebPublish" target against the web application project and "default target" for all other projects in the solution.
This MSDN article says that I can do it specifying the command line
msbuild SlnFolders.sln /t:NotInSlnfolder:Rebuild;NewFolder\InSolutionFolder:Clean
But I never could make it work - MSBuild return an error, something like "NotInSlnFolder:Rebuild" target does not exist. It does not matter what target to specify, Build, Rebuild or Clean - it does not work in any case.
How can I achieve my goal of specifying project-specific targets for a solution?
The MSDN documentation does not work. Or have I missed something?
NOTE: This workaround is not officially supported by Microsoft, so there is no guarantee that it will work forever.
Short Answer
In folder with the SLN file, create the file before.{YourSolution}.sln.targets, with the following content: (Replace what in curly brackets to whatever you need.)
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="{MyCompany_MyProduct_WebApp:WebPublish}">
<MSBuild
Condition="'%(ProjectReference.Identity)' == '{$(SolutionDir)MyCompany.MyProduct.WebApp\MyCompany.MyProduct.WebApp.csproj}'"
Projects="#(ProjectReference)"
Targets="{WebPublish}"
BuildInParallel="True"
ToolsVersion="4.0"
Properties="BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)"
SkipNonexistentProjects="%(ProjectReference.SkipNonexistentProjects)" />
</Target>
</Project>
After that you can execute the command line:
msbuild {YourSolution}.sln /t:{MyCompany_MyProduct_WebApp:WebPublish}
Long Answer
If you add environment variable MSBUILDEMITSOLUTION, setting its value to 1, MSBuild will not delete temporary files generated for the solution and projects.
This will allow you to find {YourSolution}.sln.metaproj and {YourSolution}.sln.metaproj.tmp files generated in the solution folder, which are just standard MSBuild project files.
For MSBuild 3.5, the generated file is {YourSolution}.sln.cache and is retained regardless of environment variables. Analyzing those files, you will understand low-level details of the process and to see the customization opportunities available.
After executing MSBuild with some project-specific target in the .Metaproj file you will find out that the list of project-specific targets is hardcoded and only standard targets are supported (Build, Rebuild, Clean, Compile, Publish; note: Publish and WebPublish are not the same). MSBuild 3.5 only generates Clean, Rebuild and Publish targets as well as a target with just the project's name that means "Build".
You also can see that NotInSlnfolder:Rebuild is just a name of an autogenerated target. In reality MSBuild does not parse it and does not care about project names and location. Also note that the autogenerated target names specify the project name with solution folders hierarchy if it's in one, e.g. SolFolder\SolSubfolder\ProjectName:Publish.
One more critically important thing you will find: The MSBuild Target Name does not support dots. All dots in project names are replaced with underscores. For example, for a project named MyCompany.MyProduct.Components you will have to specify in the command line:
/t:MyCompany_MyProduct_Components:Rebuild
That's why even standard project-specific target Build didn't work - my project name contained dots.
Analyzing file {YourSolution}.sln.metaproj.tmp, you will find out that at runtime it tries to import targets from file named before.{YourSolution}.sln.targets and after.{YourSolution}.sln.targets, if those files exist. This has a key to the workaround for this MSBuild limitation/bug.
You can open your solution file in text editor and check whether following line is exist or not if not then you can add
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> inside the <Project> tag.
Hope this help you.

VS and imported msbuild project files

I have an import statement in one of my msbuild project files:
<Import Project="$(ProjectDir)..\CommonProjectProperties\OurCommon.targets" Condition="Exists('$(ProjectDir)..\CommonProjectProperties\OurCommon.targets" />
The imported msbuild project has the following propertygroup:
<PropertyGroup Condition="'$(Configuration)' == 'Debug' And '$(SolutionName)' == 'Ourwork' And Exists('$(ProjectDir)..\..\FXCopSettings\OurGlobal.ruleset')">
<CodeAnalysisRuleSet>$(ProjectDir)..\..\FXCopSettings\OurGlobal.ruleset</CodeAnalysisRuleSet>
<RunCodeAnalysis>true</RunCodeAnalysis>
<CodeAnalysisIgnoreGeneratedCode>True</CodeAnalysisIgnoreGeneratedCode>
<DefineConstants>$(DefineConstants);CODE_ANALYSIS</DefineConstants>
</PropertyGroup>
And the following item group:
<When Condition="Exists('$(ProjectDir)\..\..\Solutions\FXCopSettings\GlobalSuppressions.cs') And Exists('$(ProjectDir)$(ProjectName).csproj')">
<ItemGroup>
<Compile Include="$(ProjectDir)\..\..\Solutions\FXCopSettings\GlobalSuppressions.cs" />
</ItemGroup>
</When>
Basically what I am trying to do is to have the above import statement in all my projects files, and then manipulate whether FXCop is run by tweaking just the imported project file.
Problem is that from the command line, all this works fine (with the exception of the constant CODE_ANALYSIS which i have to set using something like msbuild /t:build proj.sln /p:DefineConstants="CODE_ANALYSIS=true". That's fine.
From the VS 2010 IDE its just... hell! It "looks" like VS is respecting all the stuff in the imported msbuild project file.... EXCEPT
1) the included file is greyed out, meaning that is is shown in the IDE only if you have "show all files on". This means that it not compiled. I can copy the exact same ItemGroup into the parent msbuild project file, and the file shows up, un-greyed out, in solution explorer, meaning it is compiled.
2) The suppression file is used to tell FXCop which objects to ignore when it does a code analysis. When I get the suppression file correctly compiled as part of the project, FXCop seems to ignore it. Reading up on this, apparently the CODE_ANALYSIS constant needs to be turned on. Which I thought i did from the imported project file, but it is not being respected. Even though the project properties window in the VS IDE show it is as a defined constant. Again, when i move the DefinedConstansts declaration into one of the parent project files, the IDE respects the constant.
So... I am not sure whether i need to separate this into two posts, but here are my questions:
1) how do i get the IDE to respect and compile files listed in an imported project file?
2) how do i get the IDE to respect DefineConstants in an imported project file

How make MSBuild build custom target specified in csproj building sln?

I am facing an issue with MSBuild I can't overcome it by myself. As a result I rely on community's wisdom.
The real situation I'm having troubles with
I have a soluiton file containing several projects with dependencies to other projects in same solution. I'd like to append a custom target to one of the project's csproj file and build it from the command line. It will allow me to make all the necessary output binaries for this project for further processing during the building of the custom target. But the main thing is that I can't figure out how to do it, googling doesn't help either.
Simplification
To make thing simplier I decided to make a new C# console project, add a simple custom target to the project's file and try to make it build. Still no success! Here what I've done so far:
Created a solution app with a default console project coreapp. This gaves me at least two files:
app.sln
coreapp\coreapp.csproj
Modified coreapp.csproj with addition of my custom target inside of the Project tag
<Target Name="SampleTarget">
<Message Text="This is a SampleTarget" />
</Target>
Run on the command line the following command
%windir%\Microsoft.NET\framework\v3.5\msbuild.exe app.sln /t:coreapp:SampleTarget
or even
%windir%\Microsoft.NET\framework\v3.5\msbuild.exe app.sln /t:coreapp.csproj:SampleTarget
Results
No luck, facing the error
MSB4057: The target "coreapp.csproj:SampleTarget" does not exist in the project.
I suspect that MSBuild thinks somehting fundamentally different from what I want it to think...
BEsides that, I also tried to set on the same command line the environment variable MSBuildEmitSolution=1 to force msbuild dump a temporary solution file it creates while processing the solution. In this file, indeed, no such target. However I guess it isn't the reason because I asked msbuild to build coreapp.proj where target SampleTarget really resides.
The question is how to build SampleTarget in this simplified scenario using solution file since potencially it can contain dependencies for the project containing this SampleTarget target?
I'd be greatful for any sort of help or firection for further investigation!
Instead of inserting a custom target in your project file, you could try creating a new standalone msbuild file, which would:
build the solution file (which builds projects)
defines your extra target
Call it app-custom-Debug.msbuild , for example.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WorkingFolder>$(MSBuildProjectDirectory)</WorkingFolder>
<Configuration>Debug</Configuration>
<SolutionFile>app.sln</SolutionFile>
</PropertyGroup>
<Target Name="Build" DependsOnTargets="Compile" />
<Target Name="Compile">
<Message Text="=== COMPILING $(Configuration) configuration ===" />
<MSBuild Projects="$(SolutionFile)"
Properties="Configuration=$(Configuration)" />
</Target>
<Target Name="SampleTarget">
<Message Text="This is a SampleTarget" />
</Target>
</Project>
Then you execute:
msbuild.exe app-custom-Debug.msbuild /t:SampleTarget
One option is to tie your SampleTarget to the standard Build targets via overriding the appropriate DependsOn property. In this case you could tell BeforeBuild that it DependsOn SampleTarget or you do the same thing with AfterBuild. This will ensure that MSBuild processes your target prior to the standard target indicated.

Preventing MSBuild from building a project in a .sln without using Solution Configurations

I want to inhibit the building of certain projects within a solution from building (within a TeamCity Build Configuration in order to optimize the speed of my Commit Build feedback if you must know).
I'm aware of the Solution Configurations mechanism but don't want to have to force lots of .sln files to end up with every permutation of things I want to be able to switch off. I have Convention based rule where I want to say "If I'm doing the Commit Build, I dont want to do the final installer packaging". (And I don't want to break it out into a separate solution).
I'd prefer not to use a solution involving find and replace in the .sln file or in a .proj file created via [MsBuildEmitSolution][1]. I'm aware of questions here which cover the out of the box solution and this slightly related question.
I see MSBuild /v:diag is saying:
2>Target "Build" in file "Z.sln.metaproj" from project "Z.sln" (entry point):
Using "MSBuild" task from assembly "Microsoft.Build.Tasks.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
Task "MSBuild"
Global Properties:
BuildingSolutionFile=true
CurrentSolutionConfigurationContents=<SolutionConfiguration>
<ProjectConfiguration Project="{C83D035D-169B-4023-9BEE-1790C9FE22AB}" AbsolutePath="X.csproj" BuildProjectInSolution="True">Debug|AnyCPU</ProjectConfiguration>
<ProjectConfiguration Project="{15E7887D-F1DB-4D85-8454-E4EF5CBDE6D5}" AbsolutePath="Y.csproj" BuildProjectInSolution="True">Debug|AnyCPU</ProjectConfiguration>
</SolutionConfiguration>
So the question is:
Is there a neat way of me getting to do an XPath replace or similar to have the effect of changing BuildProjectInSolution="True" to BuildProjectInSolution="False" for Project Y above
Failing that, is there a relatively simple edit I can do within a .ccproj (An Azure 1.4 Package) or a .csproj (a general project) file to cause the effects (including triggering of dependent projects) of the project being enabled within a commandline msbuild Z.sln solution build to be nullified?
Not sure it qualifies as neat, but you can set CustomAfterMicrosoftCommonTargets to import an msbuild file to over-ride the BuildDependsOn property, pointing it to your own custom build task. Basically, by setting CustomAfterMicrosoftCommonTargets you get msbuild to import an msbuild file containing the following:
<PropertyGroup>
<OldBuildDependsOn>$(BuildDependsOn)</OldBuildDependsOn>
<BuildDependsOn>MyBuild</BuildDependsOn>
</PropertyGroup>
<Target Name="OldBuild" DependsOnTargets="$(OldBuildDependsOn)" />
<Target Name="MyBuild">
<CallTarget Targets="OldBuild" Condition="<IfIWantThis>" />
</Target>
Edit
You can use the following MyBuild target to Include/Exclude projects based on regular expressions passed in as IncludeInBuild and ExcludeFromBuild properties. (If you want complex regexes, you may fall foul of MSBuild special character escaping, but this works well enough for simple matching)
> msbuild /p:ExcludeFromBuild="Tests|Install|Azure"
<Target Name="MyBuild">
<CallTarget Targets="OldBuild" Condition="('$(IncludeInBuild)'=='' OR
'$([System.Text.RegularExpressions.Regex]::IsMatch($(MSBuildProjectFullPath),
$(IncludeInBuild),
System.Text.RegularExpressions.RegexOptions.IgnoreCase))'=='True') AND
('$(ExcludeFromBuild)'=='' OR
'$([System.Text.RegularExpressions.Regex]::IsMatch($(MSBuildProjectFullPath),
$(ExcludeFromBuild),
System.Text.RegularExpressions.RegexOptions.IgnoreCase))'=='False')" />
</Target>
You could always pass the particular projects you want to build as parameters to the MSBuild.
The MSBuild command line would look like this:
MSBuild /t:<Project Name>:Rebuild;<Another Project Name>:Rebuild
In TeamCity, you would put <Project Name>:<Target Action> in the target field in the MSBuild runner.
I add a system parameter under Parameters
Name: system.ExcludeFromBuild
Kind: System property (system.)
Value: path to your csproj