Identifying a build as being due to a dependent project reference - msbuild

In MSBuild is there a property, or some other mechanism, that indicates that the current project is being built because it was a referenced by another project?

After looking around a bit this does not seem possible using built-in functionaility. From one point of view this makes sense: why would a project have to know whether it's built directly by the user vs being built as a dependency? Possibly the MSBuild team followed that logic as well: there are quite a lot of extensions points in MSBuild but not for doing this.
Two problems: the code for building the dependent projects is just using the MSBuild Task and does not provide a way to pass properties. But even if it did, it would only work when building from the command line, not in VS, so it's not a 'complete' solution. Here's a snippet taken from the ResolveProjectReferences which builds the dependent projects:
<!--
Build referenced projects when building from the command line.
-->
<MSBuild
Projects="#(_MSBuildProjectReferenceExistent)"
Targets="%(_MSBuildProjectReferenceExistent.Targets)"
BuildInParallel="$(BuildInParallel)"
Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform)"
Condition="'%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '#(ProjectReferenceWithConfiguration)' != '' and '$(BuildingInsideVisualStudio)' != 'true' and '$(BuildProjectReferences)' == 'true' and '#(_MSBuildProjectReferenceExistent)' != ''"
ContinueOnError="$(ContinueOnError)"
RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove)">
...
</MSBuild>
So, no way to add properties here. Though as you figured you can remove properties by setting a ProjectReference's GlobalPropertiesToRemove; depending on what you're after this could be valueable.
For the rest there aren't many options left; you can specify the target used: _MSBuildProjectReferenceExistent.Targets gets set to $(ProjectReferenceBuildTargets) so you can override the target called but then you'd need all your projects which could possibly be dependent projects to declare a custom target (which would in turn call the Build target as well in order not to break things). Doable, but not nice, and not a direct answer to the question. Same goes for other solutions: you could just override the whole ResolveProjectReferences target (for any project which can have dependent projects) by copying it and adding a property in the snippet shown above.
But as said (and as shown in the Condition in the above snippet): none of these possible solutions would apply when building in VS. I don't know exactly why or how that works, but if A depends on B and you build A in VS and it sees B is out of date it just fires up a build for it before even building A and I don't know of any standard way to interact with that.

In addition to #stijn's answer, I've discovered that you can also prevent a property from being passed to dependent projects.
For example, you can prevent Web Project dependencies from building with the top level project by updating their <ProjectReference> to include <GlobalPropertiesToRemove>DeployOnBuild</GlobalPropertiesToRemove>. Or, to do it automatically based on another property:
<PropertyGroup Condition="'$(DisableProjectReferenceDeployOnBuild)'=='true'">
<BeforeResolveReferences>
$(BeforeResolveReferences);
DisableProjectReferenceDeployOnBuild
</BeforeResolveReferences>
</PropertyGroup>
<Target Name="DisableProjectReferenceDeployOnBuild">
<ItemGroup>
<_ProjectReferencesTmp Include="#(ProjectReferences)" />
<ProjectReferences Remove="#(ProjectReferences)" />
<ProjectReferences Include="#(_ProjectReferencesTmp)">
<GlobalPropertiesToRemove>%(GlobalPropertiesToRemove);DeployOnBuild</GlobalPropertiesToRemove>
</ProjectReferences>
</ItemGroup>
</Target>
(I won't mark this as the answer since it doesn't directly answer the question I asked)

