2010-02-24 15 views
6

Dans le cadre d'une solution contenant de nombreux projets, j'ai un projet qui référence (via un <ProjectReference> trois autres projets dans la solution, plus quelques autres). Dans le AfterBuild, j'ai besoin de copier les sorties de 3 projets dépendants spécifiques à un autre endroit.Déterminer les sorties d'un ProjectReference dans MSBuild sans déclencher des reconstructions redondantes

Via diverses réponses SO, etc. la façon dont je me suis installé à accomplir c'était:

<MSBuild 
     Projects="@(ProjectReference)" 
     Targets="Build" 
     BuildInParallel="true" 
     Condition="'%(Name)'=='ProjectA' OR '%(Name)'=='ProjectB' OR '%(Name)'=='ProjectC'"> 
     <Output TaskParameter="TargetOutputs" ItemName="DependentAssemblies" /> 
    </MSBuild> 
    <Copy SourceFiles="@(DependentAssemblies)" DestinationFolder="XX" SkipUnchangedFiles="true" /> 

Cependant, je suis tombé sur des problèmes avec cela. La tâche <MSBuild de l'étape IncrementalClean finit par supprimer un nombre de sorties de ProjectC. Lors de l'exécution sous VS2008, un fichier build.force est déposé dans le dossier obj/Debug de ProjectC qui déclenche alors la reconstruction de ProjectC si je fais une Build sur l'ensemble de la solution si le projet contient cette cible AfterBuild, alors que si on exclut ce projet de la build, il [correctement] ne déclenche pas une reconstruction de ProjectC (et critique une reconstruction de toutes les dépendances de ProjectC). Cela peut être une supercherie spécifique à VS dans ce cas qui ne se produirait pas dans le contexte d'une invocation TeamBuild ou MSBuild en ligne de commande (mais l'utilisation la plus courante sera via VS, donc j'ai besoin de résoudre ça)

les projets (et le reste de la solution en général) ont tous été créés de manière interactive avec VS, et par conséquent les ProjectRefence contiennent des chemins relatifs, etc. J'ai vu une mention de cela susceptible de causer des problèmes - mais sans une explication complète de pourquoi, ou quand il sera réparé ou comment le contourner. En d'autres termes, je ne m'intéresse pas vraiment, par exemple. convertir les chemins ProjectReference en chemins absolus en modifiant manuellement le fichier .csproj.

Alors qu'il est tout à fait possible que je fasse quelque chose de stupide et que quelqu'un va immédiatement préciser ce que c'est (ce qui serait génial), soyez assuré que j'ai passé beaucoup de temps pour construire une repro à partir du sol - ce qui est dans le contexte d'une construction globale relativement complexe)

Répondre

6

Comme indiqué dans mon commentaire, l'appel de GetTargetPath sur le projet référencé renvoie uniquement l'assembly de sortie principal de ce projet. Pour obtenir tous les assemblys copiés-locaux référencés du projet référencé, c'est un peu plus compliqué.

Ajouter ce qui suit à chaque projet que vous faites référence que vous souhaitez obtenir les CopyLocals de:

<Target 
    Name="ComputeCopyLocalAssemblies" 
    DependsOnTargets="ResolveProjectReferences;ResolveAssemblyReferences" 
    Returns="@(ReferenceCopyLocalPaths)" /> 

Ma situation est que je devais recréer la structure du dossier Pipeline pour System.AddIn dans le bac dossier de mon projet hôte de premier niveau. C'est un peu compliqué et je n'étais pas content des solutions MSDN suggérées pour le meulage avec OutputPath - car cela casse notre serveur de construction et empêche la création de la structure de dossier dans un projet différent (par exemple un SystemTest)

l'objectif ci-dessus (à l'aide d'une importation .targets), j'ajouté ce qui suit dans un fichier .targets importé par chaque « hôte » qui a besoin du dossier de pipeline créé:

<Target 
    Name="ComputePipelineAssemblies" 
    BeforeTargets="_CopyFilesMarkedCopyLocal" 
    Outputs="%(ProjectReference.Identity)"> 

    <ItemGroup> 
     <_PrimaryAssembly Remove="@(_PrimaryAssembly)" /> 
     <_DependentAssemblies Remove="@(_DependentAssemblies)" /> 
    </ItemGroup> 

    <!--The Primary Output of the Pipeline project--> 
    <MSBuild Projects="%(ProjectReference.Identity)" 
      Targets="GetTargetPath" 
      Properties="Configuration=$(Configuration)" 
      Condition=" '%(ProjectReference.PipelineFolder)' != '' "> 
     <Output TaskParameter="TargetOutputs" 
       ItemName="_PrimaryAssembly" /> 
    </MSBuild> 

    <!--Output of any Referenced Projects--> 
    <MSBuild Projects="%(ProjectReference.Identity)" 
      Targets="ComputeCopyLocalAssemblies" 
      Properties="Configuration=$(Configuration)" 
      Condition=" '%(ProjectReference.PipelineFolder)' != '' "> 
     <Output TaskParameter="TargetOutputs" 
       ItemName="_DependentAssemblies" /> 
    </MSBuild> 

    <ItemGroup> 
     <ReferenceCopyLocalPaths Include="@(_PrimaryAssembly)" 
           Condition=" '%(ProjectReference.PipelineFolder)' != '' "> 
      <DestinationSubDirectory>%(ProjectReference.PipelineFolder)</DestinationSubDirectory> 
     </ReferenceCopyLocalPaths> 
     <ReferenceCopyLocalPaths Include="@(_DependentAssemblies)" 
           Condition=" '%(ProjectReference.PipelineFolder)' != '' "> 
      <DestinationSubDirectory>%(ProjectReference.PipelineFolder)</DestinationSubDirectory> 
     </ReferenceCopyLocalPaths> 
    </ItemGroup> 
