Partager via


Créer une tâche inline MSBuild avec RoslynCodeTaskFactory

RoslynCodeTaskFactory utilise les compilateurs Roslyn multiplateformes pour générer des assemblys de tâches en mémoire à utiliser comme tâches inline. RoslynCodeTaskFactory les tâches ciblent .NET Standard et peuvent fonctionner sur les runtimes .NET Framework et .NET Core, ainsi que d’autres plateformes telles que Linux et macOS.

Remarque

RoslynCodeTaskFactory est disponible uniquement dans MSBuild 15.8 et versions ultérieures. Les versions de MSBuild suivent les versions de Visual Studio. RoslynCodeTaskFactory est donc disponible dans Visual Studio 2017 version 15.8 et versions ultérieures.

Structure d’une tâche inline avec RoslynCodeTaskFactory

RoslynCodeTaskFactory Les tâches inline sont déclarées à l’aide de l’élément UsingTask . La tâche inline et l’élément UsingTask qui le contient sont généralement inclus dans un .targets fichier et importés dans d’autres fichiers projet en fonction des besoins. Voici une tâche inline de base. Notez qu’il ne fait rien.

<Project>
  <!-- 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>
</Project>

L’élément UsingTask de l’exemple comporte trois attributs qui décrivent la tâche et la fabrique de tâches inline qui la compile.

  • L’attribut TaskName nomme la tâche, dans ce cas, DoNothing.

  • L’attribut TaskFactory nomme la classe qui implémente la fabrique de tâches inline.

  • L’attribut AssemblyFile donne l’emplacement de la fabrique de tâches inline. Vous pouvez également utiliser l’attribut AssemblyName pour spécifier le nom complet de la classe de fabrique de tâches inline, qui se trouve généralement dans le Global Assembly Cache (GAC).

Les éléments restants de la DoNothing tâche sont vides et sont fournis pour illustrer l’ordre et la structure d’une tâche inline. Un exemple plus robuste est présenté plus loin dans cet article.

  • L’élément ParameterGroup est facultatif. Lorsqu’elle est spécifiée, elle déclare les paramètres de la tâche. Pour plus d’informations sur les paramètres d’entrée et de sortie, consultez Les paramètres d’entrée et de sortie plus loin dans cet article.

  • L’élément Task décrit et contient le code source de la tâche.

  • L’élément Reference spécifie des références aux assemblys .NET que vous utilisez dans votre code. Cela équivaut à ajouter une référence à un projet dans Visual Studio. L’attribut Include spécifie le chemin d’accès de l’assembly référencé.

  • L’élément Using répertorie les espaces de noms auxquels vous souhaitez accéder. Cet élément ressemble à la using directive en Visual C#. L’attribut Namespace spécifie l’espace de noms à inclure.

Reference et Using les éléments sont indépendant du langage. Les tâches inline peuvent être écrites dans l’un des langages .NET CodeDom pris en charge, par exemple Visual Basic ou Visual C#.

Remarque

Les éléments contenus par l’élément Task sont spécifiques à la fabrique de tâches, dans ce cas, la fabrique de tâches de code.

Élément Code

Le dernier élément enfant à afficher dans l’élément Task est l’élément Code . L’élément Code contient ou localise le code à compiler dans une tâche. Ce que vous placez dans l’élément Code dépend de la façon dont vous souhaitez écrire la tâche.

L’attribut Language spécifie la langue dans laquelle votre code est écrit. Les valeurs acceptables sont cs pour C#, vb pour Visual Basic.

L’attribut Type spécifie le type de code trouvé dans l’élément Code .

  • Si la valeur est TypeClass, l’élément contient du Code code pour une classe qui dérive de l’interface ITask .

  • Si la valeur est TypeMethod, le code définit une substitution de la Execute méthode de l’interface ITask .

  • Si la valeur est TypeFragment, le code définit le contenu de la Execute méthode, mais pas la signature ou l’instruction return .

Le code lui-même apparaît généralement entre un <![CDATA[ marqueur et un ]]> marqueur. Étant donné que le code se trouve dans une section CDATA, vous n’avez pas à vous soucier de l’échappement de caractères réservés, par exemple «< » ou «> ».

