MSBuild extract properties from one project to another - msbuild

Say I have two project files "Parent.proj" and "Child.proj". If I declare a property in Parent.proj called MyProp I can pass this to Child.proj with the following code:
<MSBuild Projects="Child.proj" Targets="dostuff" Properties="MyProp=MyValue" />
This is fine, but I want to know if there is a way of referencing MyProp within Child.proj without Child.proj being called by Parent.proj.
I know I can declare the same property in Child.proj and this will get overriden when Child.proj is called by Parent.proj but I want to avoid repeating a property value.

If you define your properties in an external project file then each of the projects can import the property settings.
Here's a very simple properties files called orders.properties which I am currently working on.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- always include the root properties -->
<Import Project="$(root)\root.properties.proj"/>
<PropertyGroup>
<!-- Version numbers/names for this branch -->
<orders_ver_major>99</orders_ver_major>
<orders_ver_minor>0</orders_ver_minor>
<orders_ver_release>0</orders_ver_release>
<orders_ver>$(orders_ver_major).$(orders_ver_minor).$(orders_ver_release)</orders_ver>
<orders_ver_db>$(orders_ver_major)_$(orders_ver_minor)_$(orders_ver_release)</orders_ver_db>
<!-- setup folders specific to the orders project -->
<orders_database>$(orders_root)\btq.orders.database</orders_database>
<!--
Setup order database default properties, can be overriden if passed in when called from
the command line or from other build scripts.
-->
<orders_force_create Condition="'$(orders_force_create)' == ''">false</orders_force_create>
<orders_db_server Condition="'$(orders_db_server)' == ''" >.\sqlexpress</orders_db_server>
<orders_db_username Condition="'$(orders_db_username)' == ''" >yyyyyyyy</orders_db_username>
<orders_db_password Condition="'$(orders_db_password)' == ''" >xxxxxx</orders_db_password>
<orders_db_name Condition="'$(orders_db_name)' == ''" >$(COMPUTERNAME)_btq_orders_v$(orders_ver_db)</orders_db_name>
</PropertyGroup>
</Project>
In my main build project I import the order properties in the orders.build.proj file and any subprojects that require it.
Here is the initial section of the main build file.
<Project DefaultTargets="build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--
Always setup the path to the root and also the orders root folder.
We then include the orders properties, which includes the root properties
For this project the orders folder is in the same folder as this build file
so can just reference the ms build project directory property as the orders_root.
-->
<PropertyGroup>
<root>$(MSBuildProjectDirectory)\..\..</root>
<orders_root>$(MSBuildProjectDirectory)</orders_root>
</PropertyGroup>
<!--
Once we have the roots configured we can now include all the standard properties,
this also includes the root.properties also.
-->
<Import Project="$(orders_root)\orders.properties.proj"/>
Hope this answers your question.
Kind Regards
Noel

Related

msbuild Directory.build.props cascade per project?