With modern versions of visualstudio/.net SDK, you can do this in your Directory.Build.targets to apply this to all (as this requires participation from the project which is depending on other projects):
<?xml version="1.0"?>
<Project>
<PropertyGroup>
<IsBuildDueToProjectReference Condition=" '$(IsBuildDueToProjectReference)' == '' ">false</IsBuildDueToProjectReference>
</PropertyGroup>
<Target AfterTargets="AssignProjectConfiguration" Name="SetIsBuildDueToProjectReferenceOnProjectReferences">
<ItemGroup>
<ProjectReferenceWithConfiguration>
<AdditionalProperties>%(ProjectReferenceWithConfiguration.AdditionalProperties);IsBuildDueToProjectReference=true</AdditionalProperties>
</ProjectReferenceWithConfiguration>
</ItemGroup>
</Target>
</Project>
This works because targets build items from ProjectReferenceWithConfiguration. So you can treat that item as if it is passed as the Projects parameter of the MSBuild Task because its metadata will be carried along.
To see the effect, you can put something like the following in each of your project files:
<Target AfterTargets="Build" Name="PrintInfo">
<Warning Text="IsBuildDueToProjectReference=$(IsBuildDueToProjectReference)"/>
</Target>
For example, if I build ConsoleApp1.csproj which has a dependency on ClassLibrary1.csproj, I get:
C:\Users\ohnob\source\repos\ConsoleApp1\ClassLibrary1\ClassLibrary1.csproj(10,5): warning : IsBuildDueToProjectReference=true
C:\Users\ohnob\source\repos\ConsoleApp1\ConsoleApp1\ConsoleApp1.csproj(15,5): warning : IsBuildDueToProjectReference=false
And if I build ClassLibrary.csproj direct, I get:
C:\Users\ohnob\source\repos\ConsoleApp1\ClassLibrary1\ClassLibrary1.csproj(10,5): warning : IsBuildDueToProjectReference=false

Related

MSBuild - Get a list of projects by a condition

I have the following MSBuild task which runs the "CustomBuildTask" task for every referenced project in parallel:
<Target Name="CustomBuild" AfterTargets="Build">
<ItemGroup>
<CustomProjectReferences Include="#(ProjectReference)" />
</ItemGroup>
<MSBuild
Targets="CustomBuildTask"
BuildInParallel="$(BuildInParallel)"
Projects="#(CustomProjectReferences)"
Condition="'#(CustomProjectReferences)' != ''"
ContinueOnError="true">
</MSBuild>
</Target>
This seems to work fine. However is there a way I can improve this by filtering "CustomProjectReferences" on some condition. For example only select the projects which have the "CustomBuildTask" task defined or only select the ones with some property defined.
I'd appreciate the help as I'm looking to improve my build time. Thanks
As #stijn points out in his comment, the project files represented by the ProjectReference items would each have to be evaluated by MSBuild in order to know all of the targets and properties defined therein. This would likely not be worth the performance expense. However, you may be able to get the data you need without a huge performance hit by using the XmlPeek task. Depending on your needs, this might be sufficiently reliable for the scenario you suggest where you are checking for the presence a target, but only if that target would never be incorporated via an Import nor ever have a Condition attribute. The property scenario would be much more dicey, only working if you know the property will never be defined via an Import, never defined or updated inside a Target, and never have a Condition attribute. If you go down this road, a helpful piece of the puzzle will be Item Functions, e.g. WithMetadataValue.

MSBuild Managed vs Unmanaged property

