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
iAfterTargets
.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
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ępowanychDependsOn
właściwości.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.Zdefiniuj niestandardowe obiekty docelowe przed lub po definicji właściwości.
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->'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>
<Copy SourceFiles="@(_FilesToCopy)"
DestinationFiles="@(_FilesToCopy->'$(_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
Jeśli projekt używa atrybutu
Sdk
, zmień go na jawną składnię importu. Zobacz Jawne i niejawne importy.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ć.
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 poziomuProject
został usunięty.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.
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 elemencieDependsOnTargets
.Należy użyć
BeforeTargets
dla dowolnego obiektu docelowego, który nie jest sterowany przed wykonaniem (na przykładBeforeTargets="PrepareForBuild"
dla elementu docelowego, który musi działać na początku kompilacji).Użyj
AfterTargets
dla dowolnego obiektu docelowego, który nie kontroluje, że gwarantuje dostępność potrzebnych danych wyjściowych. Na przykład określAfterTargets="ResolveReferences"
element, który zmodyfikuje listę odwołań.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.