We have a solution with all standard .NET 3.5 projects (Silverlight 3, RIA Services enabled, Enterprise Lib projects). The solution builds without any problems inside Visual Studio, however when building from the command line with MSBuild (MSBuild MySolution.sln), it fails to build.
We get the following errors:
error CS0246: The type or namespace name 'ICategory' could not be found (are you missing a using directive or an assembly reference?)
... and more of these (on 4 Silverlight projects in total).
However, these projects contain the correct project references (that's why it's also building inside VS).
After days of trying to figure out what happens in the core msbuild files (Microsoft.Common.Targets) and the MSBuild log files, I discovered the following:
... in the log file of MSBuild:
Task "AssignProjectConfiguration"
Project reference "..\IntraCityNet.Sample.Framework\IntraCityNet.Sample.Framework.csproj" has been assigned the "Debug|AnyCPU" configuration.
Project reference "..\Yuse.Framework.Silverlight\Yuse.Framework.Silverlight.csproj" has been assigned the "Debug|AnyCPU" configuration.
Done executing task "AssignProjectConfiguration".
... so the references for this project have been correctly identified by MSBuild
on the following lines:
Done building target "SplitProjectReferencesByType" in project "IntraCityNet.Sample.Module.PostIt.Context.csproj".
Target "SplitProjectReferencesByType" skipped. Previously built successfully.
Target "_SplitProjectReferencesByFileExistence" skipped. Previously built successfully.
and again a bit further, he starts compiling the project:
Task "Csc"
Command:
c:\WINDOWS\Microsoft.NET\Framework\v3.5\Csc.exe /noconfig /nowarn:1701,1702 /nostdlib+ /errorreport:prompt /warn:4 /define:DEBUG;TRACE;SILVERLIGHT /reference:..\Lib\Silverlight\mscorlib.dll /reference:..\Lib\Silverlight\System.ComponentModel.DataAnnotations.dll /reference:..\Lib\Silverlight\System.Core.dll /reference:..\Lib\Silverlight\system.dll /reference:..\Lib\Silverlight\System.Net.dll /reference:..\Lib\Silverlight\System.Runtime.Serialization.dll /reference:..\Lib\Silverlight\System.Windows.Browser.dll /reference:..\Lib\Silverlight\System.Windows.dll /reference:..\Lib\Ria\System.Windows.Ria.dll /reference:..\Lib\Silverlight\System.Xml.dll /debug+ /debug:full /optimize- /out:obj\Debug\IntraCityNet.Sample.Module.PostIt.Context.dll /target:library Category.cs CategorySummary.cs Generated_Code\IntraCityNet.Sample.Web.g.cs PostItCategory.cs Properties\AssemblyInfo.cs c:\B\MyJames\CI_MyJames\Sources\Source\IntraCityNet.Sample.Module.PostIt.Context\Generated_Code\IntraCityNet.Sample.Web.g.cs
The "Csc" task is using "Csc.exe" from "c:\WINDOWS\Microsoft.NET\Framework\v3.5\Csc.exe".
Microsoft (R) Visual C# 2008 Compiler version 3.5.30729.1
for Microsoft (R) .NET Framework version 3.5
Copyright (C) Microsoft Corporation. All rights reserved.
CSC : warning CS2002: Source file 'c:\B\MyJames\CI_MyJames\Sources\Source\IntraCityNet.Sample.Module.PostIt.Context\Generated_Code\IntraCityNet.Sample.Web.g.cs' specified multiple times
Category.cs(5,36): error CS0246: The type or namespace name 'ICategory' could not be found (are you missing a using directive or an assembly reference?)
CategorySummary.cs(5,44): error CS0246: The type or namespace name 'ICategorySummary' could not be found (are you missing a using directive or an assembly reference?)
PostItCategory.cs(5,42): error CS0246: The type or namespace name 'IItemCategory' could not be found (are you missing a using directive or an assembly reference?)
The compilation error is normal, as MSBuild decided not to add the (needed) project references to the csc command as paramaters. The question is ... why did MSBuild decide to do that?
I think it is suspicious that MSBuild decided to skip the target "_SplitProjectReferencesByFilExistence" ("Previously built successfully."). I searched in the log where this target was previously called for this project:
Project "c:\B\MyJames\CI_MyJames\Sources\Source\IntraCityNet.Sample.Module.PostIt\IntraCityNet.Sample.Module.PostIt.csproj" (31:2) is building "c:\B\MyJames\CI_MyJames\Sources\Source\IntraCityNet.Sample.Module.PostIt.Context\IntraCityNet.Sample.Module.PostIt.Context.csproj" (32:7) on node 0 (GetCopyToOutputDirectoryItems target(s)).
...
Target "_SplitProjectReferencesByFileExistence" in file "c:\WINDOWS\Microsoft.NET\Framework\v3.5\Microsoft.Common.targets" from project "c:\B\MyJames\CI_MyJames\Sources\Source\IntraCityNet.Sample.Module.PostIt.Context\IntraCityNet.Sample.Module.PostIt.Context.csproj":
So the _SplitProjectReferenceByFileExistence target was last called for this project from the GetCopyToOutputDirectoryItems target.
I think that at that time, the _MSBuildProjectReferenceExistent item collection is being "cleared".
A bit later, MSBuild decides to build/compile (default target) the project again, but (as I described above) because the SplitProjectReferenceByFileExistence target was already called, it decides to skip it ... and finally passes to the csc command the empty _MSBuildProjectReferenceExistent list.
... and then we get the compilation error.
I managed to implement a temporary workaround by overriding some of the targets with my "CustomAfterMicrosoftCommon.targets" file, listed below:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--
============================================================
_YuseSplitProjectReferencesByFileExistence
Split referenced projects into two lists: those that exist on
disk and those that don't.
+ Yuse patch: this target is always called except when executing target GetCopyToOutputDirectoryItems (then the original _SplitProjectReferencesByFileExistence gets called)
============================================================
-->
<Target
Name="_YuseSplitProjectReferencesByFileExistence">
<Message Text="YUSE (_YuseSplitProjectReferencesByFileExistence): in _YuseSplitProjectReferencesByFileExistence" />
<!--
Use this task for matching projects with pre-resolved project outputs set by the IDE
if building inside the IDE. The IDE only includes non-MSBuild projects in the output list. We'll
use MSBuild to resolve MSBuild projects.
This task will resolve VSIP (3rd party) project references and create a new item list with only project references
to projects in the MSBuild format.
-->
<ResolveNonMSBuildProjectOutput
ProjectReferences="@(NonVCProjectReference)"
PreresolvedProjectOutputs="$(VSIDEResolvedNonMSBuildProjectOutputs)"
Condition="'$(BuildingInsideVisualStudio)'=='true' and '@(NonVCProjectReference)'!=''">
<Output TaskParameter="ResolvedOutputPaths" ItemName="_ResolvedProjectReferencePaths"/>
<Output TaskParameter="UnresolvedProjectReferences" ItemName="_MSBuildProjectReference"/>
</ResolveNonMSBuildProjectOutput>
<!--
If building from the command line, simply copy the NonVCProjectReference item list to _MSBuildProjectReference,
since we have to assume all non-VC projects are in the MSBuild format. We have no way of building
VSIP (3rd party) projects from the command line.
-->
<ItemGroup>
<_MSBuildProjectReference Include="@(NonVCProjectReference)" Condition="'$(BuildingInsideVisualStudio)'!='true' and '@(NonVCProjectReference)'!=''"/>
</ItemGroup>
<!-- Break the project list into two lists: those that exist on disk and those that don't. -->
<ItemGroup>
<_MSBuildProjectReferenceExistent Include="@(_MSBuildProjectReference)" Condition="Exists('%(Identity)')"/>
<_MSBuildProjectReferenceNonexistent Include="@(_MSBuildProjectReference)" Condition="!Exists('%(Identity)')"/>
</ItemGroup>
<Message Text="YUSE (_YuseSplitProjectReferencesByFileExistence): _MSBuildProjectReferenceExistent=@(_MSBuildProjectReferenceExistent)" />
<Message Text="YUSE (_YuseSplitProjectReferencesByFileExistence): _MSBuildProjectReference=@(_MSBuildProjectReference)" />
</Target>
<Target
Name="CleanReferencedProjects"
DependsOnTargets="SplitProjectReferencesByType; _YuseSplitProjectReferencesByFileExistence">
<!--
When building the project directly from the command-line, clean those referenced projects
that exist on disk. For IDE builds and command-line .SLN builds, the solution build manager
takes care of this.
-->
<MSBuild
Projects="@(_MSBuildProjectReferenceExistent)"
Targets="Clean"
BuildInParallel="$(BuildInParallel)"
UnloadProjectsOnCompletion="$(UnloadProjectsOnCompletion)"
Condition="'$(BuildingInsideVisualStudio)' != 'true' and '$(BuildingSolutionFile)' != 'true' and '$(BuildProjectReferences)' == 'true' and '@(_MSBuildProjectReferenceExistent)' != ''" />
</Target>
<Target
Name="ResolveProjectReferences"
DependsOnTargets="SplitProjectReferencesByType;_YuseSplitProjectReferencesByFileExistence">
<!--
When building this project from the IDE or when building a .SLN from the command-line,
just gather the referenced build outputs. The code that builds the .SLN will already have
built the project, so there's no need to do it again here.
The ContinueOnError setting is here so that, during project load, as
much information as possible will be passed to the compilers.
-->
<Message Text="YUSE (_MSBuildProjectReferenceExistent): _MSBuildProjectReferenceExistent=@(_MSBuildProjectReferenceExistent)" />
<MSBuild
Projects="@(_MSBuildProjectReferenceExistent)"
Targets="GetTargetPath"
BuildInParallel="$(BuildInParallel)"
UnloadProjectsOnCompletion="$(UnloadProjectsOnCompletion)"
Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform)"
Condition="'@(NonVCProjectReference)'!='' and ('$(BuildingSolutionFile)' == 'true' or '$(BuildingInsideVisualStudio)' == 'true' or '$(BuildProjectReferences)' != 'true') and '@(_MSBuildProjectReferenceExistent)' != ''"
ContinueOnError="!$(BuildingProject)">
<Output TaskParameter="TargetOutputs" ItemName="_ResolvedProjectReferencePaths"/>
</MSBuild>
<!--
Build referenced projects when building from the command line.
The $(ProjectReferenceBuildTargets) will normally be blank so that the project's default
target is used during a P2P reference. However if a custom build process requires that
the referenced project has a different target to build it can be specified.
-->
<MSBuild
Projects="@(_MSBuildProjectReferenceExistent)"
Targets="$(ProjectReferenceBuildTargets)"
BuildInParallel="$(BuildInParallel)"
UnloadProjectsOnCompletion="$(UnloadProjectsOnCompletion)"
Condition="'@(NonVCProjectReference)'!='' and '$(BuildingInsideVisualStudio)' != 'true' and '$(BuildingSolutionFile)' != 'true' and '$(BuildProjectReferences)' == 'true' and '@(_MSBuildProjectReferenceExistent)' != ''">
<Output TaskParameter="TargetOutputs" ItemName="_ResolvedProjectReferencePaths"/>
</MSBuild>
<!--
Get manifest items from the (non-exe) built project references (to feed them into ResolveNativeReference).
-->
<MSBuild
Projects="@(_MSBuildProjectReferenceExistent)"
Targets="GetNativeManifest"
BuildInParallel="$(BuildInParallel)"
UnloadProjectsOnCompletion="$(UnloadProjectsOnCompletion)"
Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform)"
Condition="'@(NonVCProjectReference)'!='' and '$(BuildingProject)'=='true' and '@(_MSBuildProjectReferenceExistent)'!=''">
<Output TaskParameter="TargetOutputs" ItemName="NativeReference"/>
</MSBuild>
<!-- Issue a warning for each non-existent project. -->
<Warning
Text="The referenced project '%(_MSBuildProjectReferenceNonexistent.Identity)' does not exist."
Condition="'@(NonVCProjectReference)'!='' and '@(_MSBuildProjectReferenceNonexistent)'!=''"/>
</Target>
</Project>
Actually with this workaround I make sure that when building the project, the _YuseSplitProjectReferencesByFileExistence target (which is just an exact copy of the original _SplitProjectReferencesByFileExistence ... only with a different name) gets always called.
With this temp fix, my solution builds fine in MSBuild. But of course I'm looking for a cleaner and more correct solution and explanation for the problem.
According to me, MSBuild is somehow not building the projects in the solution in the right order ... e.g. the 4 projects that fail to compile all get compiled twice. The first time successfully (then the compilation is triggered by another project in the solution that is referencing this project). After that it copies the binaries to the output location of the project ... and many steps (and compilations) later ... MSBuild decides to build the project again (although it was already compiled succesfully), this time the compilation is triggered from the solution ... and the compilation fails. I do not understand why it decides to try to compile the project again.
One more finding: when I use MSBuild to compile each of the projects separately from the command line, all projects compile successfully.