How to ignore the property value given on the command line from within the respective csproj file? - msbuild

Our TFS build controllers build all the projects from the same solution into the same shared bin directory, because the TFS build workflow passes the OutDir parameter to the msbuild command responsible for building the solution.
I have a project where I want to suppress this behavior and let it build into the standard relative bin\Debug or bin\Release directory.
But I cannot find how to do it. Indeed, observe the following trivial msbuild script:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutDir>$(MSBuildThisFileDirectory)bin\$(Configuration)</OutDir>
</PropertyGroup>
<Target Name="Build">
<Message Text="$(OutDir)" Importance="High"/>
</Target>
</Project>
Now, I am running it:
PS C:\> msbuild .\1.csproj /p:OutDir=XoXo /nologo
Build started 11/13/2015 9:50:57 PM.
Project "C:\1.csproj" on node 1 (default targets).
Build:
XoXo
Done Building Project "C:\1.csproj" (default targets).
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.03
PS C:\>
Notice it displays XoXo, ignoring my attempt to override it from within.
So, is it possible?

This is a bit of a classic RTFM situation but interesting nonetheless. See the documentation for MSBuild Properties in particular the part on Global Properties and how to make properties not being overridden by the former:
MSBuild lets you set properties on the command line by using the /property (or /p) switch. These global property values override property values that are set in the project file. This includes environment properties, but does not include reserved properties, which cannot be changed.
Global properties can also be set or modified for child projects in a multi-project build by using the Properties attribute of the MSBuild task
If you specify a property by using the TreatAsLocalProperty attribute
in a project tag, that global property value doesn't override the
property value that's set in the project file.
It also links to the Project element documentation which basically repeats the same info and says multiple properties in the attribute should be seperated by semi-colons.
In short, code applied to your case:
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
TreatAsLocalProperty="OutDir">
Note that this will completely disable altering OutDir from outside the project though. An alternate solution which is more configurable could be to have a small stub project which you make TFS build instead of the main project. In that project you can then decide on whether to pass OutDir to the actual project or override it, e.g. by fetching the value by importing a file which might or might not be defined, or based on an environment variable or so. This gives the basic idea:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Try import if this file exists, it should supply the value for CustomOutDir-->
<Import Project="$(MSBuildThisFileDirectory)customoutdir.props" Condition="Exists('$(MSBuildThisFileDirectory)customoutdir.props')"/>
<PropertyGroup>
<!-- Default value for CustomOutDir if not set elsewhere -->
<CustomOutDir Condition="'$(CustomOutDir)' == ''">$(MSBuildThisFileDirectory)bin\$(Configuration)</CustomOutDir>
<!-- ApplyCustomOutDir specifies whether or not to apply CustomOutDir -->
<ActualOutDir Condition="'$(ApplyCustomOutDir)' == 'True'">$(CustomOutDir)</ActualOutDir>
<ActualOutDir Condition="'$(ApplyCustomOutDir)' != 'True'">$(OutDir)</ActualOutDir>
</PropertyGroup>
<Target Name="Build">
<MSBuild Projects="$(MasterProject)" Properties="OutDir=$(ActualOutDir)"/>
</Target>
</Project>
And should be invoked by passing the neede properties like
msbuild stub.targets /p:MasterProject=/path/to/main.vcxproj;ApplyCustomOutDir=True
(I have never used TFS so the way to get the properties passed might be different)

Related

Is it possible to extract the value of element in csproj file with dotnet CLI?

csproj file contains project properties which I'd like to use in CI/CD pipeline.
Is it possible to extract those values with dotnet cli (or some other standard tool) without parsing xml via some standalone script?
For example having the project:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.0.0</Version>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</<Project>
I need to resolve the value of <Version>.
It's also wroth noting that project may contain conditional elements, and ideally it would be nice to resolve properties in the context of predefined variables.
MSBuild is very extensible. You could use a target to write the version number to a file:
<Target Name="WriteVersion" AfterTargets="Build">
<WriteLinesToFile Lines="$(Version)"
File="$(IntermediateOutputPath)version.txt" />
</Target>
That would write a version.txt file to a folder such as obj/Debug/net5.0 (depending on the Configuration and TargetFramework).
Also be sure to check out https://msbuildlog.com/ for how to investigate / debug builds.

