I created a custom common target "RealClean" which remove every files in the output and "intermediate output" directory. I put it in the Microsoft.Common.targets file. When I run MsBuild on my csproj everything is fine. But when I run MsBuild on my sln (which just references a list of csproj) I have the following error
error MSB4057: The target "RealClean" does not exist in the project.
Here is the command line I enter to run MsBuild
C:\Windows\Microsoft .NET\Framework\v3.5\MsBuild.exe /p:Configuration="Release";OutputPath="..\..\MSBuild.Referentiel.net35";nowarn="1591,1573" /t:RealClean mySolution.sln
Any hint?
To work on solution file, MSBuild creates a temporary MSBuild project file containing only some targets like Build and Clean. So you can't call your custom target on a solution file.
I had the same issue but didn't want to modify things outside of the source tree in order to get this to work. Adding files to C:\Program Files... means that you have to do this manually on every dev machine to get the same behavior.
I did three things:
1) Created a Custom targets file which I import into every C# and/or VB/F# project in my solution by adding the following to each proj file:
<!-- Rest of project file --> <PropertyGroup Condition="'$(SolutionDir)' == '' or '$(SolutionDir)' == '*undefined*'"> <!-- Relative path to containing solution folder --> <SolutionDir>..\</SolutionDir> </PropertyGroup> <Import Project="$(SolutionDir)CommonSettings.targets" />
2) Added a clean target which gets called after the real Clean (using the AfterTargets attribute from MSBuild 4.0):
<Target Name="CleanCs" AfterTargets="Clean"> <Message Text="Deep cleaning C# project..." /> <CreateItem Include="$(OutDir)**\*.*; $(ProjectDir)\obj\**\*.*; $(IntermediateOutputPath)**\*.*" Exclude="**\bin\**\*.vshost.exe; $(IntermediateOutputPath)**\*.log"> <Output TaskParameter="Include" ItemName="AfterClean_FilesToDelete"/> </CreateItem> <Delete Files="@(AfterClean_FilesToDelete)" /> <CreateItem Include="$(ProjectDir)\obj\" > <Output TaskParameter="Include" ItemName="AfterClean_DirectoriesToDelete" /> </CreateItem> <CreateItem Include ="$(ProjectDir)\bin\" Condition="'$(TargetExt)' != '.exe'" > <Output TaskParameter="Include" ItemName="AfterClean_DirectoriesToDelete"/> </CreateItem> <RemoveDir ContinueOnError="true" Directories="@(AfterClean_DirectoriesToDelete)" /> </Target>
3) In my continuous integration MSBuild project I check and make sure that all proj files have #1:
<ItemGroup> <!-- Exclude viewer acceptance tests as they must compile as x86 --> <CheckProjects_CsProjects Include="**\*.csproj" /> </ItemGroup> <Target Name="CheckProjects"> <!-- Look for C# projects that don't import CommonSettingsCs.targets --> <XmlRead XPath="//n:Project[count(n:Import[@Project[contains(string(), 'CommonSettingsCs.targets')]]) = 0]/n:PropertyGroup/n:AssemblyName/text() " XmlFileName="%(CheckProjects_CsProjects.Identity)" Namespace="http://schemas.microsoft.com/developer/msbuild/2003" Prefix="n" > <Output TaskParameter="Value" ItemName="CheckProjects_CsMissingImports"/> </XmlRead> <Error Text="Project missing CommonSettingsCs.targets: %(CheckProjects_CsMissingImports.Identity)" Condition="'%(CheckProjects_CsMissingImports.Identity)' != ''" /> </Target>
This prevents developers from forgetting to add #1. You could create your own project template to ensure that al new projects have this by default.
The advantage to this approach is setting up a new source tree enlistment doesn't involve anything more than getting the current source tree. The downside is that you have to edit the project files once when you create them.
Madgnome is probably right. But I wanted to add that you should not be editing the Microsoft.common.targets files. If you do so you risk having a different build process on that machine versus what everybody else has. In your case you could have created a new MSBuild file with just the RealClean target and placed it at C:\Program Files (x86)\MSBuild\v4.0\Custom.After.Microsoft.Common.targets or for 32 bit C:\Program Files\MSBuild\v4.0\Custom.After.Microsoft.Common.targets and essentially that would be the same as putting that file inside of Microsoft.Common.targets, except you don't have to modify that file.