Determinazione uscite di un ProjectReference in MSBuild senza innescare ricostruzioni ridondanti

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

Domanda

Come parte di una soluzione contenente molti progetti, ho un progetto che fa riferimento (tramite un <ProjectReference> altri tre progetti nella soluzione, oltre ad alcuni altri). Nel AfterBuild, ho bisogno di copiare le uscite di 3 specifici progetti dipendenti in un'altra posizione.

Via varie risposte SO, ecc il modo in cui ho optato per per realizzare questo è stato:

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

Comunque, mi sono imbattuto in problemi con questo. compito <MSBuild dello step IncrementalClean finisce per l'eliminazione di un certo numero di uscite di ProjectC. Quando si esegue questo sotto VS2008, un file build.force depositato nella cartella obj/Debug di ProjectC che poi innesca ProjectC sempre ricostruita se faccio un accumulo su tutta la soluzione, se il progetto che contiene questo obiettivo AfterBuild, mentre se si esclude questo progetto dalla costruzione, esso [correttamente] non innescare una ricostruzione di ProjectC (e criticamente a ricostruire di tutti i dipendenti di ProjectC). Questo può essere inganno VS-specifico, in questo caso, che non si verificherebbe nel contesto di una TeamBuild o di altra riga di comando MSBuild invocazione (ma l'uso più comune sarà via VS quindi ho bisogno di risolvere questo in entrambi i casi)

I progetti dipendenti (e il resto della soluzione in generale) sono stati tutti creati in modo interattivo con VS, e quindi alle ProjectRefences contenere percorsi relativi ecc Ho visto menzione di questo essere suscettibili di causare problemi - ma senza un pieno spiegazione del perché, o quando sarà fisso o come lavorare intorno ad esso. In altre parole, non sono veramente interessati a esempio convertendo i percorsi ProjectReference a percorsi assoluti a mano-modificando il csproj.

Mentre è del tutto possibile che sto facendo qualcosa di stupido e qualcuno immediatamente sottolineare quello che è (che sarebbe fantastico), essere assicurato ho passato un sacco di tempo studiando attentamente uscite /v:diag ecc (anche se io ho mai provato a costruire un Repro dalla terra in su - questo è nel contesto di una costruzione complessiva relativamente complessa)

È stato utile?

Soluzione

Come notato nel mio commento, chiamando GetTargetPath sul progetto di riferimento restituisce solo l'assemblaggio output primario di quel progetto. Per ottenere tutte le assemblee di riferimento da copia locale del progetto di riferimento è un po 'disordinato.

Aggiungere il seguente ad ogni progetto che si fa riferimento che si desidera ottenere i CopyLocals di:

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

La mia situazione particolare, è che avevo bisogno di ricreare la struttura delle cartelle Pipeline per System.AddIn nella cartella bin del mio top progetto di livello host. Questo è un pò disordinato e non ero contento del MSDN suggerito soluzioni di pasticciare con l'OutputPath - come quella rompe sul nostro server di build e impedisce la creazione della struttura di cartelle in un progetto diverso (ad esempio uno SystemTest)

Quindi, insieme con l'aggiunta il target sopra (utilizzando un'importazione .targets), ho aggiunto il seguente in un file .targets importato da ogni "ospite" che ha bisogno la cartella creata gasdotto:

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

Ho anche bisogno di aggiungere la necessaria PipelineFolder metadati ai riferimenti del progetto attuale. Ad esempio:

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

Altri suggerimenti

La vostra soluzione originale dovrebbe funzionare semplicemente cambiando

Targets="Build"

a

Targets="GetTargetPath"

L'obiettivo GetTargetPath restituisce semplicemente la proprietà TargetPath e non necessita di costruzione.

È possibile proteggere i file in ProjectC se si chiama un obiettivo come questa:

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

soluzione corrente è sulla base di questa domanda SO , vale a dire, ho:

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

Questo però si romperà sotto TeamBuild (dove tutte le uscite finiscono in una directory), e anche se i nomi di una qualsiasi delle uscite dei progetti dipendenti cambiano.

EDIT: Anche alla ricerca di eventuali commenti sul fatto che ci sia una risposta più pulita per come fare la hardcoding un po 'più pulita rispetto a:

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

e

    <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>
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top