How to ship the stylecop.json and custom.ruleset files with a NuGet package in VS2017

At the moment we are switching from VS2015 to VS2017. One of our upgrade steps is to switch from stylecop to the new Stylecop.Analyzer package. The new Stylecop is using 2 files. The stylecop.json and the Stylecop.ruleset.
The target: I want to provide the stylecop files as a custom nuget package. But I dont know how to create the needed .csproj entries.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
...
<CodeAnalysisRuleSet>packages\My.StyleCop.1.0.0-pre15\RuleSet\My.StyleCop.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
...
<ItemGroup>
<AdditionalFiles Include="packages\My.StyleCop.1.0.0-pre15\Config\stylecop.json">
<Link>stylecop.json</Link>
</AdditionalFiles>
</ItemGroup>
</Project>
In the past, there was the possibility to use a install.ps1 script to do this stuff. But with NuGet 3. (or 4.) the install scripts are obsolete and will be ignored.
I already tried to use My.StyleCop.targets:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<AdditionalFiles Include="packages\My.StyleCop.1.0.0-pre17\Config\stylecop.json">
<Link>stylecop.json</Link>
</AdditionalFiles>
</ItemGroup>
</Project>
But here I have some issues, too. Since NuGet 3. (or 4.) there is no solution wide package folder and I dont know any variable or placeholder I can use here to get a absolute or relative path to my package.
You can add .props or .targets files to the build folder in your packages and they will be imported to the projects.
On the .props file, you can use the MSBuildThisFileDirectory MSBuild variable that represents the folder where that file is located.
Thanks to Paulo.
How I did it:
This is the structure of my NuGet package.
The solution is quiet easy. You need to create to files. A .props and a .targets file named like the NuGet package and place them in the build folder of your package.
In these MSBuild files you can use the $(MSBuildThisFileDirectory) variable to get the path of your NuGet package.
MSBuildThisFileDirectory = C:\Users\UserName\.nuget\packages\sig.stylecop\1.0.0-pre23\build\
My SIG.StyleCop.props file:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)\..\RuleSet\SIG.combiLink.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>
My SIG.StyleCop.targets file:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)\..\Config\stylecop.json">
<Link>stylecop.json</Link>
</AdditionalFiles>
</ItemGroup>
</Project>
Cause of the structure of my package i need to navigate (..) into the Config and into the RuleSet folder.
The variable $(MSBuildThisFileDirectory) already includes the backslash at the end. It is important to omit the backslash when you reference the ruleset and the stylecop.json file:
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)..\RuleSet\SIG.combiLink.ruleset</CodeAnalysisRuleSet>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)..\Config\stylecop.json">
With the double backslash I experienced two strange problems in Visual Studio 2017:
Unit tests rebuild the code each time I start them, even without any code change
The IDE shows many StyleCop errors in the Error List window and shows red marks in the scroll bar even for rules that are explicitly disabled in the rule set.

Inexplicably cleared msbuild properties in TeamCity build

I'm trying to create a "tools NuGet package" that provides a tool and setting that is unpacked during build and used by a later TeamCity build step.
The NuGet package contains the following content in its build\MyPackageId.props file:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MyTool1>$(MSBuildThisFileDirectory)..\tools\MyTool.exe</MyTool1>
</PropertyGroup>
<Target Name="ReportMyToolToTeamCity" BeforeTargets="PrepareToRun">
<PropertyGroup>
<MyTool2>$(MSBuildThisFileDirectory)..\tools\MyTool.exe</MyTool2>
</PropertyGroup>
<Message Text="MyTool1 = $(MyTool1)" />
<Message Text="MyTool2 = $(MyTool2)" />
</Target>
</Project>
(The messages will eventually set a TeamCity property, but this is sufficient to demonstrate the issue.)
Because it's a props file, after installing the NuGet package into a C# project it has added an import as the very first thing, above the import of Microsoft.Common.props. I want a props file rather than a targets file so that the property values are also available to other project settings and targets files.
When I compile this inside Visual Studio 2015, I see both MyTool1 and MyTool2 paths set to the same (correct) path as expected.
When I compile this from TeamCity (2017.2.2, using the Visual Studio (sln) runner), according to the output the MyTool1 property is empty and only MyTool2 shows the correct value.
Why?

Directory.Build.Props and Msbuild /nr parameter