Executive Summary: I want to set properties in property groups based on conditions that are present only late in the build pipeline and am looking for a way to solve this earlier.
I have a fairly simple Directory.build.props file
<Project>
<PropertyGroup>
<MyMode>Default</MyMode>
</PropertyGroup>
<!-- This one overrides the default group above -->
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<MyMode>Changed to Debug</MyMode>
</PropertyGroup>
<!-- This one is not applied -->
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v4.7.2' ">
<MyMode>Framework</MyMode>
</PropertyGroup>
<Target Name="Stats" AfterTargets="Build">
<Message Importance="High" Text="::::: Mode set to $(MyMode)" />
<Message Importance="High" Text="::::: Target Framework set to $(TargetFrameworkVersion)" />
</Target>
</Project>
And a simple project structure
E:.
│ Directory.build.props
│ MSBuild_Test.sln
│
├───ConsoleAppNet
│ App.config
│ ConsoleAppNet.csproj
│ Program.cs
│
└───MSBuild_Test
Class1.cs
LibStandard.csproj
LibStandard is a .net standard library, ConsoleAppNet is a .net framework project which also has a build dependency to LibStandard
When I execute the msbuild script above I get this output
LibStandard -> E:\temp\MSBuild_Test\MSBuild_Test\bin\Debug\netstandard2.0\LibStandard.dll
::::: Mode set to Changed to Debug
::::: Target Framework set to v2.0
ConsoleAppNet -> E:\temp\MSBuild_Test\ConsoleAppNet\bin\Debug\ConsoleAppNet.exe
::::: Mode set to Changed to Debug
::::: Target Framework set to v4.7.2
As you can see, the console output should have triggered the property group with the condition resulting in MyMode being Framework, but it did not work out. This one was never matched:
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v4.7.2' ">
<MyMode>Framework</MyMode>
</PropertyGroup>
Is there a good way to apply PropertyGroups during load based on the condition above?
I am aware that I can place PropertyGroup overrides in a Target, e.g.:
<Target Name="TooLate" BeforeTargets="BeforeBuild" Condition=" '$(TargetFrameworkVersion' == 'v4.7.2' ">
<PropertyGroup >
<MyMode>Framework</MyMode>
</PropertyGroup>
</Target>
and it also gets executed correctly but at this point in time I cannot set important other variables.
My intention is to redirect output directories based on different conditions. When I set $(OutputPath) in a target, it is already too late. The project ignores this output for the entire build of this project:
<Target Name="TooLate" BeforeTargets="BeforeBuild" Condition=" '$(TargetFrameworkVersion)' == 'v4.7.2' ">
<PropertyGroup >
<OutputPath>New_Output_Directory</OutputPath>
</PropertyGroup>
</Target>
I can even echo the OutputPath variable and it points to the correct value but the build uses the old value and not redirecting the output.
High five me, I found the solution for all the coming up Samuels asking about the same issue.
Quick answer
At the time of import of the Directory.build.props no other properties (e.g TargetFramework) are already imported and will default to empty. This is why the checks on them fail. Use Directory.build.targets instead!
Directory.build.props imported very early, allowing you to set properties at the beginning
Directory.build.targets imported very late, allowing you to customize the build chain
Resources
Here are some very useful pages regarding msbuild
Explanation of available targets
How to customize your build
Explanation
Here is a quote from the paragraph on the customization page (so long the current documents are alive ...)
Import order
Directory.Build.props is imported very early in
Microsoft.Common.props, and properties defined later are unavailable
to it. So, avoid referring to properties that are not yet defined (and
will evaluate to empty).
Directory.Build.targets is imported from Microsoft.Common.targets
after importing .targets files from NuGet packages. So, it can
override properties and targets defined in most of the build logic,
but sometimes you may need to customize the project file after the
final import.
By reading this the implication is somewhat fuzzy about the targets but Directory.Build.targets is the best place to override properties and use conditional checks.

MSBuild, OutputPath to a lib directory is not honoured