Is there a way in MSBuild logic to determine if I am running managed vs unmanaged code? Not C++ vs C#, but just managed vs unmanaged? I'd like to set some properties (usually just version information) differently depending on whether the code is managed or unmanaged.
There are normally two things that change in a vcxproj file for managed complation (afaik, at least that's how we have it in our master c++/cli property sheet used for all cli projects: the CLRSupport property is set to true and the ClCompile ItemGroup has the CompileAsManaged metadata set to true. You can check on any of these or both. Here's a target which prints the values:
<Target Name="CheckManaged">
<ItemGroup>
<ClCompile Include="dummy.cpp" />
</ItemGroup>
<PropertyGroup>
<CompileAsManaged>#(ClCompile->AnyHaveMetadataValue('CompileAsManaged','true'))</CompileAsManaged>
</PropertyGroup>
<Message Text="CompileAsManaged is $(CompileAsManaged) and CLRSupport is $(CLRSupport)" />
<ItemGroup>
<ClCompile Remove="dummy.cpp" />
</ItemGroup>
</Target>
As you can see getting the CompileAsManaged metadata value requires some treatment: I'm adding an item to the ClCompile group because if the group is empty you canot use CompileAsManaged; normally you can just omit this.
In C++, each item in ClCompile (list of source files) has a CompileAsManaged metadata value. Setting properties is difficult since it can vary for each source file, but is more straightforward if you only expect (and support) keying off the whole-project setting. Toggle that in the IDE and see what changes in the vcxproj file. It has a few different values to choose from.

Default or specify msbuild properties in an external file

Ok, so I have a few dozen solutions all built using the exact same command line.
msbuild SolutionName.sln /p:property1=value1;property2=value2;etc etc etc.
Except the number of properties just grows and grows.
Is there a way to specify an external file some how so I don't end up with a 10 line msbuild command? (Think property 100, property 101, etc).
I'm aware of .wpp.target files. However, having to copy them into each project folder really... is my last resort.
And no, I'm not modifying any default MSBuild targets/files whatsoever.
To answer the original question, yes you can specify properties in an external file. They are called MSBuild response files.
msbuild somesolution.sln #PathToResponseFile.rsp
Inside the response file you can put your properties, one per line.
/verbosity:detailed
/target:build
/platform:AnyCPU
/configuration=Release
Some links to better understand:
http://dailytechlearnings.wordpress.com/2011/08/24/msbuild-response-file/
http://msdn.microsoft.com/en-us/library/vstudio/ms404301.aspx
However, using an msbuild file to build your solutions and projects is a better solution. You can create global targets that will do exactly as you want. You can create your own custom Clean and Build targets that will then build/clean your solutions.
First of all - I would recommend you to use msbuild scripts to build your solutions, instead of direct building sln file using command line. E.g. use something like this:
msbuild SolutionName.Build.proj
and inside this Solution1.Build.proj you can put anything as simple as
<Project ToolsVersion="4.0" DefaultTargets="BuildMe" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="BuildMe">
<MSBuild Projects="SolutionName.sln" Properties="property1=value1;property2=value2;"/>
</Target>
</Project>
After this step, which adds flexibility to your build process, you can start leverage AdditionalProperties metadata for MSBuild task.
Then you can use <Import construction to store your list of shared properties in a separate file and item metadata for passing property values:
<Project ToolsVersion="4.0" DefaultTargets="BuildMe" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="MySharedProperies.props" />
<ItemGroup>
<ProjectToBuild Include="SolutionName.sln">
<AdditionalProperties>SomeProjectSpecificProperty</AdditionalProperties>
</ProjectToBuild>
</ItemGroup>
<Target Name="BuildMe">
<MSBuild Projects="#(ProjectToBuild)" Properties="#(MySharedProperies)"/>
</Target>
</Project>
You can check this post for more details about properties and additional properties metadata or this original MSDN reference (scroll to Properties Metadata section)
This is the base idea how to do it, if you have any questions - feel free to ask.
I use an Import file for things that are common across various projects.
<Import Project="CommonBuildProperties.proj"/>
That file contains a PropertyGroup that has the things I want to have the same value across build projects. There's also a conditional statement there that sets certain folder names depending on the name of the computer it's running on. At runtime, if I need to override anything on the command line I do that.
I also have some project-specific Import files (one of our builds is a Powerbuilder application with its own toolset and pecadilloes); order of Import ensures if they require different values for the same element name I get what I want.
My command lines aren't horrible unless I'm doing something odd that needs most everything overridden. About the only things I have to pass in are version number and build type (release or debug).

How to avoid repetition in MSBuild?

I don't mind an occasional repetition of something when it's necessary, but in MSBuild I really don't know how to ever avoid repetition. It doesn't offer "functions" in the usual sense; a target can only ever get called once, even via CallTarget, and <Import> only works on Project level.
Here's a specific example I'm trying to de-"repetize":
<Target Name="Tgt1">
<PropertyGroup><Conf1>Twiddle</Conf1><Conf2>Thing</Conf2></PropertyGroup>
<PropertyGroup><xxxxxxxxxxExePath>$(xxxxxxxBuildRoot)\$(Conf1)Console-xxxxxxxxed</xxxxxxxxorExePath></PropertyGroup>
<MSBuild Projects="$(BuildSingleProj)" Targets="Build;Merge"
Properties="Configuration=$(Conf1)$(Conf2);Platform=$(Platform);CompiledFileName=$(CompiledFileName);ProjectName=$(ProjectName);SolutionFile=$(SolutionFile);Root=$(Root);Caller=$(MSBuildProjectFullPath)"/>
<MakeDir Directories="$(xxxxxxxxorExePath)" />
<WriteLinesToFile File="$(xxxxxxxxorExePath)\xxxxxxx.IsPortable.txt" />
<WriteLinesToFile File="$(xxxxxxxxorExePath)\xxxxxxx.Global.Settings.xml" Lines="#(xxxxxxxLicense)" Overwrite="true" />
<Exec Command='$(xxxxxxxxorExePath)\xxxxxxx.exe -a "$(xxxxxxxBuildRoot)\$(Conf1)$(Conf2)-Merged\xxxxxxx.exe" "$(xxxxxxxBuildRoot)\$(Conf1)$(Conf2)-xxxxxxxxed\xxxxxxx.exe"'/>
</Target>
I have four such targets, Tgt1, Tgt2, Tgt3, Tgt4. The only thing that differs between these four targets is the first line, the one that defines Conf1 and Conf2.
The only more or less workable de-duplication idea that I'm aware of is by moving the shared code to a new target and calling it via the MSBuild task. This, unfortunately, requires a loooooong string of properties to be manually passed in, and this task uses rather a few (I counted 11 properties and 1 item group).
An additional requirement is that I can invoke the script with an arbitrary subset of these targets, e.g. \t:Tgt2,Tgt3.
Is there any sensible alternative to just copy/pasting this chunk of code - that doesn't involve copying around huge lists of properties instead?
This is a perfect scenario to use Batching.
You'll need to create custom Items with the appropriate metadata and then create a single Target to reference the new Items.
You can wrap each Item in it's own target like so:
<Target Name="Tgt1">
<ItemGroup>
<BuildConfig Include="Tgt1">
<Conf1>Twiddle</Conf1>
<Conf2>Thing</Conf2>
</BuildConfig>
</ItemGroup>
</Target>
<Target Name="Tgt2">
<ItemGroup>
<BuildConfig Include="Tgt2">
<Conf1>Twaddle</Conf1>
<Conf2>Thing 1</Conf2>
</BuildConfig>
</ItemGroup>
</Target>
<Target Name="Tgt3">
<ItemGroup>
<BuildConfig Include="Tgt3">
<Conf1>Tulip</Conf1>
<Conf2>Thing 2</Conf2>
</BuildConfig>
</ItemGroup>
</Target>
You'll then need a core target to call that will perform all of the work like so:
<Target Name="CoreBuild" Outputs="%(BuildConfig.Identity)">
<Message Text="Name : %(BuildConfig.Identity)" />
<Message Text="Conf1 : %(BuildConfig.Conf1)" />
<Message Text="Conf2 : %(BuildConfig.Conf2)" />
</Target>
Adding Outputs="%(BuildConfig.Identity)" to the target will make sure you batch at the target level instead of at the task level.
You can execute this from msbuild with passing arbitrary combinations of the targets as long as the last target is your core target. For example executing this command MSBuild.exe test.msbulid /t:Tgt1,Tgt3,CoreBuild will give you the following output:
Name : Tgt1
Conf1 : Twiddle
Conf2 : Thing
Name : Tgt3
Conf1 : Tulip
Conf2 : Thing 2
DRY is not a tenet of MSBuild. With that being said its not good to repeat yourself in any case, when it is reasonably avoidable. The answer that Aaron gave regarding batching is a good one. This is one means to prevent duplication.
One thing that I would like to point out is that at a higher level it seems like you are thinking of MSBuild as a procedural language (i.e. having functions that you can call and what not). MSBuild is much more declarative than procedural though. If you are creating MSBuild scripts and you have the mindset 'Create function X so that I can call it at point Y', then you're entering a world of pain. Instead you should think of MSBuild as phases. For example; gather files, compile, publish, etc. When you think of it in this way then it makes total sense why targets are skipped after they have been executed once (which you've obviously observed during your trials).
Also after having been working with MSBuild for as long as I have I've figured out that it can really be a PITA to do things in a generic/uber-reusable way. It can be done, but I would reserve that type of effort for .targets files that you know for sure will be re-used many times. Now a days instead of going through that I am much more pragmatic and I land somewhere in between totally hacking scripts & doing things the way I used to do. I have a set of scripts that I re-use, but besides those I try and keep things simple. One big reason for this is that there are a lot of people how know the basics of MSBuild, but very few who have a very deep knowledge of it. Creating good generic scripts requires a deep knowledge of MSBuild, so when you leave a project the person who comes in behind you will have no idea what you were doing (perhaps good if you are a contractor? lol).
In any case I've got a bunch of resources on batching at: http://sedotech.com/Resources#Batching.

