La determinación de salidas de un ProjectReference en MSBuild sin desencadenar reconstrucciones redundantes

StackOverflow https://stackoverflow.com/questions/2325598

Pregunta

Como parte de una solución que contiene muchos proyectos, tengo un proyecto que las referencias (a través de un <ProjectReference> otros tres proyectos de la solución, además de algunos otros). En el AfterBuild, necesito copiar los resultados de los proyectos dependientes 3 específicos a otro lugar.

a través de diferentes SO respuestas, 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. <MSBuild tarea del paso IncrementalClean termina la eliminación de un número de las salidas de ProjectC. Cuando se ejecuta esta bajo VS2008, un archivo build.force siendo depositado en la carpeta obj/Debug de ProjectC lo cual dispara ProjectC conseguir reconstruido si hago un Construir sobre toda la solución si el proyecto que contiene este objetivo AfterBuild, mientras que si se excluye este proyecto de la construcción, que [correctamente] no provocar una recompilación de ProjectC (y críticamente a reconstruir de todos los dependientes de ProjectC). Esto puede ser engaño VS-específico en este caso que no se producen en el contexto de un TeamBuild u otra invocación de comandos de MSBuild (aunque el uso más común será a través de VS por lo que necesito resolver este cualquier manera)

Los proyectos dependientes (y el resto de la solución en general) han sido creados de forma interactiva con VS, por tanto, las rutas relativas ProjectRefences contener etc. he visto mención de que esto sea probable que causan problemas - pero sin una plena explicación de por qué, o cuando va a ser fijo o cómo trabajar alrededor de ella. En otras palabras, no estoy realmente interesado en, por ejemplo, la conversión de los caminos ProjectReference a rutas absolutas a mano de edición de la .csproj.

Si bien es muy posible que estoy haciendo algo estúpido y alguien de inmediato a señalar lo que es (lo cual sería estupendo), estar seguro de que he pasado mucho tiempo estudiando detenidamente /v:diag salidas etc (aunque no he intentado construir una repro desde cero - esto es, en el contexto de una relativamente compleja estructura global)

¿Fue útil?

Solución

Como se señaló en mi comentario, llamando GetTargetPath en el proyecto de referencia sólo devuelve la salida primaria de montaje de ese proyecto. Para obtener todos los conjuntos de copia local antes mencionados del proyecto de referencia que es un poco más desordenado.

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 carpeta bin de mi top proyecto de nivel de host. Esto es un poco desordenado y no estaba contento con el MSDN sugiere soluciones de ensuciar con el OutputPath - como que las roturas en nuestro servidor de compilación e impide la creación de la estructura de carpetas en un proyecto diferente (por ejemplo, un SystemTest)

Así que junto con la adición del 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 de tuberías creado:

<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 es necesario añadir los datos PipelineFolder meta requerida para las referencias reales del proyecto. 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>

Otros consejos

Su solución original debería funcionar simplemente cambiando

Targets="Build"

a

Targets="GetTargetPath"

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

Se puede proteger sus archivos en ProjectC si se 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>

solución actual se basa en esta cuestión de forma , 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 todas las salidas terminan en un directorio), y también si los nombres de cualquiera de las salidas del cambio depende proyectos.

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>
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top