Udostępnij przez


Zadania wbudowane programu MSBuild

Zadania programu MSBuild są zwykle tworzone przez kompilowanie klasy, która implementuje ITask interfejs. Aby uzyskać więcej informacji, zobacz Tasks.

Jeśli chcesz uniknąć nakładu pracy związanego z tworzeniem skompilowanego zadania, możesz utworzyć zadanie wbudowane w pliku projektu lub w zaimportowanym pliku. Nie musisz tworzyć oddzielnego zestawu do hostowania zadania. Użycie wbudowanego zadania ułatwia śledzenie kodu źródłowego i łatwiejsze wdrażanie zadania. Kod źródłowy jest zintegrowany z plikiem projektu MSBuild lub zaimportowanym plikiem, zazwyczaj plikiem .targets .

Tworzysz zadanie wbudowane, korzystając z fabryki zadań kodu. W przypadku bieżącego rozwoju trzeba używać RoslynCodeTaskFactory, a nie CodeTaskFactory. CodeTaskFactory Obsługuje tylko wersje języka C# do wersji 4.0.

Zadania liniowe są przeznaczone dla małych zadań, które nie wymagają skomplikowanych zależności i są wygodne w użyciu. Obsługa debugowania dla zadań wbudowanych jest ograniczona. Zaleca się utworzenie skompilowanego zadania zamiast wbudowanego zadania, gdy chcesz napisać bardziej złożony kod, odwołać się do pakietu NuGet, uruchomić narzędzia zewnętrzne lub wykonać operacje, które mogą powodować błędy. Ponadto wbudowane zadania są kompilowane za każdym razem, gdy tworzysz, więc może to mieć zauważalny wpływ na wydajność kompilacji.

Struktura zadania wbudowanego

Zadanie inline jest zawarte w elemencie UsingTask. Zadanie w linii i element UsingTask, który je zawiera, są zazwyczaj uwzględniane w pliku .targets i importowane do innych plików projektu zgodnie z potrzebami. Oto podstawowe zadanie wbudowane, które nie wykonuje niczego, ale ilustruje składnię:

 <!-- This simple inline task does nothing. -->
  <UsingTask
    TaskName="DoNothing"
    TaskFactory="RoslynCodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" >
    <ParameterGroup />
    <Task>
      <Reference Include="" />
      <Using Namespace="" />
      <Code Type="Fragment" Language="cs">
      </Code>
    </Task>
  </UsingTask>

