我總是讓構建團隊處理構建定義。由於一些限制,我現在不得不忍受這一點,並且對於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 -> '$(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
不知道如果我理解正確的要求100% ,但是可以使用內置功能而不是該自定義任務:在頂級項目上調用msbuild,並調用AssignProjectConfiguration目標以獲取項目引用。然後,再次爲每個引用調用msbuild調用ResolveAssemblyReferences目標,您將在ReferenceCopyLocalPaths項目中獲得引用的第三方dll的完整路徑。然後你可以複製到你想要的任何輸出目錄(可能,頂層應用程序的輸出目錄?)。遞歸地實現這一點也不難。 – stijn