What values can the MSBuild output TaskParameter take? - msbuild

In an MSBuild script, I have the following:
<Target Name="CompileCode">
<MSBuild Projects="$(SolutionPath)" Targets="Build" Properties="...">
<Output TaskParameter="TargetOutputs" ItemName="Binaries" />
</MSBuild>
</Target>
The output of this target will be a collection Binaries which contains all the assemblies from my project. I'd like to include all assemblies, including external libraries that I've referenced (such as NUnit or Castle.Core). For that, I Imagine there is another value I should set for the TaskParameter - but which one?
I'd like to know of all the available options here, not just the ones that applies to my specific case - there are other things in this build script that might be eaiser (or even no longer impossible) if I know all my options...
So, what can I put in the TaskParameter property?

When using the <Output /> targets output, valid values for the TaskParameter property would be any readable parameter of the <MSBuild /> task.
The solution for your problem at hand will be to ensure that the projects in your solution specify to copy all referenced assemblies, i.e. the property CopyLocal is set to true for every referenced assembly you want to receive in Binaries (via TargetOutputs).

Related

Identifying a build as being due to a dependent project reference

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

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.

In MSBuild can you set per-file properties for all files once?

I'm trying to setup a simple rule for VS2010/MSBuild builds to reduce project management. It's related to the 'ExcludedFromBuild' property.
The rule is, if the filename doesn't have the platform name in it, ExcludedFromBuild = true.
ie-
I have Win32Math.cpp & Win64Math.cpp. I only want Win32Math to be compiled when I'm buliding the Win32 Platform. Similar for Win64.
Setting this up per file is easy, but a bit tedious. We have 4 platforms we're targeting, and each time we add a file we have to update properties for each target. I want the rule to be global, so each time I add a platform file I don't have to go through the setup each time.
Is this possible?
You can use ítem definition groups for this kind of thing http://msdn.microsoft.com/en-us/library/bb629392.aspx, but I don't quite understand your specific situation. You'll probably need to set the metadata based on the item's filename matching the platform.
This shows how to use property functions with item metadata. Using Item functions on metadata values
It is possible, but you can't test intrinsic item metadata in <ItemDefinitionGroup>s. The only known way is to use a target.
<Target Name="RemoveNonPlatformItems" BeforeTargets="ClCompile">
<ItemGroup>
<ClCompile>
<ExcludedFromBuild Condition="!$([System.String]::Copy(%(FileName)).Contains($(Platform)))">true</ExcludedFromBuild>
</ClCompile>
</ItemGroup>
</Target>
Or even better:
<Target Name="RemoveNonPlatformItems" BeforeTargets="ClCompile">
<ItemGroup>
<ClCompile Remove="%(Identity)" Condition="!$([System.String]::Copy(%(FileName)).Contains($(Platform)))" />
</ItemGroup>
</Target>

MsBuild: Passing an ItemGroup with CallTarget

I'm having some problems witht the scoping of item groups I create in an MSBuild script. Basically, what I want to do is to have two different targets - let's call them RunUnitTests and RunIntegrationTests - that generate an item group called TestAssemblies and then call RunTests, which uses TestAssemblies to determine which assemblies to run tests from.
The two different targets for unit and integration tests both depend on the build target and get an item group with all compiled assemblies from there, but since the RunTests target will be called from different places, it can't really depend on either of them. Thus, I need to pass the item group to the common testrunner target somehow. However, this seems to be impossible, because changes to an item group within a target seems to be scoped to only work within that target.
I've seen these posts, but they only seem to confirm my fears, and suggest DependsOnTarget as a workaround - which won't work for me, since I need to get the items from different places on different runs.
This is what I have so far:
<Target Name="RunAllTests" DependsOnTarget="BuildProject">
<!-- In here, items created in BuildProject are available. -->
<CallTarget Targets="RunUnitTests;RunIntegrationTests">
</Target>
<Target Name="RunUnitTests" DependsOnTarget="BuildProject">
<!-- In here, items created in BuildProject are available. -->
<!-- One of those is #(UnitTestAssemblies) -->
<CreateItem Include="#(UnitTestAssemblies)">
<Output TaskParameter="Include" ItemName="TestAssemblies" />
</CreateItem>
<CallTarget Targets="RunTests" />
</Target>
<!-- Then there's a similar target named RunIntegrationTests, which does the
same as RunUnitTests except it includes #(IntegrationTestAssemblies) -->
<Target Name="RunTests">
<!-- Here, I'd like to access #(TestAssemblies) and pass them to the NUnit
task, but they have fallen out of scope. -->
</Target>
Is there any way around this, or will I have to completely restructure my build script?
Changes to an item group within a target are only visible to other targets after the changing target exits. So to get the list of test assemblies to stick, you may have to move actually setting up the targets to its own target as in the following:
<Target Name="PrepareUnitTestList" DependsOnTarget="BuildProject">
<ItemGroup>
<TestAssemblies Include="#(UnitTestAssemblies)"/>
</ItemGroup>
</Target>
<Target Name="RunUnitTests" DependsOnTargets="PrepareUnitTestList">
<CallTarget Targets="RunTests"/>
</Target>
<Target Name="RunTests">
<Message Text="Test: %(TestAssemblies.Identity)"/>
</Target>
In "MSBuild" task you can pass properties to targets, but I'm not sure if it will work for ItemGroup. But you definitely can do it through batching - passing one assembly at a time.
<Target Name="RunUnitTests">
<MSBuild Projects="$(MSBuildProjectFullPath)" Targets="RunTests" Properties="TestAssemblies=%(TestAssemblies.Identity)"/>
</Target>
It would run the "RunTests" for only one assembly at a time, so it will be useless if you need knowledge of other assemblies at time of running tests. But maybe it will give some better ideas how to resolve this problem...

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.