2017-08-28 27 views
1

我总是让构建团队处理构建定义。由于一些限制,我现在不得不忍受这一点,并且对于MSBUILD如何处理XML构建定义没有太多线索。一些见解/帮助将不胜感激。在MSBUILD中正确设置间接引用

经过研究,我发现这是一个常见的问题,很少有记录的解决方案。在一个复杂的应用程序中(我们有超过50个“.csproj”项目作为一个应用程序一起工作),您会发现顶级项目(web应用程序,web api,win服务等)可以参考中级项目实用程序,基础设施,核心,日志等),这些代码又引用第三方DLL。在完整构建期间,这些第三方引用永远不会将其发送到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。我可以让它为一个项目工作,但是我的大脑完全打破了正确的工作方式。喜欢,在哪里设置了$(OutputPath)?如何正确地复制所有8个项目的尝试?

TIA

+0

不知道如果我理解正确的要求100% ,但是可以使用内置功能而不是该自定义任务:在顶级项目上调用msbuild,并调用AssignProjectConfiguration目标以获取项目引用。然后,再次为每个引用调用msbuild调用ResolveAssemblyReferences目标,您将在ReferenceCopyLocalPaths项目中获得引用的第三方dll的完整路径。然后你可以复制到你想要的任何输出目录(可能,顶层应用程序的输出目录?)。递归地实现这一点也不难。 – stijn

回答

0

这太长添加为更多的评论,但我很快扔东西在一起,该递归(通过扫描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