Open fedeazzato opened 8 years ago
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 ProjectReference
s. 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.
We tried this for a while, and works great. The main drawback is not being able to see the source of A.dll
when working in SolAandB.sln
. When adding the reference I would have something like:
<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.
The thing is that we have some C++ projects referencing C# projects, and the other way around too. The C++ projects with references to C#, are not so smart to not rebuild themselves, as they apparently have a difficult time deciding if the referenced C# project changed or not (I see warnings in output of some metadata cache failure), and that triggers the C++ project build again. And as a consequence of that, C# projects that reference those C++ projects are also built again.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.
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
andSolAandB.sln
.SolA.sln
only contains projectA.csproj
whileSolAandB.sln
contains bothA.csproj
andB.csproj
. The projectA.csproj
is just a class library, whileB.csproj
is a command line project which references projectA.csproj
. The subtle peculiarity about this projects, is thatA.csproj
is configured to not be built inSolAandB.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 usingVS2015
with update 1, but don't believe that changes anything), I can see that the invocation tocsc.exe
forB.csproj
is as follows (I've added line breaks between arguments for clarity):Note the
/reference:"F:\Visual Studio 2015\Projects\BuildError\A\bin\Debug\A.dll"
which points correctly to the output ofA.csproj
for the selected build configuration. If I had built inRelease
, the reference would have correctly pointed to the release build output ofA.csproj
.The result of that build depends on having
A.csproj
already built, which I have. You can see a sample build script inBuildSolutions.proj
which first buildsSolA.sln
and thenSolAandB.sln
.Now if I build
SolAandB.sln
through the command line, or as a result of buildingBuildSolutions.proj
, I can see that the invocation tocsc.exe
forB.csproj
is as follows: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 errorerror 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?