2017-08-28 116 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