I have a NuGet package I'm working on with custom props/targets files and am wondering about best practice for how to many Properties, Items, and Targets in terms of definition and overriding.
Is it better to have all properties directly in my MyNuGetPackage.targets file and don't use the MyNuGetPackage.props at all, like so?
<PropertyGroup>
<UseMyTarget Condition="'$(Prop1)' == ''">true</UseMyTarget>
</PropertyGroup>
<Target Name="MyTarget" Condition="'$(UseMyTarget)' == 'true'>
...
</Target>
or is it better practice to define UseMyTarget in the .props file, unconditionally and keep the target in the .targets file?
Both approaches break down if I need to catch a property that Microsoft or someone else sets a default value for. For example:
<PropertyGroup>
<MyCustomOutputPath>$(OutputPath)/somethingelse</UseMyTarget>
</PropertyGroup>
If I define this in .props, OutputPath may not have been set yet...
On the other hand MANY authors seem to exclusively use .targets themselves...so am I better off just doing something like this for dependent things in my .targets and not bother with .props files?
<Project InitialTargets="InitializeDependentProperties">
<Target Name="InitializeDependentProperties">
<PropertyGroup>
<MyCustomOutputPath>$(OutputPath)/somethingelse</UseMyTarget>
</PropertyGroup>
</Target>
</Project>
Microsoft's own practices for their own MSBuild props/targets don't seem to follow any kind of standard/convention at all.
I DO want to allow consuming projects to be able to alter behavior of my .props/.targets by setting Properties/Items/Targets themselves.
Related
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
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.
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).
I'd like to be able to write the below, but I can't in MSBuild:
<Target Name="SetDynamicPropertyValues">
<PropertyGroup>
<TargetHost>$($(Target-Environment)-Host)</TargetHost>
</PropertyGroup>
</Target>
This is easily done in NAnt using the property::get-value function. The answer to an earlier question shows an approach using the Condition attribute.
Is there a nicer way to do this?
MSBuild processes property names once. To do that kind of feature it must call the preprocessing several times. I think it would be better to use a Condition-based approach.
<PropertyGroup>
<TargetHost Condition="'$(Target-Environment)'=='Env1'">Host_1</TargetHost>
<TargetHost Condition="'$(Target-Environment)'=='Env2'">Host_2</TargetHost>
<TargetHost Condition="'$(TargetHost)'==''">DefaultHost</TargetHost>
</PropertyGroup>
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.