Rozszerzanie procesu kompilacji programu Visual Studio

Proces kompilacji programu Visual Studio jest definiowany przez serię plików MSBuild .targets , które są importowane do pliku projektu. Te importy są niejawne, jeśli używasz zestawu SDK jako projektów programu Visual Studio zwykle. Jeden z tych zaimportowanych plików, Microsoft.Common.targets, można rozszerzyć, aby umożliwić uruchamianie zadań niestandardowych w kilku punktach procesu kompilacji. W tym artykule opisano trzy metody, których można użyć do rozszerzenia procesu kompilacji programu Visual Studio:

  • Utwórz obiekt docelowy niestandardowy i określ, kiedy ma być uruchamiany przy użyciu atrybutów BeforeTargets i AfterTargets .

  • Zastąpi DependsOn właściwości zdefiniowane we wspólnych miejscach docelowych.

  • Przesłaniaj określone wstępnie zdefiniowane obiekty docelowe zdefiniowane we wspólnych miejscach docelowych (Microsoft.Common.targets lub pliki importowane).

AfterTargets i BeforeTargets

Możesz użyć AfterTargets atrybutów i BeforeTargets w obiekcie docelowym niestandardowym, aby określić, kiedy ma zostać uruchomiona.

W poniższym przykładzie pokazano, jak za pomocą atrybutu AfterTargets dodać obiekt docelowy niestandardowy, który wykonuje coś z plikami wyjściowymi. W takim przypadku kopiuje pliki wyjściowe do nowego folderu CustomOutput. W przykładzie pokazano również, jak wyczyścić pliki utworzone przez niestandardową CustomClean operację kompilacji z obiektem BeforeTargets docelowym przy użyciu atrybutu i określić, że niestandardowa operacja czyszczenia jest uruchamiana przed CoreClean celem.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
     <TargetFramework>netcoreapp3.1</TargetFramework>
     <_OutputCopyLocation>$(OutputPath)..\..\CustomOutput\</_OutputCopyLocation>
  </PropertyGroup>

  <Target Name="CustomAfterBuild" AfterTargets="Build">
    <ItemGroup>
      <_FilesToCopy Include="$(OutputPath)**\*"/>
    </ItemGroup>
    <Message Text="_FilesToCopy: @(_FilesToCopy)" Importance="high"/>

    <Message Text="DestFiles:
        @(_FilesToCopy->'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>

    <Copy SourceFiles="@(_FilesToCopy)"
          DestinationFiles=
          "@(_FilesToCopy->'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>
  </Target>

  <Target Name="CustomClean" BeforeTargets="CoreClean">
    <Message Text="Inside Custom Clean" Importance="high"/>
    <ItemGroup>
      <_CustomFilesToDelete Include="$(_OutputCopyLocation)**\*"/>
    </ItemGroup>
    <Delete Files='@(_CustomFilesToDelete)'/>
  </Target>
</Project>

Ostrzeżenie

Pamiętaj, aby użyć różnych nazw niż wstępnie zdefiniowane obiekty docelowe (na przykład niestandardowy element docelowy kompilacji w tym miejscu to CustomAfterBuild, a nie AfterBuild), ponieważ te wstępnie zdefiniowane obiekty docelowe są zastępowane przez import zestawu SDK, który również je definiuje. Zapoznaj się z tabelą na końcu tego artykułu, aby uzyskać listę wstępnie zdefiniowanych celów.

Rozszerzanie właściwości DependsOn

Innym sposobem rozszerzenia procesu kompilacji jest użycie DependsOn właściwości (na przykład BuildDependsOn), aby określić cele, które mają być uruchamiane przed standardowym celem.

Ta metoda jest preferowana do zastępowania wstępnie zdefiniowanych obiektów docelowych, które zostały omówione w następnej sekcji. Zastępowanie wstępnie zdefiniowanych obiektów docelowych jest starszą metodą, która jest nadal obsługiwana, ale ponieważ program MSBuild ocenia definicję obiektów docelowych sekwencyjnie, nie ma możliwości zapobiegania innemu projektowi, który importuje projekt z zastępowania obiektów docelowych, które zostały już zastąpione. Na przykład ostatni AfterBuild element docelowy zdefiniowany w pliku projektu, po zaimportowaniu wszystkich innych projektów, będzie tym, który jest używany podczas kompilacji.

Można chronić przed niezamierzonym przesłonięciami obiektów docelowych, przesłaniając DependsOn właściwości, które są używane w DependsOnTargets atrybutach we wspólnych miejscach docelowych. Na przykład element docelowy Build zawiera DependsOnTargets wartość atrybutu "$(BuildDependsOn)". Rozważ następujące kwestie:

<Target Name="Build" DependsOnTargets="$(BuildDependsOn)"/>

Ten fragment kodu XML wskazuje, że przed uruchomieniem Build obiektu docelowego należy najpierw uruchomić wszystkie elementy docelowe określone we BuildDependsOn właściwości . Właściwość jest zdefiniowana BuildDependsOn jako:

<PropertyGroup>
    <BuildDependsOn>
        $(BuildDependsOn);
        BeforeBuild;
        CoreBuild;
        AfterBuild
    </BuildDependsOn>
</PropertyGroup>

Tę wartość właściwości można zastąpić, deklarując inną właściwość o nazwie BuildDependsOn na końcu pliku projektu. W projekcie w stylu zestawu SDK oznacza to, że musisz używać jawnych importów. Zobacz Importy niejawne i jawne, aby można było umieścić DependsOn właściwość po ostatnim zaimportowaniu. Po uwzględnieniu poprzedniej BuildDependsOn właściwości w nowej właściwości można dodać nowe elementy docelowe na początku i na końcu listy docelowej. Na przykład:

<PropertyGroup>
    <BuildDependsOn>
        MyCustomTarget1;
        $(BuildDependsOn);
        MyCustomTarget2
    </BuildDependsOn>
</PropertyGroup>

<Target Name="MyCustomTarget1">
    <Message Text="Running MyCustomTarget1..."/>
</Target>
<Target Name="MyCustomTarget2">
    <Message Text="Running MyCustomTarget2..."/>
</Target>

Projekty importujące plik projektu mogą dodatkowo rozszerzyć te właściwości bez zastępowania wprowadzonych dostosowań.

Aby zastąpić właściwość DependsOn

  1. Zidentyfikuj wstępnie zdefiniowaną DependsOn właściwość we wspólnych miejscach docelowych, które chcesz zastąpić. Poniższa tabela zawiera listę często zastępowanych DependsOn właściwości.

  2. Zdefiniuj inne wystąpienie właściwości lub właściwości na końcu pliku projektu. Uwzględnij oryginalną właściwość, na przykład $(BuildDependsOn), w nowej właściwości.

  3. Zdefiniuj niestandardowe obiekty docelowe przed lub po definicji właściwości.

  4. Skompiluj plik projektu.

Często zastępowane właściwości DependsOn

Nazwa właściwości Dodano obiekty docelowe uruchamiane przed tym punktem:
BuildDependsOn Główny punkt wejścia kompilacji. Zastąpi tę właściwość, jeśli chcesz wstawić niestandardowe obiekty docelowe przed lub po całym procesie kompilacji.
RebuildDependsOn Rebuild
RunDependsOn Wykonanie końcowych danych wyjściowych kompilacji (jeśli jest to .EXE)
CompileDependsOn Kompilacja (Compile element docelowy). Zastąpi tę właściwość, jeśli chcesz wstawić niestandardowe procesy przed lub po kroku kompilacji.
CreateSatelliteAssembliesDependsOn Tworzenie zestawów satelickich
CleanDependsOn Element docelowy Clean (usuwanie wszystkich danych wyjściowych kompilacji pośredniej i końcowej). Zastąpi tę właściwość, jeśli chcesz wyczyścić dane wyjściowe z niestandardowego procesu kompilacji.
PostBuildEventDependsOn Element docelowy PostBuildEvent
PublishBuildDependsOn Publikowanie kompilacji
ResolveAssemblyReferencesDependsOn Cel ResolveAssemblyReferences (znalezienie przejściowego zamknięcia zależności dla danej zależności). Zobacz: ResolveAssemblyReference.

Przykład: BuildDependsOn i CleanDependsOn

Poniższy przykład jest podobny do przykładu BeforeTargets i AfterTargets , ale pokazuje, jak osiągnąć podobne funkcje. Rozszerza kompilację przy użyciu polecenia BuildDependsOn , aby dodać własne zadanie CustomAfterBuild , które kopiuje pliki wyjściowe po kompilacji, a także dodaje odpowiednie CustomClean zadanie przy użyciu polecenia CleanDependsOn.

W tym przykładzie jest to projekt w stylu zestawu SDK. Jak wspomniano w notatce dotyczącej projektów w stylu zestawu SDK we wcześniejszej części tego artykułu, należy użyć metody ręcznego importowania zamiast Sdk atrybutu używanego przez program Visual Studio podczas generowania plików projektu.

<Project>
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk"/>

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk"/>

  <PropertyGroup>
    <BuildDependsOn>
      $(BuildDependsOn);CustomAfterBuild
    </BuildDependsOn>

    <CleanDependsOn>
      $(CleanDependsOn);CustomClean
    </CleanDependsOn>

    <_OutputCopyLocation>$(OutputPath)..\..\CustomOutput\</_OutputCopyLocation>
  </PropertyGroup>

  <Target Name="CustomAfterBuild">
    <ItemGroup>
      <_FilesToCopy Include="$(OutputPath)**\*"/>
    </ItemGroup>
    <Message Importance="high" Text="_FilesToCopy: @(_FilesToCopy)"/>

    <Message Text="DestFiles:
      @(_FilesToCopy-&gt;'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>

    <Copy SourceFiles="@(_FilesToCopy)"
          DestinationFiles="@(_FilesToCopy-&gt;'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>
  </Target>

  <Target Name="CustomClean">
    <Message Importance="high" Text="Inside Custom Clean"/>
    <ItemGroup>
      <_CustomFilesToDelete Include="$(_OutputCopyLocation)**\*"/>
    </ItemGroup>
    <Delete Files="@(_CustomFilesToDelete)"/>
  </Target>
</Project>

Kolejność elementów jest ważna. BuildDependsOn Elementy i CleanDependsOn muszą pojawić się po zaimportowaniu pliku obiektów docelowych standardowego zestawu SDK.

Zastępowanie wstępnie zdefiniowanych obiektów docelowych

Typowe .targets pliki zawierają zestaw wstępnie zdefiniowanych pustych obiektów docelowych, które są wywoływane przed i po niektórych głównych miejscach docelowych w procesie kompilacji. Na przykład program MSBuild wywołuje element docelowy BeforeBuild przed głównym CoreBuild celem i AfterBuild elementem docelowym po elemercie CoreBuild docelowym. Domyślnie puste obiekty docelowe w typowych miejscach docelowych nie robią nic, ale można zastąpić ich domyślne zachowanie, definiując obiekty docelowe w pliku projektu. Preferowane są metody opisane wcześniej w tym artykule, ale może wystąpić starszy kod, który używa tej metody.

Jeśli projekt używa zestawu SDK (na przykład Microsoft.Net.Sdk), musisz wprowadzić zmianę z niejawnych na jawne importy, zgodnie z opisem w temacie Jawne i niejawne importy.

Aby zastąpić wstępnie zdefiniowany element docelowy

  1. Jeśli projekt używa atrybutu Sdk , zmień go na jawną składnię importu. Zobacz Jawne i niejawne importy.

  2. Zidentyfikuj wstępnie zdefiniowany element docelowy we wspólnych miejscach docelowych, które chcesz zastąpić. Poniższa tabela zawiera pełną listę obiektów docelowych, które można bezpiecznie zastąpić.

  3. Zdefiniuj element docelowy lub docelowy na końcu pliku projektu bezpośrednio przed tagiem </Project> i po jawnym zaimportowaniu zestawu SDK. Na przykład:

    <Project>
        <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
        ...
        <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
        <Target Name="BeforeBuild">
            <!-- Insert tasks to run before build here -->
        </Target>
        <Target Name="AfterBuild">
            <!-- Insert tasks to run after build here -->
        </Target>
    </Project>
    

    Należy pamiętać, że Sdk atrybut w elemecie najwyższego poziomu Project został usunięty.

  4. Skompiluj plik projektu.

Tabela wstępnie zdefiniowanych obiektów docelowych

W poniższej tabeli przedstawiono wszystkie elementy docelowe we wspólnych miejscach docelowych, które można zastąpić.

Nazwa docelowa opis
BeforeCompile, AfterCompile Zadania wstawione w jednym z tych obiektów docelowych są uruchamiane przed lub po zakończeniu kompilacji podstawowej. Większość dostosowań odbywa się w jednym z tych dwóch celów.
BeforeBuild, AfterBuild Zadania wstawione w jednym z tych obiektów docelowych będą uruchamiane przed lub po wszystkim innym w kompilacji. Uwaga:BeforeBuild Elementy docelowe i AfterBuild są już zdefiniowane w komentarzach na końcu większości plików projektu, co umożliwia łatwe dodawanie zdarzeń wstępnych i po kompilacji do pliku projektu.
BeforeRebuild, AfterRebuild Zadania wstawione w jednym z tych obiektów docelowych są uruchamiane przed lub po wywołaniu podstawowej funkcji ponownej kompilacji. Kolejność wykonywania docelowego w pliku Microsoft.Common.targets to: BeforeRebuild, Clean, Build, , a następnie AfterRebuild.
BeforeClean, AfterClean Zadania wstawione w jednym z tych obiektów docelowych są uruchamiane przed lub po wywołaniu podstawowej funkcji czyszczenia.
BeforePublish, AfterPublish Zadania wstawione w jednym z tych obiektów docelowych są uruchamiane przed lub po wywołaniu podstawowej funkcji publikowania.
BeforeResolveReferences, AfterResolveReferences Zadania wstawione w jednym z tych obiektów docelowych są uruchamiane przed lub po rozpoznaniu odwołań do zestawu.
BeforeResGen, AfterResGen Zadania wstawione w jednym z tych obiektów docelowych są uruchamiane przed wygenerowaniem lub po wygenerowaniu zasobów.

Istnieje wiele innych obiektów docelowych w systemie kompilacji i zestawie SDK platformy .NET. Zobacz Cele programu MSBuild — zestaw SDK i domyślne cele kompilacji.

Najlepsze rozwiązania dotyczące obiektów docelowych niestandardowych

Właściwości DependsOnTargets i BeforeTargets mogą określać, że element docelowy musi działać przed innym obiektem docelowym, ale są one wymagane w różnych scenariuszach. Różnią się one tym, w którym celu określono wymaganie zależności. Masz kontrolę tylko nad własnymi obiektami docelowymi i nie można bezpiecznie modyfikować obiektów docelowych systemu ani innego zaimportowanego obiektu docelowego, dzięki czemu ograniczenia wyboru metod.

Podczas tworzenia niestandardowego obiektu docelowego postępuj zgodnie z tymi ogólnymi wytycznymi, aby upewnić się, że element docelowy jest wykonywany w zamierzonej kolejności.

  1. Użyj atrybutu DependsOnTargets , aby określić obiekty docelowe, które należy wykonać przed wykonaniem obiektu docelowego. W przypadku łańcucha obiektów docelowych, które kontrolujesz, każdy element docelowy może określić poprzedni element członkowski łańcucha w elemencie DependsOnTargets.

  2. Należy użyć BeforeTargets dla dowolnego obiektu docelowego, który nie jest sterowany przed wykonaniem (na przykład BeforeTargets="PrepareForBuild" dla elementu docelowego, który musi działać na początku kompilacji).

  3. Użyj AfterTargets dla dowolnego obiektu docelowego, który nie kontroluje, że gwarantuje dostępność potrzebnych danych wyjściowych. Na przykład określ AfterTargets="ResolveReferences" element, który zmodyfikuje listę odwołań.

  4. Można ich używać w połączeniu. Na przykład DependsOnTargets="GenerateAssemblyInfo" BeforeTargets="BeforeCompile".

Jawne i niejawne importy

Projekty generowane przez program Visual Studio zwykle używają atrybutu Sdk w elemecie projektu. Te typy projektów są nazywane projektami w stylu zestawu SDK. Zobacz Korzystanie z zestawów SDK projektu MSBuild. Oto przykład:

<Project Sdk="Microsoft.Net.Sdk">

Gdy projekt używa atrybutu Sdk , dwa importy są niejawnie dodawane, jeden na początku pliku projektu i jeden na końcu.

Niejawne importy są równoważne z następującą instrukcją importu, taką jak pierwszy wiersz w pliku projektu, po elemencie Project :

<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />

i następująca instrukcja import jako ostatni wiersz w pliku projektu:

<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />

Ta składnia jest określana jako jawne importy zestawu SDK. Jeśli używasz tej jawnej składni, należy pominąć Sdk atrybut w elemecie projektu.

Importowanie niejawnego zestawu SDK jest równoważne importowaniu określonego "wspólnego" .props lub .targets plików, które są typową konstrukcją w starszych plikach projektu, takich jak:

<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />

oraz

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

Wszelkie takie stare odwołania powinny zostać zastąpione jawną składnią zestawu SDK pokazaną wcześniej w tej sekcji.

Użycie jawnej składni zestawu SDK oznacza, że można dodać własny kod przed pierwszym importem lub po zakończeniu importowania zestawu SDK. Oznacza to, że można zmienić zachowanie, ustawiając właściwości przed pierwszym importem, które zaczną obowiązywać w zaimportowanym .props pliku, i można zastąpić element docelowy zdefiniowany w jednym z plików zestawu SDK .targets po zakończeniu importowania. Korzystając z tej metody, można zastąpić BeforeBuild lub AfterBuild zgodnie z opisem w następnej kolejności.

Następne kroki

Istnieje o wiele więcej możliwości, które można wykonać za pomocą programu MSBuild, aby dostosować kompilację. Zobacz Dostosowywanie kompilacji.