I'm having an issue building some projects. I麓ve found differences between building my solutions in VS (it work fine), and building the solutions with msbuild through command line (it fails).
I've managed to make a small sample that illustrates the problem. BuildError.zip
Basically I've got 2 solutions SolA.sln and SolAandB.sln. SolA.sln only contains project A.csproj while SolAandB.sln contains both A.csproj and B.csproj. The project A.csproj is just a class library, while B.csproj is a command line project which references project A.csproj. The subtle peculiarity about this projects, is that A.csproj is configured to not be built in SolAandB.sln. The reason of this, is that I've got a lot of projects (more than 300, some of which are C# and others are C++ managed and unmanaged), and some projects are included in more than one solution (mainly to allow adding references to the project), but I only want to build the project once.
When I build SolAandB.sln in VS (I'm using VS2015 with update 1, but don't believe that changes anything), I can see that the invocation to csc.exe for B.csproj is as follows (I've added line breaks between arguments for clarity):
C:\Program Files (x86)\MSBuild\14.0\bin\csc.exe
/noconfig
/nowarn:1701,1702,2008
/nostdlib+
/platform:anycpu32bitpreferred
/errorreport:prompt
/warn:4
/define:DEBUG;TRACE
/errorendlocation
/preferreduilang:en-US
/highentropyva+
/reference:"F:\Visual Studio 2015\Projects\BuildError\A\bin\Debug\A.dll"
/reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\mscorlib.dll"
/reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.Core.dll"
/debug+
/debug:full
/filealign:512
/optimize-
/out:obj\Debug\B.exe
/ruleset:"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Team Tools\Static Analysis Tools\\Rule Sets\MinimumRecommendedRules.ruleset"
/subsystemversion:6.00
/target:exe
/utf8output
Program.cs
Properties\AssemblyInfo.cs
"C:\Users\Fede\AppData\Local\Temp\.NETFramework,Version=v4.5.2.AssemblyAttributes.cs"
obj\Debug\\TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs
obj\Debug\\TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs
obj\Debug\\TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs
Note the /reference:"F:\Visual Studio 2015\Projects\BuildError\A\bin\Debug\A.dll" which points correctly to the output of A.csproj for the selected build configuration. If I had built in Release, the reference would have correctly pointed to the release build output of A.csproj.
The result of that build depends on having A.csproj already built, which I have. You can see a sample build script in BuildSolutions.proj which first builds SolA.sln and then SolAandB.sln.
Now if I build SolAandB.sln through the command line, or as a result of building BuildSolutions.proj, I can see that the invocation to csc.exe for B.csproj is as follows:
C:\Program Files (x86)\MSBuild\14.0\bin\csc.exe
/noconfig
/nowarn:1701,1702
/nostdlib+
/platform:anycpu32bitpreferred
/errorreport:prompt
/warn:4
/define:DEBUG;TRACE
/highentropyva+
/reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\mscorlib.dll"
/reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.Core.dll"
/debug+
/debug:full
/filealign:512
/optimize-
/out:obj\Debug\B.exe
/ruleset:"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Team Tools\Static Analysis Tools\\Rule Sets\MinimumRecommendedRules.ruleset"
/subsystemversion:6.00
/target:exe
/utf8output
Program.cs
Properties\AssemblyInfo.cs
"C:\Users\Fede\AppData\Local\Temp\.NETFramework,Version=v4.5.2.AssemblyAttributes.cs"
As you can see, beside several apparently subtle differences, the biggest difference is in that the argument /reference:"F:\Visual Studio 2015\Projects\BuildError\A\bin\Debug\A.dll" is missing, and thus the project fails to build with the error error CS0246: The type or namespace name 'A' could not be found (are you missing a using directive or an assembly reference?).
How can I build the solution, in a way that when csc.exe is invoked, it's invoked the same way as when I'm building the solution inside visual studio?
This does look like a bug, thanks for pointing it out.
Some further information from a /v:diag build log. The reference isn't added because it doesn't get computed in ResolveProjectReferences
Target "ResolveProjectReferences: (TargetId:83)" in file "C:\Program Files (x86)\MSBuild\14.0\bin\Microsoft.Common.CurrentVersion.targets" from project "C:\BuildError\B\B.csproj" (target "ResolveReferences" depends on it):
Task "MSBuild" skipped, due to false condition; ('%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and ('$(BuildingInsideVisualStudio)' == 'true' or '$(BuildProjectReferences)' != 'true') and '$(VisualStudioVersion)' != '10.0' and '@(_MSBuildProjectReferenceExistent)' != '') was evaluated as ('false' == 'true' and '..\A\A.csproj' != '' and ('' == 'true' or 'true' != 'true') and '14.0' != '10.0' and '..\A\A.csproj' != '').
Task "MSBuild" skipped, due to false condition; ('%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and ('$(BuildingInsideVisualStudio)' == 'true' or '$(BuildProjectReferences)' != 'true') and '$(VisualStudioVersion)' == '10.0' and '@(_MSBuildProjectReferenceExistent)' != '') was evaluated as ('false' == 'true' and '..\A\A.csproj' != '' and ('' == 'true' or 'true' != 'true') and '14.0' == '10.0' and '..\A\A.csproj' != '').
Task "MSBuild" skipped, due to false condition; ('%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and '$(BuildingInsideVisualStudio)' != 'true' and '$(BuildProjectReferences)' == 'true' and '@(_MSBuildProjectReferenceExistent)' != '') was evaluated as ('false' == 'true' and '..\A\A.csproj' != '' and '' != 'true' and 'true' == 'true' and '..\A\A.csproj' != '').
Task "MSBuild" skipped, due to false condition; ('%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and '$(BuildingProject)' == 'true' and '@(_MSBuildProjectReferenceExistent)' != '') was evaluated as ('false' == 'true' and '..\A\A.csproj' != '' and 'true' == 'true' and '..\A\A.csproj' != '').
Task "Warning" skipped, due to false condition; ('@(ProjectReferenceWithConfiguration)' != '' and '@(_MSBuildProjectReferenceNonexistent)' != '') was evaluated as ('..\A\A.csproj' != '' and '' != '').
Done building target "ResolveProjectReferences" in project "B.csproj".: (TargetId:83)
Because the project has metadata BuildReference=false from:
Target "AssignProjectConfiguration: (TargetId:81)" in file "C:\Program Files (x86)\MSBuild\14.0\bin\Microsoft.Common.CurrentVersion.targets" from project "C:\BuildError\B\B.csproj" (target "ResolveReferences" depends on it):
Set Property: OnlyReferenceAndBuildProjectsEnabledInSolutionConfiguration=true
Set Property: ShouldUnsetParentConfigurationAndPlatform=true
Set Property: AddSyntheticProjectReferencesForSolutionDependencies=true
Task "AssignProjectConfiguration" (TaskId:47)
Task Parameter:
ProjectReferences=
..\A\A.csproj
Name=A
OutputItemType=
Project={df198bff-9ad4-45bd-8152-d92259567466}
ReferenceSourceTarget=ProjectReference
Targets= (TaskId:47)
Task Parameter:CurrentProject=C:\BuildError\B\B.csproj (TaskId:47)
Task Parameter:CurrentProjectConfiguration=Debug (TaskId:47)
Task Parameter:CurrentProjectPlatform=AnyCPU (TaskId:47)
Task Parameter:OutputType=Exe (TaskId:47)
Task Parameter:ResolveConfigurationPlatformUsingMappings=False (TaskId:47)
Task Parameter:SolutionConfigurationContents=<SolutionConfiguration>
<ProjectConfiguration Project="{DF198BFF-9AD4-45BD-8152-D92259567466}" AbsolutePath="C:\BuildError\A\A.csproj" BuildProjectInSolution="False">Debug|AnyCPU</ProjectConfiguration>
<ProjectConfiguration Project="{0D8A39A6-7DEF-45D4-86E6-41E1FA9BCC8C}" AbsolutePath="C:\BuildError\B\B.csproj" BuildProjectInSolution="True">Debug|AnyCPU</ProjectConfiguration>
</SolutionConfiguration> (TaskId:47)
Task Parameter:AddSyntheticProjectReferencesForSolutionDependencies=True (TaskId:47)
Task Parameter:OnlyReferenceAndBuildProjectsEnabledInSolutionConfiguration=True (TaskId:47)
Task Parameter:ShouldUnsetParentConfigurationAndPlatform=True (TaskId:47)
Project reference "..\A\A.csproj" has been assigned the "Debug|AnyCPU" configuration. (TaskId:47)
Output Item(s):
_ProjectReferenceWithConfiguration=
..\A\A.csproj
BuildReference=false
Configuration=Debug
FullConfiguration=Debug|AnyCPU
Name=A
OutputItemType=
Platform=AnyCPU
Project={df198bff-9ad4-45bd-8152-d92259567466}
ReferenceOutputAssembly=false
ReferenceSourceTarget=ProjectReference
SetConfiguration=Configuration=Debug
SetPlatform=Platform=AnyCPU
Targets= (TaskId:47)
Output Item(s):
ProjectReferenceWithConfiguration=
..\A\A.csproj
BuildReference=false
Configuration=Debug
FullConfiguration=Debug|AnyCPU
Name=A
OutputItemType=
Platform=AnyCPU
Project={df198bff-9ad4-45bd-8152-d92259567466}
ReferenceOutputAssembly=false
ReferenceSourceTarget=ProjectReference
SetConfiguration=Configuration=Debug
SetPlatform=Platform=AnyCPU
Targets= (TaskId:47)
Done executing task "AssignProjectConfiguration". (TaskId:47)
Done building target "AssignProjectConfiguration" in project "B.csproj".: (TargetId:81)
That's set here.
Setting MSBUILDEMITSOLUTION=1, gives this in the .sln.metaproj:
<CurrentSolutionConfigurationContents>
<SolutionConfiguration xmlns="">
<ProjectConfiguration Project="{DF198BFF-9AD4-45BD-8152-D92259567466}" AbsolutePath="C:\BuildError\A\A.csproj" BuildProjectInSolution="False">Debug|AnyCPU</ProjectConfiguration>
<ProjectConfiguration Project="{0D8A39A6-7DEF-45D4-86E6-41E1FA9BCC8C}" AbsolutePath="C:\BuildError\B\B.csproj" BuildProjectInSolution="True">Debug|AnyCPU</ProjectConfiguration>
</SolutionConfiguration>
</CurrentSolutionConfigurationContents>
So I think the culprit is that BuildProjectInSolution="False" is interpreted a bit too strictly.
I don't know yet what the best fix would be. Maybe stop checking that condition for the GetTargetPath invocation? But we'd need to think through the implications of that in detail.
Thanks for taking the time to analyze the issue and respond. I麓m aware that VS uses msbuild itself behind the curtains.
Is there a way for me to build my solutions mimicking how VS invokes msbuild? This is for my build script that I'm going to use in a CI server. That may be good enough for me until this is fixed.
Thanks.
I don't think there's a straightforward way to do that. VS invokes build for each project individually by logic that it controls. MSBuild attempts to replicate this logic when building a solution by generating .metaproj projects. Since this looks like a case where the logic isn't well-replicated, there's no easy way to get the .sln build working.
There are a few options that I can think of:
.proj file to build on your CI server, rather than the .sln? That keeps the logic of what to build entirely in MSBuild and is usually more understandable. But of course it can cause drift between what people do on their desktops (building with the .sln in VS) and what the "official" build does.<Reference>, rather than using ProjectReferences. That is doable but can be tricky to get right.devenv.exe /build instead of MSBuild directly. That should ensure that you're getting the VS logic when deciding what to build.Thanks for sharing your thoughts. It helped me understand a little more how things work under the hood.
I think I'm going to re-enable building theses projects (A.csproj in my sample) in the solutions that require them. I have already tried some of your suggestions. I'll share my experience bellow, just in case someone else finds this thread, and wants to know other users experience.
A.dll when working in SolAandB.sln. When adding the reference I would have something like:xml
<Reference Include="A">
<HintPath>..\A\$(Configuration)\A.dll</HintPath>
<Private>False</Private>
</Reference>
Note the hint path, that uses $(Configuration) to target the appropriate build output depending on the configuration. The only deal breaker of this approach, is that we have some custom build configurations (e.g. Release_Trial for a trial version), and not all projects are built in this configuration, but rather when building the solution in that configuration, the projects that don't need nothing special in Release_Trial are just built with the Release configuration, and so the hint path ..\A\Release_Trial\A.dll may not always be valid. VS and msbuild already solve this mess for us if we stay with <ProjectReference> and have the configuration mappings defined in the solution file.
BuildSolutions.proj. Please correct me if that's not the intended way to build the solutions from a unique proj file.In the long run, we麓re probably going to transition to nuget dependencies, and instead of having a handful solutions with lots of projects, have lots of small solutions that build and publish nuget packages into a internal server, and other projects get the required packages from there. In the mean time, I'll re-enable building the disabled projects in the solution.
Has there been any progress with this issue? I am seeing the same behavior using MSBuild 15.1.1012.6693
We are using build configurations to control the order of what we want built from out of our solution via the command line (i.e. abstractions before implementations) and the project references simply do not get built/included. I've even made sure that the project dependencies are properly configured in the sln file.
It was mentioned earlier in the thread that: BuildProjectInSolution="False" possibly was the culprit.
Same issue for me, but solved checking build column of desired project in Configuration Manager.
I have similar issue, I want to set a Solution configuration which does not have a project configuration (I use only as a project filter), but the MSBuild does not recognize and ended up building all the projects. Right now, I am using devenv.exe to build the solution with the desired "filter".
I have the opposite problem: we want to enforce that all ProjectReferences are built as part of the solution, so we have implemented a target that enforces '%(ProjectReferenceWithConfiguration.BuildReference)' == 'true'.
MSBuild sets BuildProjectInSolution=false explicitly, which causes BuildReference to be set false and makes this validation logic work as expected. VS IDE leaves this property undefined, the AssignProjectConfiguration target in Microsoft.Common.CurrentVersion.Targets defaults an undefined BuildReference property to true, our validation encounters a false negative, and our build breaks with more esoteric errors from unresolved references in code.
We would prefer that MSBuild & VS IDE have matching behavior in that _both_ should set BuildProjectInSolution=false on the ProjectConfiguration when a project is not configured to build in the current solution configuration.
Our solution to the problem of "I want project A to build as part of solution 1, then project B to build as part of solution 2" is for project B to use a Reference to A.dll instead of a ProjectReference to A.csproj. We still include project A in solution 2 for easy reference at coding time, but disable its Build in all solution configurations.
Most helpful comment
Has there been any progress with this issue? I am seeing the same behavior using MSBuild 15.1.1012.6693
We are using build configurations to control the order of what we want built from out of our solution via the command line (i.e. abstractions before implementations) and the project references simply do not get built/included. I've even made sure that the project dependencies are properly configured in the sln file.
It was mentioned earlier in the thread that: BuildProjectInSolution="False" possibly was the culprit.