2017-08-28 4 views
1

J'ai toujours laissé l'équipe de construction gérer les définitions de construction. En raison de certaines contraintes, je suis obligé de mordre cela maintenant et je n'ai pas la moindre idée de la façon dont MSBUILD traite la définition de construction XML. Un aperçu/aide serait apprécié. Après avoir fait des recherches, j'ai découvert que c'est un problème commun avec très peu de solutions documentées. Dans une application complexe (nous avons plus de 50 projets ".csproj" fonctionnant tous en une seule application), vous trouverez que les projets de haut niveau (application web, API web, services win, etc.) font référence à des projets de niveau intermédiaire (utilitaires, infrastructure, noyau, journalisation, etc) qui à leur tour ont des références à des DLL tiers. Pendant une génération complète, ces références tierces ne parviennent jamais au dossier BIN.Définir correctement les références indirectes dans MSBUILD

Alors, sans plus tarder, essayons de faire fonctionner cette définition de construction. Ma tentative de récursion est venu de cet article: 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> 

Pour chacun des 8 builds Je voudrais trouver récursive les DLL référencées indirectement. Je peux obtenir cela pour travailler sur un projet, mais mon cerveau a juste fait tout ce temps pour que cela fonctionne correctement sur tous les 8. Comme, où diable est $(OutputPath) réglé? Comment répliquer correctement ma tentative sur l'ensemble des 8 projets?

TIA

+0

Je ne sais pas si je comprends l'exigence de 100% correctement , mais au lieu de cette tâche personnalisée, vous pouvez utiliser la fonctionnalité intégrée: appelez msbuild sur les projets de premier niveau et appelez la cible AssignProjectConfiguration pour obtenir des références de projet. Ensuite, pour chaque référence, appelez à nouveau msbuild, appelez la cible ResolveAssemblyReferences et vous obtiendrez des chemins d'accès complets aux dlls tiers référencés dans l'élément ReferenceCopyLocalPaths. Que pouvez-vous copier dans n'importe quel répertoire de sortie (probablement le répertoire de sortie des applications de toplevel?). Pour que cela se passe de manière récursive ne devrait pas être difficile non plus. – stijn

Répondre

0

Ceci est trop long pour ajouter d'autres commentaires, mais je me suis vite jeté quelque chose ensemble qui récursive (par balayage ProjectReferences) trouve des références qui ont mis en CopyLocal True. Je suppose que c'est ce que vous recherchez - mais je ne suis pas sûr: c'est plutôt simple par rapport à votre tentative. Il montre également à quel niveau il est récursif, donc il est facile de déterminer s'il fait la bonne chose.

<?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> 

Ajoutez une tâche de copie qui copie les DLL ReferenceCopyLocalPaths où vous le souhaitez, par ex. en passant le projet OutputPath du toplevel sur la ligne, et vous êtes prêt à partir. Enregistrer par ex. recursecopy puis invoquer comme ceci:

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

Exemple de sortie pour un projet de premier niveau avec des références de projet récursifs qui à leur tour ont des dépendances « dures »:

::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