2010-02-24 14 views
6

Como parte de una solución que contiene muchos proyectos, tengo un proyecto que hace referencia (a través de <ProjectReference> otros tres proyectos en la solución, más algunos otros). En el AfterBuild, necesito copiar las salidas de 3 proyectos dependientes específicos a otra ubicación.Determinación de salidas de ProjectReference en MSBuild sin activar reconstrucciones redundantes

Via diversas respuestas SO, etc., la forma en que se establecieron en lograr que era:

<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" /> 

Sin embargo, me encontré con problemas con esto. La tarea <MSBuild del paso IncrementalClean termina borrando varias salidas de ProjectC. Al ejecutar esto en VS2008, se deposita un archivo build.force en la carpeta obj/Debug de ProjectC que luego activa la reconstrucción de ProjectC si realizo una generación en la solución completa si el proyecto contiene este AfterBuild objetivo, mientras que si uno excluye este proyecto de la compilación, [correctamente] no desencadena una reconstrucción de ProjectC (y críticamente una reconstrucción de todos los dependientes de ProjectC). Esto puede ser un truco específico de VS en este caso que no ocurriría en el contexto de una invocación de TeamBuild u otra línea de comandos de MSBuild (pero el uso más común será a través de VS, así que necesito resolverlo de cualquier manera)

los proyectos (y el resto de la solución en general) se han creado interactivamente con VS, y por lo tanto, el ProjectRefence s contiene rutas relativas, etc. He visto mencionar que esto podría causar problemas, pero sin una explicación completa de por qué, o cuándo se solucionará o cómo solucionarlo. En otras palabras, no estoy realmente interesado en, p. convirtiendo las rutas ProjectReference en rutas absolutas editando manualmente .csproj.

Si bien es completamente posible que estoy haciendo algo estúpido y alguien inmediatamente señalará lo que es (que sería genial), tenga la seguridad de que he dedicado mucho tiempo estudiando /v:diag salidas, etc. (aunque no lo he probado para construir una repro desde cero - esto es, en el contexto de una relativamente compleja estructura global)

Respuesta

6

Como mencioné en mi comentario, al llamar a GetTargetPath en el proyecto al que se hace referencia solo se devuelve el ensamblaje de salida principal de ese proyecto. Para obtener todos los conjuntos locales de copia referenciados del proyecto al que se hace referencia, es un poco más complicado.

Añadir lo siguiente a cada proyecto que se hace referencia a que desea obtener los CopyLocals de:

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

Mi situación particular es que necesitaba para volver a crear la estructura de carpetas de la tubería de System.AddIn en la papelera carpeta de mi proyecto de host de nivel superior. Esto es un poco desordenado y no estaba contento con las soluciones sugeridas de MSDN de mucking con OutputPath, ya que se rompe en nuestro servidor de compilación y evita crear la estructura de carpetas en un proyecto diferente (por ejemplo, un SystemTest)

Así que junto con agregar el objetivo anterior (usando una importación .targets), que añadió lo siguiente a un archivo .targets importado por cada "huésped" que necesita la carpeta creada tubería:

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

también necesitaba para agregar los metadatos necesarios PipelineFolder a las referencias del proyecto real. Por ejemplo:

<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

Mi current workaround is based on this SO question, es decir, que tengo:

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

Sin embargo, esto se romperá bajo TeamBuild (donde todo el salidas terminan en un directorio), y también si los nombres de cualquiera de las salidas del proyecto dependiente los cambios cambian.

EDIT: También en busca de cualquier comentario acerca de si hay una respuesta más limpio para cómo hacer que la codificando un poco más limpio que:

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

y:

<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

Usted puede proteger sus archivos en ProjectC si llama a un objetivo como este primero:

<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

En primer lugar, gracias/felicidades por elegir este rompecabezas como su primera respuesta SO. Personalmente, me mostraría reticente a introducir una dependencia en el funcionamiento interno de MSBuild de esta naturaleza, pero sí, esto ciertamente me permitiría lograr los "archivos que escribió" programáticamente. Hay algunas otras preguntas de "cálculo de los resultados" aquí en SO que podrían encajar mejor como respuesta. El problema principal aquí para mí es que el bit '

5

Su origina l solución debe trabajar simplemente cambiando

Targets="Build" 

a

Targets="GetTargetPath" 

El objetivo GetTargetPath simplemente devuelve la propiedad TargetPath y no requiere la construcción.

+0

+1 Gracias, no estoy en condiciones de verificar que funcionó (¡mucha agua debajo del puente desde entonces!), Pero parece ser un enfoque muy viable y limpio. –

+0

YMMV: GetTargetPath solo devuelve el ensamblaje de salida primario de un proyecto (.DLL/.EXE) Si desea _todos los ensamblados de ese proyecto, entonces necesita una técnica diferente –

+0

En caso de que alguien más necesite hacer esto para un proyecto C++, el objetivo correcto invocar es Targets = "GetNativeTargetPath". – Neutrino

Cuestiones relacionadas