I have a quite huge solution and I need to add the /nr:false parameter to each build.I've found googling that starting from msbuild 3.5 it's possible to write a Directory.Build.Props and all the msbuild will realay to this one.
I've tried with this XMLbut doesn't seem to work
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBUILDDISABLENODEREUSE>1</MSBUILDDISABLENODEREUSE>
</PropertyGroup>
</Project>
What am I doing wrong?
Node reuse is a flag on msbuild itself, not in the projects. While environment variables are also treated as global properties in projects, some - like MSBUILDDISABLENODEREUSE - are interpreted by msbuild itself, before even processing projects. So setting a property with the same name inside a project file will have no effect.
In MSBuild 15.6 (in preview at the time of writing), it will support a similar concept with files named Directory.Build.rsp which can contain additional command line switches, so you can create a file with that name and /nr:false as content.

VCBuild task in MSBuild - change outputpath

I'm attempting to write an automated build for one of our products, and I've hit up against a wall for some of our VC++ projects: I need to be able to set the output path to where the assemblies will be copied once its done.
Here is a makeshift msbuild file:
<Project DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="3.5">
<Target Name="Build">
<VCBuild Projects="C:\src\SomeProject\SomeProject.vcproj"
ToolPath="C:\Program Files\Microsoft Visual Studio 9.0\VC\vcpackages"
Configuration="Debug" />
</Target>
</Project>
Stijn's Answer:
I thought I'd use this space to clarify how I personally used Stijn's answer to solve this. He has some code in his MSBuild file that writes the vsprops file for him. I decided to take a simpler approach and just write the file manually.
I created this file, called build.vsprops (my output path is V:)
<?xml version="1.0"?>
<VisualStudioPropertySheet ProjectType="Visual C++"
Version="8.00"
Name="Overrides"
OutputDirectory="V:\">
<Tool Name="VCCLCompilerTool"
AdditionalUsingDirectories="V:\" />
</VisualStudioPropertySheet>
Then I edited my MSBuild file to add the Override parameter:
<Project DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="3.5">
<Target Name="Build">
<VCBuild Projects="C:\src\SomeProject\SomeProject.vcproj"
ToolPath="C:\Program Files\Microsoft Visual Studio 9.0\VC\vcpackages"
Configuration="Debug"
Override="$(MSBuildProjectDirectory)\build.vsprops" />
</Target>
</Project>
have a look at the Override parameter for the VCBuild task. Basically you specify a property sheet which you can use to override whatever property you want (it has the same effect as adding a property sheet to the top of the list in a project within VS). You could even generate the override file using the WriteLinesToFile task.
Example:
<PropertyGroup>
<VCOverridesFile Condition=" '$(VCOverridesFile)'=='' ">overrides.vsprops</VCOverridesFile>
<VCOverridesOpen>%3C?xml version=%221.0%22?%3E%0D%0A%3CVisualStudioPropertySheet ProjectType=%22Visual C++%22 Version=%228.00%22 Name=%22My Overrides%22%3E</VCOverridesOpen>
<VCOverridesClose>%3C/VisualStudioPropertySheet%3E</VCOverridesClose>
<MyOutPath><Tool Name="VCLinkerTool" OutputFile ="c:\my.exe"/></MyOutPath>
</PropertyGroup>
<Target Name="WriteOverridesFile">
<WriteLinesToFile
File="$(VCOverridesFile)"
Lines="$(VCOverridesOpen);$(AdditionalVCOverrides);$(VCOverridesClose)"
Overwrite="true" />
</Target>
Then pass $(VCOverridesFile) to the Override property and make sure your VCBuild Task DependsOnTarget WriteOverridesFile.
Doing it the dirty way you can pass output directory path through command line arguments of msbuild.
msbuild yourProject /p:OutDir=yourPath
Although I suspect, there should be the better way to accomplish the task. The main idea is to set 'OutDir' property in such a way that it will not be overriden by your SomeProject.vcproj
if you are using Azure DevOps and needs to create a YAML do build a .net framework (vintage[old])
- task: VSBuild#1
inputs:
solution: '**\*.sln'
msbuildArgs: '/p:DeployOnBuild=true /p:SkipInvalidConfigurations=false /p:OutDir="$(System.DefaultWorkingDirectory)\publish_output"'
platform: 'Any CPU'
configuration: 'Release'