Vous pouvez également utiliser l’attribut Source de l’élément Code pour spécifier l’emplacement d’un fichier qui contient le code de votre tâche. Le code du fichier source doit être du type spécifié par l’attribut Type . Si l’attribut Source est présent, la valeur Type par défaut est Class. S’il Source n’est pas présent, la valeur par défaut est Fragment.

Remarque

Si vous définissez la classe de tâche dans un fichier source, le nom de la classe doit accepter l’attribut TaskName de l’élément UsingTask correspondant.

Salut tout le monde

Voici une tâche inline plus robuste avec RoslynCodeTaskFactory. La tâche HelloWorld affiche « Hello, world ! » sur l’appareil de journalisation des erreurs par défaut, qui est généralement la console système ou la fenêtre sortie de Visual Studio. L’élément Reference de l’exemple est inclus uniquement pour l’illustration.

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

Vous pouvez enregistrer la HelloWorld tâche dans un fichier nommé HelloWorld.targets, puis l’appeler à partir d’un projet comme suit.

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

Paramètres d’entrée et de sortie

Les paramètres de tâche inline sont des éléments enfants d’un ParameterGroup élément. Chaque paramètre prend le nom de l’élément qui le définit. Le code suivant définit le paramètre Text.

<ParameterGroup>
    <Text />
</ParameterGroup>

Les paramètres peuvent avoir un ou plusieurs de ces attributs :

  • Required est un attribut facultatif qui est false par défaut. Si true, le paramètre est requis et doit avoir une valeur avant d’appeler la tâche.

  • ParameterType est un attribut facultatif qui est System.String par défaut. Il peut être défini sur n’importe quel type qualifié complet qui est un élément ou une valeur qui peut être converti en et à partir d’une chaîne à l’aide de System.Convert.ChangeType. (En d’autres termes, tout type pouvant être passé à une tâche externe et à partir d’une tâche externe).)

  • Output est un attribut facultatif qui est false par défaut. Si true, le paramètre doit recevoir une valeur avant de retourner à partir de la méthode Execute.

Par exemple,

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

définit ces trois paramètres :

  • Expression est un paramètre d’entrée obligatoire de type System.String.

  • Files est un paramètre d’entrée de liste d’éléments requis.

  • Tally est un paramètre de sortie de type System.Int32.

Si l’élément Code a l’attribut Type ou FragmentMethod, les propriétés sont créées automatiquement pour chaque paramètre. Dans RoslynCodeTaskFactory, si l’élément Code a l’attribut TypeClass, vous n’avez pas à spécifier le ParameterGroup, car il est déduit du code source (il s’agit d’une différence par rapport CodeTaskFactoryà ). Sinon, les propriétés doivent être déclarées explicitement dans le code source de la tâche et doivent correspondre exactement à leurs définitions de paramètres.

Exemple :

La tâche inline suivante enregistre certains messages et retourne une chaîne.

<Project>

    <UsingTask TaskName="MySample"
               TaskFactory="RoslynCodeTaskFactory"
               AssemblyFile="$(MSBuildBinPath)\Microsoft.Build.Tasks.Core.dll">
        <ParameterGroup>
            <Parameter1 ParameterType="System.String" Required="true" />
            <Parameter2 ParameterType="System.String" />
            <Parameter3 ParameterType="System.String" Output="true" />
        </ParameterGroup>
        <Task>
            <Using Namespace="System" />
            <Code Type="Fragment" Language="cs">
              <![CDATA[
              Log.LogMessage(MessageImportance.High, "Hello from an inline task created by Roslyn!");
              Log.LogMessageFromText($"Parameter1: '{Parameter1}'", MessageImportance.High);
              Log.LogMessageFromText($"Parameter2: '{Parameter2}'", MessageImportance.High);
              Parameter3 = "A value from the Roslyn CodeTaskFactory";
            ]]>
            </Code>
        </Task>
    </UsingTask>

    <Target Name="Demo">
      <MySample Parameter1="A value for parameter 1" Parameter2="A value for parameter 2">
          <Output TaskParameter="Parameter3" PropertyName="NewProperty" />
      </MySample>

      <Message Text="NewProperty: '$(NewProperty)'" />
    </Target>
