【问题标题】:MSBuild 4.5 is ignoring project dependenciesMSBuild 4.5 忽略项目依赖项
【发布时间】:2012-12-06 21:10:22
【问题描述】:

我有一个 VS2010 解决方案文件,其中包含 20 多个项目,其中一些项目依赖于解决方案中的其他项目。

我还为不同的目的设置了多个构建配置,但我已经缩减了要构建的项目,只包含最少数量的项目。

例如,我有三个库(A、B 和 C)、一个网站项目和一个网站部署项目(VS2010)。该网站引用了库 A 和 B,而 B 又引用了 C。我的构建配置仅检查了网站和部署项目。当我从解决方案的属性中检查项目依赖项时,网站正确列出了库,并且 B 显示 C,如预期的那样。

当我在 VS2010 中针对我的构建配置运行构建时,它完全可以正常工作,但是当我在指定我的配置的解决方案上运行 MSBuild 时(如下所示),它只会导致一堆错误。

msbuild.exe mysolution.sln /t:Build /p:configuration=MyConfig

这是我得到的错误示例:

Services\IService.cs(11,63): error CS0246: The type or namespace name 'Priority' could not be found (are you missing a using directive or an assembly reference?)

我注意到在我的构建服务器 (TeamCity v7.1.2) 上发生了这种情况,但我可以在多台机器上重现它,我将其缩小到 MSBuild 本身的问题。

它只是在我安装了 .NET 4.5(和 2 个安全补丁)后才开始发生,所以我卸载了它,重新安装了 .NET 4.0(带补丁),因为它也被删除了,然后尝试了相同的命令,它工作得很好.

这让我相信在使用 .NET 4.5 的 MSBuild 中发生了某些变化或损坏,但他们的文档中似乎没有提到这种变化。

MSBuild 4.5 文档:http://msdn.microsoft.com/en-us/library/hh162058.aspx

我什至尝试将 BuildProjectDependencies=true 传递给 MSBuild,但它指出它跳过了其他项目,因为它们没有在配置管理器中被选中,这是正确且有意的。

我让它与 MSBuild 4.5 一起工作的唯一方法是返回并选择被跳过的项目,但由于依赖链的实际解决方案有点复杂,我不想必须每次我们使用新项目或依赖项更新解决方案时,尝试手动管理配置。它应该是自动的。

对我正在做的事情有什么想法吗?

