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.
Related
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.
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
I'm trying to figure out when the different targets are run. But I'm a little confused when it comes to the AfterBuild target, it's comment is "Redefine this target in your project in order to run tasks just after Build". But when I look at what Build depends on I see:
<BuildDependsOn>
BeforeBuild;
CoreBuild;
AfterBuild
</BuildDependsOn>
Dos not this mean that the Build target runs after "AfterBuild" or am I missing something here? I'm new to Build so maybe I have missed something trivial.
You should do some further research in the same file (just do a search on BuildDependsOn in a text editor): you'll see the Build target itself is just a stub that looks something like this:
<Target
Name="Build"
DependsOnTargets="$(BuildDependsOn)"/>
So when one calls msbuild /t:Build, msbuild looks up the build target and sees it has a DependsOnTargets property whith the value BeforeBuild;CoreBuild;AfterBuild (note that is a list). Since DependsOnTargets is always executed before the target itself, all targets listed therein executed first in the order listed. Only then the Build target itself is executed (so yes, that effectively happens after AfterBuild). But the Build target itself actually doesn't do anything: compiling etc all happens in CoreBuild so by the time it's invoked everything is done already.
This might seem odd at first, but it's actually a very expandable way to make targets depends on each other and define the order in which they run. (there's DependsOn, but also BeforeTargets and AfterTargets) So suppose you want a target that for clarity effectively runs after Build, you can use the same principle:
<Target Name="MyTarget" AfterTargets="Build">
...
</Target>
Note this is actually the preferred way: in large projects it's not reliable to override AfterBuild since you don't know if somebody else also did it already, and overriding it in multiple places results in only the last one found to be called.
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'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>