Some of my .csproj project files have special target "AssembleJS" that merges all .js files included in project in one big file (i.e. webcontrols.csproj has target "AssembleJS" with output "webcontrols.js").
So if I have project parts.csproj
That has target AssembleJS.
References project webcontrols.csproj.
References utility project utils.csproj that does not have any JavaScript and does not have AssembleJS target.
I want target AssembleJS of parts.csproj execute AssembleJS in webcontrols.csproj (the same was as MSBuild works with standard Build target).
Something like
<MSBuild Project="#ReferencedProjects" Targets="AssembleJS"/>
does not work because utils.csproj does not have target AssembleJS.
Is there any way to filter #ReferencedProjects based on whether project has certain target?
Any other idea on how to handle this scenario?
You cannot do what you are requiring. But you might be able to acheive it with batching.
<Project xmlns=''>
<ItemGroup>
<ReferencedProjects Include="webcontrols.csproj">
<Type>Web</Type>
</ReferencedProjects>
<ReferencedProjects Include="utils.csproj">
<Type>NonWeb</Type>
</ReferencedProjects>
</ItemGroup>
<Target Name="BuildWebProjects">
<MSBuild Projects="#(ReferencedProjects)" Condition=" '%(ReferencedProjects.Type)' == 'Web' " />
</Target>
</Project>
Do a search for MSBuild Batching and find some results on sedodream.com for more
info.
Should I expand on this?
Related
I have a common compile.targets file that is used to build many different solutions. In that file I would like to check if any of the contained projects are using TypeScript, and if they are, verify that certain properties are set in those projects (i.e. TypeScriptNoImplicitAny). I currently build the solution like so:
<Target Name="my-compile-target">
<MSBuild Projects="%(SolutionFile.FullPath)"/>
</Target>
What I would like is to be able to create a task that is run for each .csproj in the solution, that has access to all the properties set in the .csproj.
Is this possible? One workaround I tried was using the BeforeTargets attribute along with the targets I know are used to compile TypeScript:
<Target Name="check-typescript-options" BeforeTargets="CompileTypeScript;CompileTypeScriptWithTSConfig">
<Message Condition="'$(TypeScriptToolsVersion)' != ''" Text="Tools Version is $(TypeScriptToolsVersion)"></Message>
<Message Condition="'$(TypeScriptToolsVersion)' == ''" Text="No tools version found"></Message>
</Target>
Unfortunately, MSBuild gives me a warning that those TypeScript targets do not exist, and thus check-typescript-options is ignored.
As you say you need to "run" in the context of the individual csproj files. To do this, given your setup I would set one of two properties
CustomAfterMicrosoftCSharpTargets or CustomAfterMicrosoftCommonTargets
The easiest way would be to set these like so
<Target Name="my-compile-target">
<MSBuild Projects="%(SolutionFile.FullPath)"
Properties="CustomAfterMicrosoftCSharpTargets=$(PathToCustomTargetProj);" />
</Target>
In this case you would have $(PathToCustomTargetProj) set to some file path which has targets which run within the csproj pipeline. You could set this to compile.targets and then your check-typescript-options will be called as the BeforeTargets will be evaluated and satisfied correctly.
Solution1.sln contains two projects:
ProjectA.csproj
ProjectB.csproj
ProjectB has a custom target called "Foo". I want to run:
msbuild Solution1.sln /t:Foo
This will fail because ProjectA doesn't define the "Foo" target.
Is there a way to make the solution ignore the missing target? (E.g., do nothing if the target doesn't exist for a specific project) without modifying the SLN or project files?
There is a two-part solution if you don't want to edit the solution or project files and you're happy for it to work from MSBuild command-line but not from Visual Studio.
Firstly, the error you get when you run:
MSBuild Solution1.sln /t:Foo
Is not that ProjectA does not contain a Foo target but that the solution itself does not contain a Foo target. As #Jaykul suggests, setting the MSBuildEmitSolution environment variable will reveal the default targets contained within the solution metaproj.
Using the metaproj as inspiration you can introduce a new file "before.Solution1.sln.targets" next to the solution file (the file name pattern is important) with contents like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Foo">
<MSBuild Projects="#(ProjectReference)" Targets="Foo" BuildInParallel="True" Properties="CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)" SkipNonexistentProjects="%(ProjectReference.SkipNonexistentProjects)" />
</Target>
</Project>
The MSBuild element is mostly just copied from the solution metaproj's Publish target. Adjust the target name and any other details to suit your scenario.
With this file in place, you'll now get the error that ProjectA does not contain the Foo target. ProjectB may or may not build anyway depending on inter-project dependencies.
So, secondly, to solve this problem we need to give every project an empty Foo target which is then overridden in projects that actually already contain one.
We do this by introducing another file, eg "EmptyFoo.targets" (name not important) that looks like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Foo" />
</Project>
And then we get every project to automatically import this targets file either by running MSBuild with an extra property, eg:
MSBuild Solution1.sln /t:Foo /p:CustomBeforeMicrosoftCommonTargets=c:\full_path_to\EmptyFoo.targets
Or include the CustomerBeforeMicrosoftCommonTargets property in the Properties attribute on the MSBuild element in the first targets file where you could optionally specify the full path relative to the $(SolutionDir) property.
However, if you're willing to run Foo in conjunction with any of the default solution targets (ie Build, Rebuild, Clean, or Publish) you could take some inspiration for how the Web Publishing Pipeline in MSBuild uses the DeployOnBuild property to call the Publish target on Web projects in a solution containing other project types that don't support publishing.
More info on the before.Solution1.sln.targets file here:
http://sedodream.com/2010/10/22/MSBuildExtendingTheSolutionBuild.aspx
You can target those by project name, like /t:project:target (might need quotes, I can't remember).
You can find all the generated targets by setting the environment variable MSBuildEmitSolution = 1 ... which causes msbuild to save to disk the temp .metaproj file which it generates for your solution. That file has all those targets defined in it, just open it up and take a look ;)
Maybe not the best answer but a reasonable hack.
msbuild ProjectA.csproj
msbuild ProjectB.csproj /t:Foo
When msbuild building solution - msbuild emits only limited set of targets into it's .metaproj file, and afaik - you can't build custom target through building sln file, you have to use original project1.csproj or custom build script.
Just for reference:
Use ContinueOnError when using MSBuildTask or -p:ContinueOnError=ErrorAndContinue when using (dotnet) msbuild
It may be in limited scenarios helpful: For example you have a list of .csproj files and want attach metadata only to specific project file items then you could write something like this:
<Target Name="UniqueTargetName" Condition="'$(PackAsExecutable)' == 'Package' Or '$(PackAsExecutable)' == 'Publish'" Outputs="#(_Hello)">
<ItemGroup>
<_Hello Include="$(MSBuildProjectFullPath)" />
</ItemGroup>
</Target>
<Target Name="BuildEachTargetFramework" DependsOnTargets="_GetTargetFrameworksOutput;AssignProjectConfiguration;_SplitProjectReferencesByFileExistence"
Condition="$(ExecutableProjectFullPath) != ''">
<Message Text="[$(MSBuildThisFilename)] Target BuildEachTargetFramework %(_MSBuildProjectReferenceExistent.Identity)" Importance="high" />
<MSBuild
Projects="%(ProjectReferenceWithConfiguration.Identity)"
Targets="UniqueTargetName"
ContinueOnError="true">
<Output TaskParameter="TargetOutputs" ItemName="_Hallo2" />
</MSBuild>
<Message Text="[$(MSBuildThisFilename)] ########### HELLO %(_Hallo2.Identity)" Importance="high" />
</Target>
I have a project which has some post build events that do some copying around for other projects. I unfortunately cannot change that, and have been asked to write a build script for use on a CI server.
Problem is that the post build steps run off the debug/release bin folders and I compile through the build script to a different folder. So one solution would be to let the project build as is, and then manually copy all files from the bin folders to the output folder I am using. However that feels like a bit of a hack, so I was wondering if there is a way for an MSBuild task to tell the solution it is building to ignore PostBuild events, I believe you could set a property PostBuildEvent='' but it didnt seem to stop them from happening...
Here is an example of the build script target:
<Target Name="Compile" DependsOnTargets="Clean;">
<MSBuild Projects="$(SourceDirectory)\SomeSolution.sln"
Properties="Configuration=Release; OutputPath=$(CompilationDirectory); PostBuildEvent=''" />
</Target>
Anyone had to do anything similar before?
To disable all PostBuildEvents, set the CustomAfterMicrosoftCommonTargets to C:\PostBuild.config (or whatever you name the file) and have PostBuild.config to be:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="PostBuildEvent"/>
</Project>
Add /p:CustomAfterMicrosoftCommonTargets="C:\PostBuild.config" to your msbuild command line
Or update your MsBuild task properties:
<MsBuild Projects="$(ProjectTobuild)" Properties="Configuration=$(Configuration);Platform=$(Platform);CustomAfterMicrosoftCommonTargets='C:\PostBuild.config'" Targets="Build"/>
To disable PostBuildEvents at project level for MSBuild, simply put these codes inside .csproj:
<Target Name="BeforeBuild">
<PropertyGroup>
<PostBuildEvent Condition="'$(BuildingInsideVisualStudio)' == 'false' Or '$(BuildingInsideVisualStudio)' != 'true'"></PostBuildEvent>
</PropertyGroup>
</Target>
Can someone please point me to a reference about target hooks in MSBuild?
I'm looking for something that will let me define targets to run before and after a specified target. I know this can be done using the DependsOnTargets property but I've seen references to using target hooks and I'd like to explore that area.
Thanks,
Zain
A good list of built-in overridable build process hooks can be found here. For custom targets, the only thing I can think of is to use either the DependsOnTarget attribute (like you mentioned) or the BeforeTargets/AfterTargets attribute (like #Ritch Melton mentioned.) Be careful, the BeforeTargets/AfterTargets are only available in MSBuild 4.0
If you understand the idea behind DependsOnTargets then open up the Microsoft.Common.targets file in the .Net SDK directory (C:\Windows\Microsoft.NET\Framework\v3.5). That file defines the build process for the MSBuild task and .Net projects created by Visual Studio. Look for tags called BeforeXXXX, and AfterXXXX. BeforeBuild and AfterBuild are referenced in the default.csproj file - Snippet:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
There are others, like Clean, Rebuild, etc..
Define a Target (or Targets) to execute inside those Target elements, like this (Creates a directory, or list of directories based on the value in the Directories Property:
<Target Name="CreateDir">
<MakeDir Directories="D:\Dogs.txt"/>
</Target>
Then include those Targets in the BeforeXXX Target:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="BeforeBuild" BeforeTargets="CreateDir">
</Target>
</Project>
After MSbuild has built my solution (with an asp.net website), and the webdeployment project has built and put the website in the directory _PublishedWebsites:
c:\mybuilds\buildName\Daily_20090519.3\Release_PublishedWebsites\MyWebsite.
How do I copy this to the fixed directory where IIS points to for the test website?
I have found loads of code snippets, but I cannot seem to find one that will take into account the fact that this directory name changes.
This is pretty easy. You can edit the project and insert something similar to the following.
<PropertyGroup>
<OutputDest>$(MSBuildProjectDirectory)\..\OutputCopy\</OutputDest>
</PropertyGroup>
<Target Name="AfterBuild">
<!-- Create an item with all the output files -->
<ItemGroup>
<_OutputFiles Include="$(OutputPath)**\*" Exclude="$(OutputPath)obj\**\*" />
</ItemGroup>
<!-- You probably don't want to include the files in the obj folder so exclude them. -->
<Message Text="OutputDest : $(OutputDest)" />
<Copy SourceFiles="#(_OutputFiles)"
DestinationFiles="#(_OutputFiles->'$(OutputDest)%(RecursiveDir)%(Filename)%(Extension)')"/>
</Target>
Is this what you are looking for?
My Book: Inside the Microsoft Build Engine : Using MSBuild and Team Foundation Build
I'm using different technique.
<PropertyGroup>
<BinariesRoot>c:\BinariesForIis\</BinariesRoot>
</PropertyGroup>
The c:\BinariesForIis\ will be used for direct output compiled binaries (before copy to ...\Daily_20090519.3\Release_ ...).