I spent hours now but I simply don't get it:
Why is a lib sub directory not honoured by the VS "fast up-to-date check"?
If a lib output dir for libraries is set, the solution is always rebuild - if changes have been made or not does not matter. If \lib sub dir is removed it works. Why?
Here is what I tested so far:
Refer to the next code snippet. That one works perfectly. If several dependent project are asked to build multiple times they actually build only once if no changes have been made. The Visual Studio FastUpToDateCheck kicks in.
But if you change the line
<OutputPath>$(SolutionDir)bin\$(Configuration)\$(Platform)</OutputPath>
to
<OutputPath>$(SolutionDir)bin\$(Configuration)\$(Platform)\lib\</OutputPath>
it constantly rebuilds. Any ideas why?
ComponentBuild.props located next to .sln file
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<IntermediateOutputPath>$(SolutionDir)obj\$(Configuration)\$(MSBuildProjectName)\</IntermediateOutputPath>
<UseCommonOutputDirectory>False</UseCommonOutputDirectory>
<DisableFastUpToDateCheck>false</DisableFastUpToDateCheck>
</PropertyGroup>
<PropertyGroup Condition=" '$(OutputType)' == 'Library' ">
<!-- To distinguish by \lib\ does not work, a rebuild is triggered since the up-to-date check fails -->
<!-- <OutputPath>$(SolutionDir)bin\$(Configuration)\$(Platform)\lib\</OutputPath> -->
<OutputPath>$(SolutionDir)bin\$(Configuration)\$(Platform)</OutputPath>
<OutDir>$(OutputPath)</OutDir>
</PropertyGroup>
<PropertyGroup Condition=" '$(OutputType)' == 'Exe' ">
<OutputPath>$(SolutionDir)bin\$(Configuration)\$(Platform)\</OutputPath>
<OutDir>$(OutputPath)</OutDir>
</PropertyGroup>
</Project>
The file is included in csproj files just before Import Microsoft.CSharp.targets:
.csproj file:
<!-- position of include is important, OutputType of project must be defined already -->
<Import Project="$(SolutionDir)ComponentBuild.props" Condition="Exists('$(SolutionDir)ComponentBuild.props')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
The behaviour becomes more weird, the more I test.
I created two simple library projects A and B. B depends on A. I added above mentioned import and the FastUpToDateCheck works.
After adding lib path to the library outputtype, it works when nothing else is changed. But when lib B project is cleaned, every subsequent builds do rebuild project B.
When adding lib path to the exe outputtype as well. The FastUpToDateCheck works again.
Then I removed the lib path again from output type exe, but the FastUpToDateCheck surprisingly still works - always. Even when cleaning the build, changing a class or deleting all obj and bin folders.
BUT as soon as I removed the lib path from the lib outputtype as well, i.e. I set back all to the original state, it FAILS. It rebuilds every time. The first line of the diagnostic output is
Project 'ClassLibrary1' is not up to date. Missing output file
'c:\Users\hg348\Documents\Visual Studio
2015\Projects\BuildTest\bin\Debug\AnyCPU\lib\ClassLibrary1.dll'
It still looks into lib path even though it isn't set any more.
I think there is some nasty caching involved.
Can someone please verify this?
Well, my tests as described above lead to the answer:
It is caching in Visual Studio (VS) which triggers the builds after changing the output path. After making changes to the outputpath and probably outdir as well, Visual Studio still looks in the old directory for its FastUpToDateCheck.
Closing and reopening the Solution helps already to clear the VS cache. In some cases it is necessary to delete the hidden file .suo in hidden folder .vs
This solves all problems stated in the sample file given in the question above.
My final import file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Note that VS caches settings, to be sure the FastUpToDateCheck works
* reopen the solution after
- changing properties
- after adding a platform config
- after adding references to projects
* close VS and remove the hidden file
<solution folder>\.vs\<solution name>\v14\.suo after changing IntermediateOutputPath,
(You have to enable "how hidden files" in windows file explorer)
* After updating App.config do a random change in any .cs source file too,
otherwise FastUpToDateCheck fails
-->
<PropertyGroup>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<IntermediateOutputPath>$(SolutionDir)obj\$(Configuration)\$(Platform)\$(MSBuildProjectName)\</IntermediateOutputPath>
<!-- if true, don't copy output files of referenced assemblies, since everything builds to the same folder. -->
<UseCommonOutputDirectory>true</UseCommonOutputDirectory>
<DisableFastUpToDateCheck>false</DisableFastUpToDateCheck>
</PropertyGroup>
<PropertyGroup Condition=" '$(OutputType)' == 'Library' ">
<OutputPath>$(SolutionDir)bin\$(Configuration)\$(Platform)\lib\</OutputPath>
<OutDir>$(OutputPath)</OutDir>
</PropertyGroup>
<PropertyGroup Condition=" '$(OutputType)' == 'Exe' ">
<OutputPath>$(SolutionDir)bin\$(Configuration)\$(Platform)\</OutputPath>
<OutDir>$(OutputPath)</OutDir>
</PropertyGroup>
<!-- sets "Copy Local" property of references to false on reopen of solution
don't copy output files of referenced assemblies, since everything builds to the same folder -->
<ItemDefinitionGroup>
<Reference>
<Private>False</Private>
</Reference>
<ProjectReference>
<Private>False</Private>
</ProjectReference>
</ItemDefinitionGroup>
</Project>

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

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)

using AssemblySearchPaths in csproj files