</Target> 

J'ai aussi besoin d'ajouter les données meta PipelineFolder requis aux références réelles du projet. Par exemple:

<ProjectReference Include="..\Dogs.Pipeline.AddInSideAdapter\Dogs.Pipeline.AddInSideAdapter.csproj"> 
     <Project>{FFCD0BFC-5A7B-4E13-9E1B-8D01E86975EA}</Project> 
     <Name>Dogs.Pipeline.AddInSideAdapter</Name> 
     <Private>False</Private> 
     <PipelineFolder>Pipeline\AddInSideAdapter\</PipelineFolder> 
    </ProjectReference> 
1

Mon current workaround is based on this SO question, à savoir, je:

<ItemGroup> 
     <DependentAssemblies Include=" 
      ..\ProjectA\bin\$(Configuration)\ProjectA.dll; 
      ..\ProjectB\bin\$(Configuration)\ProjectB.dll; 
      ..\ProjectC\bin\$(Configuration)\ProjectC.dll"> 
     </DependentAssemblies> 
    </ItemGroup> 

ce sera toutefois rompre sous TeamBuild (où tous les les sorties se retrouvent dans un répertoire), et aussi si les noms de l'une des sorties du proj les changements

EDIT: Aussi la recherche des commentaires s'il y a une réponse plus propre pour savoir comment faire le hardcoding un peu plus propre que:

<PropertyGroup> 
     <_TeamBuildingToSingleOutDir Condition="'$(TeamBuildOutDir)'!='' AND '$(CustomizableOutDir)'!='true'">true</_TeamBuildingToSingleOutDir> 
    </PropertyGroup> 

et:

<ItemGroup> 
     <DependentAssemblies 
      Condition="'$(_TeamBuildingToSingleOutDir)'!='true'" 
      Include=" 
       ..\ProjectA\bin\$(Configuration)\ProjectA.dll; 
       ..\ProjectB\bin\$(Configuration)\ProjectB.dll; 
       ..\ProjectC\bin\$(Configuration)\ProjectC.dll"> 
     </DependentAssemblies> 
     <DependentAssemblies 
      Condition="'$(_TeamBuildingToSingleOutDir)'=='true'" 
      Include=" 
       $(OutDir)\ProjectA.dll; 
       $(OutDir)\ProjectB.dll; 
       $(OutDir)\ProjectC.dll"> 
     </DependentAssemblies> 
    </ItemGroup> 
2

Vous peut protéger vos fichiers ProjectC si vous appelez une cible comme celle-ci en premier:

<Target Name="ProtectFiles"> 
    <ReadLinesFromFile File="obj\ProjectC.csproj.FileListAbsolute.txt"> 
     <Output TaskParameter="Lines" ItemName="_FileList"/> 
    </ReadLinesFromFile> 
    <CreateItem Include="@(_DllFileList)" Exclude="File1.sample; File2.sample"> 
     <Output TaskParameter="Include" ItemName="_FileListWitoutProtectedFiles"/> 
    </CreateItem>  
     <WriteLinesToFile 
     File="obj\ProjectC.csproj.FileListAbsolute.txt" 
     Lines="@(_FileListWitoutProtectedFiles)" 
     Overwrite="true"/> 
    </Target> 
+0

Tout d'abord, merci/félicitations d'avoir choisi ce casse-tête comme votre première réponse SO! Personnellement, je serais réticent à l'idée d'introduire une dépendance sur les composants internes de MSBuild de cette nature, mais oui, cela me permettrait certainement de réaliser le programme "quels fichiers a-t-il écrits"? Il y a quelques autres questions de "calculer les résultats" ici sur SO que cela pourrait mieux répondre à. Le problème principal ici pour moi est que le bit '

5

Votre origine solution devrait fonctionner l simplement en changeant

Targets="Build" 

à

Targets="GetTargetPath" 

La cible GetTargetPath renvoie simplement la propriété TargetPath et ne nécessite pas la construction.

+0

+ 1 Merci, pas en mesure de vérifier que cela a vraiment fonctionné (beaucoup d'eau sous le pont depuis!), Mais semble une approche très viable et propre. –

+0

YMMV: GetTargetPath ne renvoie que l'assembly de sortie principal d'un projet (.DLL/.EXE) Si vous voulez que tous les assemblys référencés _all_ de ce projet nécessitent une technique différente –

+0

Si quelqu'un d'autre doit faire cela pour un projet C++, la bonne cible invoquer est Targets = "GetNativeTargetPath". – Neutrino