CreateItem vs ItemGroup

What is the difference between creating an item inside a target like this:
<Target Name="DoStuff">
<CreateItem Include="#(IntermediateAssembly)" >
<Output TaskParameter="Include" ItemName="FileWrites"/>
</CreateItem>
</Target>
and like this:
<Target Name="DoStuff">
<ItemGroup>
<FileWrites Include="#(IntermediateAssembly)" />
</ItemGroup>
</Target>
When would you use one or the other and why?
In versions of MSBuild prior to 3.5 you could not define properties or items inside of targets (like in your second example). So a task was used instead (CreateItem and CreateProperty)
If you are using ToolsVersion 3.5 then you don't need to use CreateItem anymore (though you still can if you prefer).
In the end they both create the item the same, with the same scope. Using the second syntax is more readable and setting up custom meta data is much easier (in my opinion).
NOTE: The 3.5 version of MSBuild is installed with .NET 3.5. Though you need to define ToolsVersion="3.5" in the Project tag of your MSBuild file to use 3.5 features.
In case you are wondering, I got most of this info from the book Inside the Microsoft® Build Engine: Using MSBuild and Team Foundation Build which I really liked (but am not affiliated with in any way).
CreateItem and CreateProperty are obsoleted in MSBuild 3.5 (though will always continue to work, of course). It was pretty obvious we needed the same familiar syntax for ItemGroup and PropertyGroup to work inside targets.
But ItemGroup inside a target has some special extra powers. It can modify items: for example, this will add true to all items in Resources list that have a metadata named Primary with value of true; only if there isn't already Copy metadata:
<ItemGroup>
<Resources Condition=" '%(Primary)' == 'true' ">
<Copy Condition=" '%(Copy)' == '' ">true</Copy>
</Resources>
</ItemGroup>
One other magic power: you can now remove items from a list. This example will remove all items from the Resources list that have metadata Type with value Bitmap:
<ItemGroup>
<Resources Condition=" '%(Type)'=='Bitmap' " Remove="#(Resources)"/>
</ItemGroup>
These magic powers only work inside at present, not outside.
For full details of this stuff, I highly recommend Sayed Hashimi's book on MSBuild. It's easily found on Amazon.
Dan -- msbuild team.
I dont think the answer accepted has defined the difference.
The difference is:
ItemGroup is evaluated when the MSBuild script is loaded.
CreateItem is evaluated when the Target is executed
This can lead to different values of the Item within the script.
Take the example of a Task that does something with a all the files that match "*.txt" in a directory. If your MSBuild script is loaded in visual studio, only the files that existed when VS started will be in the Item if you use ItemGroup.
If you use CreateItem - it will do a search for all *.txt files when the target is executed.
As an additional info for others passing here: The Build-Engine that contains an API to construct MSBuild projects does not support adding ItemGroups the new way to a Target. Here you WILL have to use the old-fashioned way.