</Project>

Ces tâches inline peuvent combiner des chemins d’accès et obtenir le nom du fichier.

<Project>

    <UsingTask TaskName="PathCombine"
               TaskFactory="RoslynCodeTaskFactory"
               AssemblyFile="$(MSBuildBinPath)\Microsoft.Build.Tasks.Core.dll">
        <ParameterGroup>
            <Paths ParameterType="System.String[]" Required="true" />
            <Combined ParameterType="System.String" Output="true" />
        </ParameterGroup>
        <Task>
            <Using Namespace="System" />
            <Code Type="Fragment" Language="cs">
            <![CDATA[
            Combined = Path.Combine(Paths);
            ]]>
            </Code>
        </Task>
    </UsingTask>

    <UsingTask TaskName="PathGetFileName"
             TaskFactory="RoslynCodeTaskFactory"
             AssemblyFile="$(MSBuildBinPath)\Microsoft.Build.Tasks.Core.dll">
        <ParameterGroup>
            <Path ParameterType="System.String" Required="true" />
            <FileName ParameterType="System.String" Output="true" />
        </ParameterGroup>
        <Task>
            <Using Namespace="System" />
            <Code Type="Fragment" Language="cs">
            <![CDATA[
            FileName = System.IO.Path.GetFileName(Path);
            ]]>
            </Code>
        </Task>
    </UsingTask>

    <Target Name="Demo">
        <PathCombine Paths="$(Temp);MyFolder;$([System.Guid]::NewGuid()).txt">
            <Output TaskParameter="Combined" PropertyName="MyCombinedPaths" />
        </PathCombine>

        <Message Text="Combined Paths: '$(MyCombinedPaths)'" />

        <PathGetFileName Path="$(MyCombinedPaths)">
            <Output TaskParameter="FileName" PropertyName="MyFileName" />
        </PathGetFileName>

        <Message Text="File name: '$(MyFileName)'" />
    </Target>
</Project>

Fournir une compatibilité descendante

RoslynCodeTaskFactory la première est devenue disponible dans MSBuild version 15.8. Supposons que vous souhaitez prendre en charge les versions précédentes de Visual Studio et MSBuild, quand elle RoslynCodeTaskFactory n’était pas disponible, mais CodeTaskFactory que vous souhaitez utiliser le même script de génération. Vous pouvez utiliser une Choose construction qui utilise la propriété pour décider au moment de la $(MSBuildVersion) génération s’il faut utiliser ou RoslynCodeTaskFactory revenir à CodeTaskFactoryl’exemple suivant :

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>

  <Choose>
    <When Condition=" '$(MSBuildVersion.Substring(0,2))' >= 16 Or
    ('$(MSBuildVersion.Substring(0,2))' == 15 And '$(MSBuildVersion.Substring(3,1))' >= 8)">
      <PropertyGroup>
        <TaskFactory>RoslynCodeTaskFactory</TaskFactory>
      </PropertyGroup>
    </When>
    <Otherwise>
      <PropertyGroup>
        <TaskFactory>CodeTaskFactory</TaskFactory>
      </PropertyGroup>
    </Otherwise>
  </Choose>
  
  <UsingTask
    TaskName="HelloWorld"
    TaskFactory="$(TaskFactory)"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
    <ParameterGroup />
    <Task>
      <Using Namespace="System"/>
      <Using Namespace="System.IO"/>
      <Code Type="Fragment" Language="cs">
        <![CDATA[
         Log.LogError("Using RoslynCodeTaskFactory");
      ]]>
      </Code>
    </Task>
  </UsingTask>

  <Target Name="RunTask" AfterTargets="Build">
    <Message Text="MSBuildVersion: $(MSBuildVersion)"/>
    <Message Text="TaskFactory: $(TaskFactory)"/>
    <HelloWorld />
  </Target>

</Project>