Element UsingTask w przykładzie ma trzy atrybuty opisujące zadanie i wbudowaną fabrykę zadań, która ją kompiluje.

  • Atrybut TaskName nazywa zadanie , w tym przypadku DoNothing.

  • Atrybut TaskFactory nazywa klasę, która implementuje fabrykę zadań inline.

  • Atrybut AssemblyFile podaje lokalizację zintegrowanej fabryki zadań. Alternatywnie można użyć atrybutu AssemblyName, aby określić w pełni kwalifikowaną nazwę klasy fabryki zadań inline, która zazwyczaj znajduje się w $(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll.

Pozostałe elementy DoNothing zadania są puste i są udostępniane w celu zilustrowania kolejności i struktury zadania wbudowanego. Kompletny przykład zostanie przedstawiony w dalszej części tego artykułu.

  • Element ParameterGroup jest opcjonalny. Gdy zostanie określony, deklaruje parametry zadania. Aby uzyskać więcej informacji na temat parametrów wejściowych i wyjściowych, zobacz Parametry wejściowe i wyjściowe w dalszej części tego artykułu.

  • Element Task opisuje i zawiera kod źródłowy zadania.

  • Element Reference określa odwołania do zestawów .NET używanych w kodzie. Użycie tego elementu jest równoważne do dodawania odwołania do projektu w programie Visual Studio. Atrybut Include określa ścieżkę przywołytowanego zestawu. Zestawy w mscorlib, .NET Standard, Microsoft.Build.Framework i Microsoft.Build.Utilities.Core, a także niektóre zestawy, które są tranzytywnie przywoływane jako zależności, są dostępne bez elementu Reference.

  • Element Using zawiera listę przestrzeni nazw, do których chcesz uzyskać dostęp. Ten element jest odpowiednikiem using dyrektywy w języku C#. Atrybut Namespace określa przestrzeń nazw do uwzględnienia. Nie można umieścić dyrektywy using w kodzie wbudowanym, ponieważ ten kod znajduje się w treści metody, gdzie dyrektywy using nie są dozwolone.

Reference i Using elementy są niezależne od języka. Zadania wbudowane można pisać w języku Visual Basic lub C#.

Uwaga / Notatka

Elementy zawarte w elemencie Task są specyficzne dla fabryki zadań, w tym przypadku fabryki zadań dla kodu.

Element kodu

Ostatnim elementem podrzędnym, który ma pojawić się w elemencie Task, jest element Code. Element Code zawiera lub lokalizuje kod, który chcesz skompilować w zadaniu. To, co umieścisz w elemenie Code , zależy od tego, jak chcesz napisać zadanie.

Atrybut Language określa język, w którym jest napisany kod. Dopuszczalne wartości to cs C#, vb dla języka Visual Basic.

Atrybut Type określa typ kodu, który znajduje się w elemecie Code .

  • Jeśli wartość Type to Class, Code element zawiera kod klasy pochodzącej z interfejsu ITask .

  • Jeśli wartość Type to Method, kod definiuje przesłonięcie metody Execute interfejsu ITask.

  • Jeśli wartość Type to Fragment, kod definiuje zawartość Execute metody, ale nie podpis lub instrukcję return .

Sam kod zwykle pojawia się między znacznikiem <![CDATA[ a znacznikiem ]]> . Ponieważ kod znajduje się w sekcji CDATA, nie musisz martwić się o ucieczkę zastrzeżonych znaków, na przykład "<" lub ">".

Alternatywnie możesz użyć Source atrybutu Code elementu, aby określić lokalizację pliku zawierającego kod zadania. Kod w pliku źródłowym musi być typu określonego przez atrybut Type. Source Jeśli atrybut jest obecny, wartość domyślna to TypeClass. Jeśli Source nie ma wartości, wartość domyślna to Fragment.

Uwaga / Notatka

Podczas definiowania klasy zadań w pliku źródłowym nazwa klasy musi być zgodna z atrybutem TaskName odpowiedniego elementu UsingTask .

HelloWorld

Oto przykład prostego zadania wbudowanego. Zadanie HelloWorld wyświetla komunikat "Hello, world!" na domyślnym urządzeniu rejestrowania błędów, które jest zazwyczaj konsolą systemową lub oknem danych wyjściowych programu Visual Studio.

<Project>
  <!-- This simple inline task displays "Hello, world!" -->
  <UsingTask
    TaskName="HelloWorld"
    TaskFactory="RoslynCodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" >
    <ParameterGroup />
    <Task>
      <Using Namespace="System"/>
      <Using Namespace="System.IO"/>
      <Code Type="Fragment" Language="cs">
<![CDATA[
// Display "Hello, world!"
Log.LogError("Hello, world!");
]]>
      </Code>
    </Task>
  </UsingTask>
</Project>

Zadanie HelloWorld można zapisać w pliku o nazwie HelloWorld.targets, a następnie wywołać je z projektu w następujący sposób.

<Project>
  <Import Project="HelloWorld.targets" />
  <Target Name="Hello">
    <HelloWorld />
  </Target>
</Project>

Parametry wejściowe i wyjściowe

Wbudowane parametry zadania to elementy podrzędne elementu ParameterGroup. Każdy parametr przyjmuje nazwę elementu, który go definiuje. Poniższy kod definiuje parametr Text.

<ParameterGroup>
  <Text />
</ParameterGroup>

Parametry mogą mieć co najmniej jeden z następujących atrybutów:

  • Required jest opcjonalnym atrybutem, który jest false domyślnie. Jeśli trueparametr jest wymagany i musi mieć wartość przed wywołaniem zadania.
  • ParameterType jest opcjonalnym atrybutem, który jest System.String domyślnie. Można go ustawić na dowolny w pełni kwalifikowany typ, który jest elementem lub wartością, którą można przekonwertować na i z ciągu przy użyciu polecenia ChangeType. (Innymi słowy, dowolny typ, który można przekazać do i z zadania zewnętrznego).
  • Output jest opcjonalnym atrybutem, który jest false domyślnie. Jeśli true, wtedy parametr musi zostać podany wartość przed zwracaniem z metody Execute.

Na przykład

<ParameterGroup>
  <Expression Required="true" />
  <Files ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
  <Tally ParameterType="System.Int32" Output="true" />
</ParameterGroup>

definiuje następujące trzy parametry:

  • Expression jest wymaganym parametrem wejściowym typu System.String.

  • Files jest wymaganym parametrem wejściowym listy elementów.

  • Tally jest parametrem wyjściowym typu System.Int32.

Code Jeśli element ma Type atrybut Fragment lub Method, właściwości są tworzone automatycznie dla każdego parametru. W przeciwnym razie właściwości muszą być jawnie zadeklarowane w kodzie źródłowym zadania i muszą dokładnie odpowiadać ich definicjom parametrów.

Debugowanie zadania wbudowanego

Program MSBuild generuje plik źródłowy zadania wbudowanego i zapisuje dane wyjściowe w pliku tekstowym z nazwą pliku GUID w folderze plików tymczasowych AppData\Local\Temp\MSBuildTemp. Dane wyjściowe są zwykle usuwane, ale aby zachować ten plik wyjściowy, można ustawić zmienną środowiskową MSBUILDLOGCODETASKFACTORYOUTPUT na 1.

Przykład 1

Następujące zadanie wbudowane zastępuje każde wystąpienie tokenu w danym pliku daną wartością.

<Project>

  <UsingTask TaskName="TokenReplace" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
    <ParameterGroup>
      <Path ParameterType="System.String" Required="true" />
      <Token ParameterType="System.String" Required="true" />
      <Replacement ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Code Type="Fragment" Language="cs"><![CDATA[
string content = File.ReadAllText(Path);
content = content.Replace(Token, Replacement);
File.WriteAllText(Path, content);

]]></Code>
    </Task>
  </UsingTask>

  <Target Name='Demo' >
    <TokenReplace Path="Target.config" Token="$MyToken$" Replacement="MyValue"/>
  </Target>
</Project>

Przykład 2

Następujące zadanie wbudowane generuje serializowane dane wyjściowe. W tym przykładzie pokazano użycie parametru wyjściowego i odwołania.

<Project>
  <PropertyGroup>
    <RoslynCodeTaskFactoryAssembly Condition="$(RoslynCodeTaskFactoryAssembly) == ''">$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll</RoslynCodeTaskFactoryAssembly>
  </PropertyGroup>

    <UsingTask 
    TaskName="MyInlineTask" 
    TaskFactory="RoslynCodeTaskFactory" 
    AssemblyFile="$(RoslynCodeTaskFactoryAssembly)">
    <ParameterGroup>
      <Input ParameterType="System.String" Required="true" />
      <Output ParameterType="System.String" Output="true" />
    </ParameterGroup>
    <Task>
      <Reference Include="System.Text.Json" /> <!-- Reference an assembly -->
      <Using Namespace="System.Text.Json" />   <!-- Use a namespace -->
      <Code Type="Fragment" Language="cs">
        <![CDATA[
          Output = JsonSerializer.Serialize(new { Message = Input });
        ]]>
      </Code>
    </Task>
  </UsingTask>

  <Target Name="RunInlineTask">
    <MyInlineTask Input="Hello, Roslyn!" >
      <Output TaskParameter="Output" PropertyName="SerializedOutput" />
    </MyInlineTask>
    <Message Text="Serialized Output: $(SerializedOutput)" />
  </Target>
</Project>