【问题讨论】:

    标签: msbuild .net-4.5


    【解决方案1】:

    如果你还没有解决这个问题,让我们试试盲拍:

    1. Msbuild 已确认错误,它有时会根据所遵循的依赖项生成错误的构建顺序。尝试构建的不是整个解决方案,而是您想要构建的确切项目 - 例如msbuild.exe YourWebsiteProject.csproj /t:Clean;Build /p:configuration=MyConfig。问题还存在吗?

    2. 确保 YourWebsiteProject 和您的库(B 和 C)有正确的引用 - 在项目上,而不是在另一个项目文件夹中的 dll 上(解决这个问题的最简单方法 - 从 B 中删除对 C 的引用并重新添加它,只需确保您正在添加项目引用而不是浏览到 bin\Debug 以获得非常 C.dll)。问题还在吗?

    如果您可以提供详细甚至诊断性的 msbuild 日志(在开关 /ds /v:diag 之后添加到您的 msbuild 命令行,然后在某处共享 teamcity 完整构建日志或将命令行日志通过管道传输到文件)或一些示例项目设置在我可以重现这种行为 - 这对解决问题很有帮助。

    【讨论】:

    【解决方案2】:

    我想我会更新我之前的答案,因为我花了很多时间和精力来创建自己的解决方法来解决这个问题。解决方法比简单地解决问题要全面一些,但我已尝试既消除问题又使自己免受未来此类冲击的影响。

    MSBuild 已被降级为使用解决方案、配置或其他方式。 MSBuild 只是被要求单独编译项目。发生这种情况的顺序由 Powershell 脚本计算,该脚本解析我们的解决方案和项目,以制定出最佳的即时构建执行计划。

    关键(我认为您可能会发现有帮助)是以下 sn-ps:

    确定我的解决方案

    我有一个我平台中所有解决方案的列表,我基本上对每个解决方案进行了迭代。

    $buildPlan = (
    @{
        solutions = (
            @{
                name      = "DataStorage"
                namespace = "Platform.Databases"
            },
            @{
                name      = "CoreFramework"
            },
            @{
                namespace = "Platform.Server"
                name      = "Application1"
            },
            @{
                namespace = "Platform.Server"
                name      = "Application2"
            },
            @{
                namespace = "Platform.Client"
                name      = "Application1"
            }
         )
    })
    

    我有一些逻辑可以帮助将其转化为实际的物理路径,但它非常符合我们的需求,所以我不会在这里列出。可以说,从这个列表中,我可以找到我需要解析的 .sln 文件。

    解析项目的解决方案文件

    对于每个解决方案,我都会阅读 .sln 文件并尝试提取其中包含的所有项目,我将在以后需要构建这些项目。

    首先,确定我的所有项目

    $solutionContent = Get-Content $solutionFile
    
    $buildConfigurations += Get-Content $solutionFile | Select-String  "{([a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12})}\.(Release.*)\|Any CPU\.Build" | % {
            New-Object PSObject -Property @{
                Name = $_.matches[0].groups[3].value.replace("Release ","");
                Guid = $_.matches[0].groups[1].value
              }
    
        }  | Sort-Object Name,Guid -unique
    

    然后将其转化为我可以在以后迭代的项目列表。

    $projectDefinitions = $solutionContent | 
          Select-String 'Project\(' |
            ForEach-Object {
              $projectParts = $_ -Split '[,=]' | ForEach-Object { $_.Trim('[ "{}]') };
              $configs = ($buildConfigurations | where  {$_.Guid -eq $projectParts[3]} | Select-Object Name)
    
              foreach ($config in $configs)
              {
                  $santisiedConfig = if ([string]::IsNullOrEmpty($config.Name)){"Release"}else{$config.Name}
                  if ($projectParts[1] -match "OurCompanyPrefix.")
                  {
                      New-Object PSObject -Property @{
                        Name = $projectParts[1];
                        File = $projectParts[2];
                        Guid = $projectParts[3];
                        Config =  $santisiedConfig
                      }
                  }
              }
        } 
    

    加载 Visual Studio 项目

    从我对解决方案文件的解析中,我现在有了每个解决方案的项目列表,其中关键包含从解决方案根目录中找到项目的相对文件路径。

    $projectDefinition = [xml](Get-Content $csProjectFileName)
    $ns = @{ e = "http://schemas.microsoft.com/developer/msbuild/2003" }
    $references = @();
    

    1) 识别外部项目参考

    $references += Select-Xml -Xml $projectDefinition -XPath "//e:Project/e:ItemGroup/e:Reference" -Namespace $ns | % {$_.Node} | where {$_.Include -match "OurCompanyPrefix" -and $_.HintPath -notmatch "packages"}  | % {$_.Include}
    

    2) 识别内部项目参考

    $references += Select-Xml -Xml $projectDefinition -XPath "//e:Project/e:ItemGroup/e:ProjectReference" -Namespace $ns | % { $_.Node.Name }
    

    3) 将“构建后”事件作为外部参考

    $references += Select-Xml -Xml $projectDefinition -XPath "//e:Project/e:PropertyGroup/e:PostBuildEvent" -Namespace $ns | where {(!([String]::IsNullOrEmpty($_.Node.InnerText)))} | % {
    
                $postBuildEvents = $_.Node.InnerText.Split("`n")
                $projectsReferencedInPostBuildEvents = $postBuildEvents | Select-String "\(SolutionDir\)((\w|\.)*)" | % {$_.Matches[0].Groups[1].Value}
                if ($projectsReferencedInPostBuildEvents -ne $null)
                {
                    Write-Output $projectsReferencedInPostBuildEvents | % { $matchedProject = $_; ($releaseConfiguation | ? {$_.File -match $matchedProject}).Name }  
                }
    
            }
    

    而且,既然我们已经做到了,那么还要获取一些基本的输出信息

    在迭代我要构建的项目列表时,这很方便,因为知道将输出推送到哪里,或者在哪里可以找到依赖项的输出。

    $assemblyName = (Select-Xml -Xml $projectDefinition -XPath "//e:Project/e:PropertyGroup/e:AssemblyName" -Namespace $ns).Node.InnerText
    $outputPath  = (Select-Xml -Xml $projectDefinition -XPath "//e:Project/e:PropertyGroup[contains(@Condition,'Release|')]/e:OutputPath" -Namespace $ns).Node.InnerText
    

    最后

    我们只需要确保我们没有任何重复项,所以我只记录这个特定代码项目的不同依赖项:

    $dependendents = @();
    if ($references -ne $null)
    {
        $buildAction.project.dependencies += $references | where {(!([string]::IsNullOrEmpty($_))) -and ($_ -match "OurCompanyPrefix\.(.*)")} | % { $_.ToLower()} | Select -unique
    }    
    

    我希望这可以为您提供足够的信息来解析您的 SLN 和 PROJ 文件。我认为您将如何选择捕获和存储这些信息完全取决于您。

    我正在写一篇关于此的相当深入的博客文章,其中将包含我上面没有提到的所有修饰和框架。这篇文章还没有准备好,但我会从之前的一篇文章中链接到它:http://automagik.piximo.me/2013/02/just-in-time-compilation.html - 因为微软的这个改变几乎让这项工作脱轨了!

    干杯。

    【讨论】:

      【解决方案3】:

      在 .NET 4.5 中,C:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets 中 OnlyReferenceAndBuildProjectsEnabledInSolutionConfiguration 属性的默认值从 false 更改为 true。顾名思义,此属性会导致 MSBuild 忽略对从构建配置中排除的项目的引用。

      (Microsoft 做出此更改可能是为了响应 Connect bug I filed against MSBuild 4.0,尽管我警告过他们此更改会破坏构建。)

      解决方法很简单:在每个项目的第一个 <PropertyGroup> 部分中将属性设置回 false

      <PropertyGroup>
          ...
          <OnlyReferenceAndBuildProjectsEnabledInSolutionConfiguration>false</OnlyReferenceAndBuildProjectsEnabledInSolutionConfiguration>
      </PropertyGroup>
      

      【讨论】:

      • 由于某种原因,我的 Connect 错误的链接现在已损坏。必应仍有cached version
      【解决方案4】:

      我遇到了完全相同的问题。我设法找到的两种变通方法从长远来看都是不可接受的,但它们确实克服了最初的问题,即我们的构建过程与新的 4.5 堆栈一起工作时变老了。

      1. 用文件引用交换项目引用
      2. 创建复合构建配置

      我选择了 #2,因为文件引用意味着开发人员会失去实时智能感知等功能。

      复合配置是这样的:

      • 发布服务器 -> 所有服务器项目
      • 发布消费者 -> “发布服务器” + 客户端项目

      问题似乎是,如果一个项目未包含在当前/活动的构建配置中,它不会将其作为引用的依赖项包含在内。因此,通过将依赖项添加到配置中,项目至少可以编译。

      都很丑,但至少它让我摆脱了困境。

      马特

      【讨论】:

        【解决方案5】:

        我感觉这个问题有多种原因。我在这里尝试了大多数解决方案。我无法更改我们的构建服务器以使用 PS 脚本,因此该解决方案已失效。没有什么我可以尝试的。

        最后,我删除了我的解决方案并开始了一个新的解决方案。新的解决方案奏效了。在将损坏的解决方案与工作解决方案进行比较后,我发现原始解决方案缺少行。每个无法编译的依赖项都缺少这一行:

        {A93FB559-F0DB-4F4D-9569-E676F59D6168}.Release|Any CPU.Build.0 = Release|Any CPU
        

        注意 1:GUID 会从依赖项变为依赖项。

        注意 2:您可以在解决方案文件的“GlobalSection(ProjectConfigurationPlatforms) = postSolution”部分下找到类似这样的行。

        我的 build.proj 文件显示使用“Any CPU”平台构建“Release”。因为 MSBuild 找不到这一行,所以它没有构建这个依赖项。这会导致“错误 CS0246:找不到类型或命名空间”消息。

        如果您好奇,有人将此解决方案设置为“x86”平台(这对我们来说是错误的)。我将其更改为“任何 CPU”(以及其他一些更改)。 Visual Studio 没有将相应的行添加到解决方案文件中。在 IDE 中一切正常,但 MSBuild 开始抛出错误。

        【讨论】:

          【解决方案6】:

          我发现 MSBuild 是按照在 .sln 文件中声明的顺序从解决方案构建项目的。因此,如果您使用文本编辑器重新排序它们,您可以修复 MSBuild 的顺序。

          【讨论】:

            猜你喜欢
            • 2013-06-13
            • 1970-01-01
            • 2011-04-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多