I am trying to set up my csproj files to search for dependencies in a parent directory by adding:
<PropertyGroup>
<AssemblySearchPaths>
..\Dependencies\VS2012TestAssemblies\; $(AssemblySearchPaths)
</AssemblySearchPaths>
</PropertyGroup>
I added this as the last PropertyGroup element right before the first ItemGroup which has all of the Reference declarations.
Unfortunately this is causing all of the other references to fail to resolve, for example:
ResolveAssemblyReferences:
Primary reference "Microsoft.CSharp".
9>C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Microsoft.Common.targets(1578,5): warning MSB3245: Could not resolve this reference. Could not locate the assembly "Microsoft.CSharp". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors.
For SearchPath "..\Dependencies\VS2012TestAssemblies\".
Considered "..\Dependencies\VS2012TestAssemblies\Microsoft.CSharp.winmd", but it didn't exist.
Considered "..\Dependencies\VS2012TestAssemblies\Microsoft.CSharp.dll", but it didn't exist.
Considered "..\Dependencies\VS2012TestAssemblies\Microsoft.CSharp.exe", but it didn't exist.
Is there a simple way for me to tell msbuild to where to search for my project's dependencies? I realize I can use /p:ReferencePath, however I prefer to have compilation logic in the csproj files themselves rather than have TFS Team Builds dictate where to look, not to mention that I'd like this to be able to be compiled on other developers machines.
I did try moving $(AssemblySearchPaths) to be first in list, but that did not help.
Can you change the value of the "AssemblySearchPaths" property within the Target "BeforeResolveReferences" and see if that solves your issue?
<Target Name="BeforeResolveReferences">
<CreateProperty
Value="..\Dependencies\VS2012TestAssemblies;$(AssemblySearchPaths)">
<Output TaskParameter="Value"
PropertyName="AssemblySearchPaths" />
</CreateProperty>
</Target>
Seems like there was a fix recently Thus this works as well:
<PropertyGroup>
<ReferencePath>MY_PATH;$(ReferencePath)</ReferencePath>
</PropertyGroup>
This makes the assemblies in that folder to also show up in the "Add References..." window :)
And since you also might not want the assemblies to be copied into the output-folder, here an example on how to achieve this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- ... -->
<PropertyGroup>
<!-- Add paths to ReferencePath. E.g. here it is Unity. -->
<ReferencePath>C:\Program Files\Unity\Hub\Editor\$(UNITY_VERSION)\Editor\Data\Managed\UnityEngine;$(ReferencePath)</ReferencePath>
</PropertyGroup>
<Target Name="DontCopyReferencePath" AfterTargets="ResolveAssemblyReferences">
<!-- Don't copy files indirectly referenced by ReferencePath -->
<ItemGroup>
<!-- Collect paths to allow for batching -->
<ReferencePaths_ Include="$(ReferencePath)" />
<!-- Use batching to remove all files which should not be copied. -->
<ReferenceCopyLocalPaths Remove="#(ReferencePaths_ -> '%(Identity)\*.*')" />
</ItemGroup>
</Target>
<!-- ... -->
</Project>

How do I use my .targets file in Visual Studio with custom build actions?

I am a beginner with MSBuild. So far I have been able to create a custom task called 'MakeTextFile' which creates a text file in C:\ based on the contents property you pass it. This works running from a command line prompt.
I have also included this in my .targets file (under the project tag):
<ItemGroup>
<AvailableItemName Include="CreateTextFileAction" />
</ItemGroup>
When I use the Import tag on my client applications .csproj I can now set items build actions to 'CreateTextFileAction', however the action never triggers (as no text file on C:\ is created)
How do I get all the file paths of items that were marked with my build action 'CreateTextFileAction' and pass them onto my custom task?
For reference, my .targets file:
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<AvailableItemName Include="CreateTextFileAction" />
</ItemGroup>
<UsingTask AssemblyFile="CustomMSBuildTask.dll" TaskName="CustomMSBuildTask.MakeTextFile" />
<Target Name="MyTarget">
<MakeTextFile Contents="TODO HOW DO I GRAB MARKED FILES?" />
</Target>
</Project>
A csproj file has a defined set of targets. The three main entry points are Build, Rebuild and Clean. These targets each have a set of dependencies. If you write your own targets to be part of the standard csproj build you need to find a suitable injection point within these dependencies.
For ease of use there are two standard targets for you to override called BeforeBuild and AfterBuild. If you define this in the csproj file (after the import of the csharp targets file) and call your custom task in there then it should work (or at least move further along).
<Target Name="BeforeBuild">
<MakeTextFile Contents="TODO HOW DO I GRAB MARKED FILES?" />
</Target>