1

I am overriding the Build target like this in my file OverrideBuild.targets:

<Target Name="OriginalBuild" DependsOnTargets="$(BuildDependsOn)"> <Message Text="Finished running target OriginalBuild" Importance="High" /> </Target> <Target Name="Build" > <CheckArtifacts ProjectGuid = "$(ProjectGuid)" SolutionPath = "$(SolutionPath)" > <Output PropertyName = "ArtifactsHaveChanged" TaskParameter = "Result" /> </CheckArtifacts> <Message Text="ArtifactsHaveChanged = $(ArtifactsHaveChanged)" Importance="high" /> <!-- if the artifacts.props file has not just been updated then we can run the original build target --> <Message Condition="'$(ArtifactsHaveChanged)' == 'false'" Text="Running target OriginalBuild" Importance="High" /> <CallTarget Condition="'$(ArtifactsHaveChanged)' == 'false'" Targets="OriginalBuild" /> <!-- Otherwise we need to run a new msbuild to avoid using an out-of-date cached version of the artifacts.props file. To force the msbuild process not to use the cached values from this process we must pass at least one property. --> <Message Condition="'$(ArtifactsHaveChanged)' == 'true'" Text="Running target OriginalBuild in nested msbuild" Importance="High" /> <MSBuild Condition="'$(ArtifactsHaveChanged)' == 'true'" Targets="OriginalBuild" Projects="$(MSBuildProjectFullPath)" Properties="InNestedMsbuild=true" /> <!-- Visual Studio doesn't pick up on the modified artifacts.props file unless we force it to reload the solution --> <Touch Condition="'$(ArtifactsHaveChanged)' == 'true' and '$(BuildingInsideVisualStudio)' == 'true'" Files = "$(SolutionPath)" /> <Message Text="Finished running build target override" Importance="High" /> </Target> 

and each of my .vcxproj or .csproj files includes this file at the end:

 <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="..\..\OverrideBuild.targets" /> </Project> 

This works as I want it to for the C++ projects but fails with the C# projects. When building a C# project via msbuild it fails because the command line to the C# compiler is missing reference arguments for local assemblies. For example, a C# file that has a line like this at the top of the file:

using My.Utils.Common; 

fails with the following error message:

error CS0234: The type or namespace name 'Common' does not exist in the namespace 'My.Utils' (are you missing an assembly reference?) 

And looking at the compiler command used it is missing this line:

/reference:C:\Code\scratch\Build\My.Utils.Common\Bin\Release\My.Utils.Common.dll 

That missing line is present when I comment out my override of the Build target. And weirdly enough it will build fine from within Visual Studio even with my Build override in place. It only fails when building using msbuild from the command line and only for C# projects.

I thought that the way I had overriden the Build target would be completely transparent but apparently it isn't. Can anybody shed some light on what is going wrong ?

2
  • Would you mind share us a sample to reproduce this issue? I could not reproduce this issue, since there are some other error when I build the project with Visual Studio. A simple sample will help us to understand this issue more clear. Commented Jun 20, 2018 at 9:16
  • @Alex I've created a simple testcase which shows the problem. I didn't want to clutter up this question too much by including the files inline so you can find a link here: link. The HelloWorld.zip file includes a Visual Studio solution containing 2 C# projects each of which contains a single file. The HelloWorld project calls a function defined in the HelloWorldHelper project. This builds and runs fine in Visual Studio. When I build with msbuild it fails with the error message: The type or namespace name 'Utils' could not be found. Commented Jun 21, 2018 at 7:36

2 Answers 2

2

It seems that when project A depends on project B with a project reference, the outputs of the Build target of B are used to deduce what should be passed as a reference to the compiler when building A. This is presumably somewhere in the ResolveAssemblyReferences logic.

Therefore to get your replacement Build target working, you need to make its outputs match those of the standard Build.

Here is how you can achieve this:

<Target Name="Build" Condition=" '$(_InvalidConfigurationWarning)' != 'true' " DependsOnTargets="GetTargetPathWithTargetPlatformMoniker" Returns="@(TargetPathWithTargetPlatformMoniker)" > </Target> 

Here Returns="@(TargetPathWithTargetPlatformMoniker)" is what the Returns of the standard Build in the SDK is. But the item array @(TargetPathWithTargetPlatformMoniker) is initially empty, so you need to run the Target GetTargetPathWithTargetPlatformMoniker to populate it before hand.

These are implementation details of the build system, so they may vary by SDK version, but you can always inspect the logic in C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\Microsoft.Common.CurrentVersion.target or equivalent.

Note that this cannot be used directly with C++ projects, their default Build target is a bit different. You may need to vary by the project type to support both. The Condition on a Target does not stop it from overwriting the existing one, it only stops it from executing, so if you need a target overwrite to differ, you need to put the alternatives in files and import them conditionally. I don't know of a more convenient way, but that at least works.

Sign up to request clarification or add additional context in comments.

1 Comment

I tried using your modified Build target and it did not work with a Visual Studio 2010 command prompt which is what I was originally using (the GetTargetPathWithTargetPlatformMoniker target does not exist). When I tried using a VS-2015 command prompt instead then I was able to reproduce my original problem but this time your solution worked fine.
0

Why does overriding the Build target in msbuild work for C++ projects but fail for C# projects?

After test your sample, I found the error not comes from the overriden the Build target, it should be related to the project type which you referenced.

Because I have tried comment the import line in the HelloWorld project file:

<Import Project="..\..\OverrideBuild.targets" /> 

Then MSBuild command line still throw that error.

Besides, I found your referenced project HelloWorldHelper is a Console application project, which output type is Class library.

To resolve this issue, I have created a new Class library instead of Console application, then build it from MSBuild command line, it works fine.

So, please try to convert your referenced project to Class library.

Hope this helps.

1 Comment

The HelloWorld example I uploaded builds fine for me if I comment out the line that includes OverrideBuild.targets in the HelloWorldHelper.csproj file. I used this command to build it: "msbuild HelloWorld.sln /p:configuration=debug". I tried converting the HelloWorldHelper project to a Class library and that did not change the behaviour. I have uploaded my reworked example here: jmp.sh/ycIjHgz

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.