【问题标题】:Correctly setting indirect references in MSBUILD在 MSBUILD 中正确设置间接引用
【发布时间】:2018-02-06 03:47:34
【问题描述】:

我一直让构建团队处理构建定义。由于一些限制,我现在不得不咬紧牙关,并且对 MSBUILD 如何处理 XML 构建定义没有太多线索。一些见解/帮助将不胜感激。

经过研究,我发现这是一个常见问题,很少有文档解决方案。在一个复杂的应用程序中(我们有超过 50 个“.csproj”项目作为一个应用程序一起工作)你会发现顶级项目(web 应用程序、web api、win 服务等)引用了中间层项目(实用程序、基础设施、核心、日志记录等),而这些又引用了 3rd 方 DLL。在完整构建期间,这些第 3 方引用永远不会进入 BIN 文件夹。

所以,事不宜迟,让我们开始尝试使这个构建定义工作。我的递归尝试来自这篇文章:Recursively Copying Indirect Project Dependencies in MSBuild

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="Build">
    <PropertyGroup>
        <VsVersion>12.0</VsVersion>
        <VsVersion Condition="'$(VS110COMNTOOLS)' != ''">11.0</VsVersion>
        <VsVersion Condition="'$(VS120COMNTOOLS)' != ''">12.0</VsVersion>
        <VsVersion Condition="'$(VS140COMNTOOLS)' != ''">14.0</VsVersion>
        <VisualStudioVersion>$(VsVersion)</VisualStudioVersion>
        <SourceDir Condition="'$(SourceDir)' == ''">..</SourceDir>
        <IncludeTest Condition="'$(IncludeTest)' == ''">True</IncludeTest>
        <DeployDatabases Condition="'$(DeployDatabases)' == ''">False</DeployDatabases>
        <RecreateDatabases Condition="'$(RecreateDatabases)' == ''">False</RecreateDatabases>
    </PropertyGroup>
    <ItemGroup Label="Business">
        <BusinessProjects Include="$(SourceDir)\Data Access\**\*.*proj;$(SourceDir)\Business\**\*.*proj;$(SourceDir)\Test\*BootStrapper\*.*proj" />
    </ItemGroup>
    <ItemGroup Label="Analytics">
        <AnalyticsProjects Include="$(SourceDir)\Analytics\**\*.*proj" />
    </ItemGroup>
    <ItemGroup Label="UI">
        <UIProjects Include="$(SourceDir)\UI\**\*.*proj" Exclude="$(SourceDir)\UI\Mobile\**\*.*proj" />
    </ItemGroup>
    <ItemGroup Label="Service">
        <ServiceProjects Include="$(SourceDir)\Service\**\*.*proj" />
    </ItemGroup>
    <ItemGroup Label="Utilities">
        <UtilityProjects Include="$(SourceDir)\Utilities\**\*.*proj" />
    </ItemGroup>
    <ItemGroup Label="Seed">
        <SeedProjects Include="$(SourceDir)\Test\*Seed*\**\*.*proj" />
    </ItemGroup>
    <ItemGroup Label="Test">
        <TestProjects Include="$(SourceDir)\Test\**\*.*proj" Exclude="$(SourceDir)\Test\Automation\**\*.*proj;$(SourceDir)\Test\*Seed*\**\*.*proj;$(SourceDir)\Test\*Test.Common\*.*proj;$(SourceDir)\Test\*BootStrapper\*.*proj" />
    </ItemGroup>
    <ItemGroup Label="ScormPlayer">
        <ScormPlayerProjects Include="$(SourceDir)\ScormPlayer\**\*.*proj" />
    </ItemGroup>
    <ItemGroup>
        <AllDatabasesProject Include=".\All Databases.proj" />
    </ItemGroup>
    <ItemGroup>
        <SharedBinariesOutput Include="$(SourceDir)\SharedBinaries\**\*.*" Exclude="$(SourceDir)\SharedBinaries\Infrastructure\**\*.*;$(SourceDir)\SharedBinaries\Education\**\*.*;$(SourceDir)\SharedBinaries\PublishUtilities\**\*.*;$(SourceDir)\SharedBinaries\ThirdParty\**\*.*" />
    </ItemGroup>
    <Target Name="MyPreBuild">
        <Message Text="VsVersion=$(VsVersion); VisualStudioVersion=$(VisualStudioVersion); VS100COMNTOOLS=$(VS100COMNTOOLS); VS110COMNTOOLS=$(VS110COMNTOOLS); VS120COMNTOOLS=$(VS120COMNTOOLS); VS140COMNTOOLS=$(VS140COMNTOOLS)" />
    </Target>
    <Target Name="Rebuild" DependsOnTargets="MyPreBuild">
        <MSBuild Targets="Rebuild" Projects="@(BusinessProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Rebuild" Projects="@(AnalyticsProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Rebuild" Projects="@(UIProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Rebuild" Projects="@(ScormPlayerProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Rebuild" Projects="@(ServiceProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Rebuild" Projects="@(UtilityProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Condition="'$(IncludeTest)' == 'True'" Targets="Rebuild" Projects="@(TestProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Rebuild" Projects="@(SeedProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
    </Target>
    <Target Name="Clean" DependsOnTargets="MyPreBuild">
        <MSBuild Targets="Clean" Projects="@(SeedProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Condition="'$(IncludeTest)' == 'True'" Targets="Clean" Projects="@(TestProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Clean" Projects="@(UtilityProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Clean" Projects="@(ServiceProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Clean" Projects="@(ScormPlayerProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Clean" Projects="@(UIProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Clean" Projects="@(AnalyticsProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Clean" Projects="@(BusinessProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <Delete Files="@(SharedBinariesOutput)" />
    </Target>
    <Target Name="Build" DependsOnTargets="MyPreBuild">
        <MSBuild Targets="Build" Projects="@(BusinessProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Build" Projects="@(AnalyticsProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Build" Projects="@(UIProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Build" Projects="@(ScormPlayerProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Build" Projects="@(ServiceProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Build" Projects="@(UtilityProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Condition="'$(IncludeTest)' == 'True'" Targets="Build" Projects="@(TestProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
        <MSBuild Targets="Build" Projects="@(SeedProjects)" Properties="VisualStudioVersion=$(VisualStudioVersion)" />
    </Target>
    <Target Condition="'$(IncludeTest)' == 'True'" Name="CopyAssemblies" DependsOnTargets="MyPreBuild">
        <PropertyGroup>
            <LastAssemblyVersion Condition="'$(LastAssemblyVersion)' == ''"></LastAssemblyVersion>
            <AssemblyDropLocation Condition="Exists($(DropLocationRoot))">$(DropLocationRoot)\..\Database\$(LastAssemblyVersion)</AssemblyDropLocation>
            <AssemblyDropLocation Condition="!Exists($(DropLocationRoot))">$(OutDir)\..\Database</AssemblyDropLocation>
        </PropertyGroup>
        <ItemGroup Condition="Exists($(AssemblyDropLocation))">
            <AssemblySourceFiles Include="$(AssemblyDropLocation)\**\*.*" />
            <AssemblySourceFiles Remove="$(AssemblyDropLocation)\logs\**\*.*" />
        </ItemGroup>
        <Copy Condition="Exists($(AssemblyDropLocation))" OverwriteReadOnlyFiles="true" SkipUnchangedFiles="true" SourceFiles="@(AssemblySourceFiles)" DestinationFiles="@(AssemblySourceFiles -&gt; '$(OutDir)%(RecursiveDir)%(Filename)%(Extension)')" />
    </Target>

    <!--KEITHB: TRY AT INCLUDING DLLs FOR PACKAGING -->
    <Target Name="AfterBuild" DependsOnTargets="CopyAssemblies">
        <!-- Here's the call to the custom task to get the list of dependencies -->
        <ScanIndirectDependencies StartFolder="$(SourceDir)\UI\" StartProjectReferences="@(UIProjects)" Configuration="$(Configuration)">
            <Output TaskParameter="IndirectDependencies" ItemName="IndirectDependenciesToCopy" />
        </ScanIndirectDependencies>

        <!-- Only copy the file in if we won't stomp something already there -->
        <Copy SourceFiles="%(IndirectDependenciesToCopy.FullPath)" DestinationFolder="$(OutputPath)" Condition="!Exists('$(OutputPath)\%(IndirectDependenciesToCopy.Filename)%(IndirectDependenciesToCopy.Extension)')" />
    </Target>

    <!-- THE CUSTOM TASK! -->
    <UsingTask TaskName="ScanIndirectDependencies" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
        <ParameterGroup>
            <StartFolder Required="true" />
            <StartProjectReferences ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
            <Configuration Required="true" />
            <IndirectDependencies ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
        </ParameterGroup>
        <Task>
            <Reference Include="System.Xml" />
            <Using Namespace="Microsoft.Build.Framework" />
            <Using Namespace="Microsoft.Build.Utilities" />
            <Using Namespace="System" />
            <Using Namespace="System.Collections.Generic" />
            <Using Namespace="System.IO" />
            <Using Namespace="System.Linq" />
            <Using Namespace="System.Xml" />
            <Code Type="Fragment" Language="cs">
                <![CDATA[
var projectReferences = new List<string>();
var toScan = new List<string>(StartProjectReferences.Select(p => Path.GetFullPath(Path.Combine(StartFolder, p.ItemSpec))));
var indirectDependencies = new List<string>();

bool rescan;
do{
  rescan = false;
  foreach(var projectReference in toScan.ToArray())
  {
    if(projectReferences.Contains(projectReference))
    {
      toScan.Remove(projectReference);
      continue;
    }

    Log.LogMessage(MessageImportance.Low, "Scanning project reference for other project references: {0}", projectReference);

    var doc = new XmlDocument();
    doc.Load(projectReference);
    var nsmgr = new XmlNamespaceManager(doc.NameTable);
    nsmgr.AddNamespace("msb", "http://schemas.microsoft.com/developer/msbuild/2003");
    var projectDirectory = Path.GetDirectoryName(projectReference);

    // Find all project references we haven't already seen
    var newReferences = doc
          .SelectNodes("/msb:Project/msb:ItemGroup/msb:ProjectReference/@Include", nsmgr)
          .Cast<XmlAttribute>()
          .Select(a => Path.GetFullPath(Path.Combine(projectDirectory, a.Value)));

    if(newReferences.Count() > 0)
    {
      Log.LogMessage(MessageImportance.Low, "Found new referenced projects: {0}", String.Join(", ", newReferences));
    }

    toScan.Remove(projectReference);
    projectReferences.Add(projectReference);

    // Add any new references to the list to scan and mark the flag
    // so we run through the scanning loop again.
    toScan.AddRange(newReferences);
    rescan = true;

    // Include the assembly that the project reference generates.
    var outputLocation = Path.Combine(Path.Combine(projectDirectory, "bin"), Configuration);
    var localAsm = Path.GetFullPath(Path.Combine(outputLocation, doc.SelectSingleNode("/msb:Project/msb:PropertyGroup/msb:AssemblyName", nsmgr).InnerText + ".dll"));
    if(!indirectDependencies.Contains(localAsm) && File.Exists(localAsm))
    {
      Log.LogMessage(MessageImportance.Low, "Added project assembly: {0}", localAsm);
      indirectDependencies.Add(localAsm);
    }

    // Include third-party assemblies referenced by file location.
    var externalReferences = doc
          .SelectNodes("/msb:Project/msb:ItemGroup/msb:Reference/msb:HintPath", nsmgr)
          .Cast<XmlElement>()
          .Select(a => Path.GetFullPath(Path.Combine(projectDirectory, a.InnerText.Trim())))
          .Where(e => !indirectDependencies.Contains(e));

    Log.LogMessage(MessageImportance.Low, "Found new indirect references: {0}", String.Join(", ", externalReferences));
    indirectDependencies.AddRange(externalReferences);
  }
} while(rescan);

// Expand to include pdb and xml.
var xml = indirectDependencies.Select(f => Path.Combine(Path.GetDirectoryName(f), Path.GetFileNameWithoutExtension(f) + ".xml")).Where(f => File.Exists(f)).ToArray();
var pdb = indirectDependencies.Select(f => Path.Combine(Path.GetDirectoryName(f), Path.GetFileNameWithoutExtension(f) + ".pdb")).Where(f => File.Exists(f)).ToArray();
indirectDependencies.AddRange(xml);
indirectDependencies.AddRange(pdb);
Log.LogMessage("Located indirect references:\n{0}", String.Join(Environment.NewLine, indirectDependencies));

// Finally, assign the output parameter.
IndirectDependencies = indirectDependencies.Select(i => new TaskItem(i)).ToArray();
      ]]>
            </Code>
        </Task>
    </UsingTask>
</Project>

对于 8 个构建中的每一个,我想递归地找到间接引用的 DLL。我可以让它为一个项目工作,但我的大脑完全打了个嗝,让这个在所有 8 个项目中都能正常工作。比如,$(OutputPath) 到底在哪里?如何在所有 8 个项目中正确复制我的尝试?

TIA

【问题讨论】:

  • 不确定我是否 100% 正确理解了需求,但您可以使用内置功能代替自定义任务:在顶级项目上调用 msbuild 并调用 AssignProjectConfiguration 目标以获取项目引用.然后对于每个引用调用 msbuild 再次调用 ResolveAssemblyReferences 目标,您将在 ReferenceCopyLocalPaths 项中获得引用的第 3 方 dll 的完整路径。然后您可以将其复制到您想要的任何输出目录(可能是顶级应用程序的输出目录?)。以递归方式实现这一点也不难。

标签: msbuild msbuild-task


【解决方案1】:

这太长了,无法添加进一步的评论,但我很快将一些东西放在一起,递归地(通过扫描 ProjectReferences)找到将 CopyLocal 设置为 True 的引用。我认为这就是您所追求的 - 但我不确定:与您的尝试相比,它相当简单。它还显示了它的递归级别,因此很容易判断它是否在做正确的事情。

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="RecurseReferences " DependsOnTargets="AssignProjectConfiguration">
    <Message Text="$(Parent)::$(MsbuildProjectName) references @(_ProjectReferenceWithConfiguration)" Condition="@(_ProjectReferenceWithConfiguration) != ''"/>
    <MSBuild Projects="@(_ProjectReferenceWithConfiguration)" Targets="CopyReferences"
             Properties="Parent=$(Parent)::$(MsbuildProjectName)"/>
  </Target>
  <Target Name="CopyReferences" DependsOnTargets="ResolveAssemblyReferences;RecurseReferences ">
    <Message Text="$(Parent)::$(MsbuildProjectName) depends on @(ReferenceCopyLocalPaths)" Condition="@(ReferenceCopyLocalPaths) != ''"/>
  </Target>
</Project>

添加一个复制任务,它将 ReferenceCopyLocalPaths dll 复制到您想要的任何位置,例如通过将顶层的项目 OutputPath 向下传递,您就可以开始了。保存到例如recursecopy 然后像这样调用:

msbuild my.csproj /t:RecurseReferences /p:CustomAfterMicrosoftCSharpTargets=recursecopy.targets

具有递归项目引用的顶级项目的示例输出,而递归项目引用又具有“硬”依赖关系:

::ConsoleApp4 references ..\ClassLibrary4\ClassLibrary4.csproj
::ConsoleApp4::ClassLibrary4 references ..\ClassLibrary1\ClassLibrary1.csproj;..\ClassLibrary6\ClassLibrary6.csproj
::ConsoleApp4::ClassLibrary4::ClassLibrary1 references ..\ClassLibrary3\ClassLibrary3.csproj
::ConsoleApp4::ClassLibrary4::ClassLibrary1::ClassLibrary3 depends on C:\temp\Newtonsoft.Json.dll
::ConsoleApp4::ClassLibrary4::ClassLibrary1 depends on C:\temp\RecursiveRefs\bin\Debug\ClassLibrary35.dll
::ConsoleApp4::ClassLibrary4::ClassLibrary6 depends on C:\temp\RecursiveRefs\bin\Debug\ClassLibrary2.dll
::ConsoleApp4::ClassLibrary4 depends on C:\temp\RecursiveRefs\ClassLibrary5\bin\Debug\ClassLibrary5.dll

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-26
    • 1970-01-01
    相关资源
    最近更新 更多