Creación de una tarea insertada de MSBuild con RoslynCodeTaskFactory
De forma similar a CodeTaskFactory, RoslynCodeTaskFactory usa los compiladores de Roslyn multiplataforma para generar ensamblados de la tarea en memoria para su uso como tareas insertadas. Las tareas RoslynCodeTaskFactory establecen como destino .NET Standard y pueden funcionar en los runtimes de .NET Framework y .NET Core, así como con otras plataformas como Linux y macOS.
Nota
La tarea RoslynCodeTaskFactory solo está disponible en MSBuild 15.8 y versiones posteriores. Las versiones de MSBuild siguen a las versiones de Visual Studio, por lo que RoslynCodeTaskFactory está disponible en Visual Studio 2017, versión 15.8 y posteriores.
La estructura de una tarea insertada con RoslynCodeTaskFactory
Las tareas insertadas con RoslynCodeTaskFactory se declaran de forma idéntica a CodeTaskFactory, con la única diferencia de que el destino es .NET Standard. La tarea insertada y el elemento UsingTask
que la contiene se suelen incluir en un archivo .targets y se importan en otros archivos de proyecto según se requiera. A continuación se muestra una tarea insertada básica. Observe que no se realiza ninguna acción.
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- 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>
El elemento UsingTask
del ejemplo tiene tres atributos que describen la tarea y el generador de tareas insertadas que lo compila.
El atributo
TaskName
asigna un nombre a la tarea, en este caso,DoNothing
.El atributo
TaskFactory
asigna un nombre a la clase que implementa el generador de tareas insertadas.El atributo
AssemblyFile
proporciona la ubicación del generador de tareas insertadas. Alternativamente, puede utilizar el atributoAssemblyName
para especificar el nombre completo de la clase de generador de tareas insertadas, que está ubicada normalmente en la caché global de ensamblados (GAC).
Los elementos restantes de la tarea DoNothing
están vacíos y se proporcionan para mostrar el orden y la estructura de una tarea insertada. Un ejemplo más completo se presenta posteriormente en este tema.
El elemento
ParameterGroup
es opcional. Cuando se especifica, declara los parámetros para la tarea. Para obtener más información sobre los parámetros de entrada y salida, vea Parámetros de entrada y salida más adelante en este tema.El elemento
Task
describe y contiene el código fuente de la tarea.El elemento
Reference
especifica las referencias a los ensamblados de .NET que se utilizan en el código. Esto es equivalente a agregar una referencia a un proyecto en Visual Studio. El atributoInclude
especifica la ruta de acceso del ensamblado al que se hace referencia.El elemento
Using
enumera los espacios de nombres a los que se desea obtener acceso. Esto es similar a la instrucciónUsing
de Visual C#. El atributoNamespace
especifica el espacio de nombres que se va a incluir.
Los elementos Reference
y Using
son independientes del lenguaje. Las tareas insertadas se pueden escribir en cualquiera de los lenguajes CodeDom para .NET admitidos, por ejemplo, Visual Basic o Visual C#.
Nota
Los elementos contenidos en el elemento Task
son específicos del generador de tareas, en este caso, el generador de tareas de código.
Elemento de código
El último elemento secundario que aparece dentro del elemento Task
es el elemento Code
. El elemento Code
contiene o localiza el código que se desea compilar en una tarea. Lo que se incluye en el elemento Code
depende de cómo se desea escribir la tarea.
El atributo Language
especifica el lenguaje en el que se escribe el código. Los valores aceptables son cs
para C#, vb
para Visual Basic.
El atributo Type
especifica el tipo de código que se encuentra en el elemento Code
.
Si el valor de
Type
esClass
, el elementoCode
contiene el código para una clase que deriva de la interfaz ITask.Si el valor de
Type
esMethod
, el código define un reemplazo del métodoExecute
de la interfaz ITask.Si el valor de
Type
esFragment
, el código define el contenido del métodoExecute
, pero no la firma o la instrucciónreturn
.
El propio código aparece normalmente entre un marcador <![CDATA[
y un marcador ]]>
. Dado que el código está en una sección CDATA, no tiene que preocuparse de anteponer caracteres de escape a los caracteres reservados, como "<" o ">".
Alternativamente, puede utilizar el atributo Source
del elemento Code
para especificar la ubicación de un archivo que contiene el código para la tarea. El código del archivo de código fuente debe ser del tipo especificado por el atributo Type
. Si el atributo Source
está presente, el valor predeterminado de Type
es Class
. Si Source
no está presente, el valor predeterminado es Fragment
.
Nota
Al definir la clase de tarea en el archivo de origen, el nombre de clase debe corresponder al atributo TaskName
del elemento UsingTask correspondiente.
Hello World
Esta es una tarea insertada más sólida con RoslynCodeTaskFactory. La tarea HelloWorld muestra "Hello, world!" en el dispositivo de registro de errores predeterminado, que suele ser la consola del sistema o la ventana Salida de Visual Studio. El elemento Reference
del ejemplo se incluye solamente a efectos de ilustración.
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- 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>
Puede guardar la tarea HelloWorld en un archivo denominado HelloWorld.targets y luego invocarlo desde un proyecto del modo siguiente.
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="HelloWorld.targets" />
<Target Name="Hello">
<HelloWorld />
</Target>
</Project>
Parámetros de entrada y salida
Los parámetros de las tareas insertadas son elementos secundarios de un elemento ParameterGroup
. Cada parámetro toma el nombre del elemento que lo define. En el código siguiente se define el parámetro Text
.
<ParameterGroup>
<Text />
</ParameterGroup>
Los parámetros pueden tener uno o más de estos atributos:
Required
es un atributo opcional que esfalse
de forma predeterminada. Si estrue
, el parámetro es necesario y se le debe proporcionar un valor antes de llamar a la tarea.ParameterType
es un atributo opcional que esSystem.String
de forma predeterminada. Se puede establecer en cualquier tipo completo que sea un elemento o un valor que se pueda convertir a y de una cadena utilizando System.Convert.ChangeType. (En otras palabras, cualquier tipo que se pueda pasar a y desde una tarea externa).Output
es un atributo opcional que esfalse
de forma predeterminada. Si estrue
, se debe proporcionar al parámetro un valor antes de volver del método Execute.
Por ejemplo,
<ParameterGroup>
<Expression Required="true" />
<Files ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
<Tally ParameterType="System.Int32" Output="true" />
</ParameterGroup>
define estos tres parámetros:
Expression
es un parámetro de entrada necesario de tipo System.String.Files
es un parámetro de entrada de lista de elementos necesario.Tally
es un parámetro de salida de tipo System.Int32.
Si el elemento Code
tiene el atributo Type
de Fragment
o Method
, las propiedades se crean automáticamente para cada parámetro. En RoslynCodeTaskFactory, si el elemento Code
tiene el atributo Type
de Class
, no es necesario especificar ParameterGroup
, ya que se deduce del código fuente (esto es una diferencia con respecto a CodeTaskFactory
). De lo contrario, las propiedades se deben declarar explícitamente en el código fuente de la tarea y deben coincidir exactamente con sus definiciones de parámetro.
Ejemplo
La tarea insertada siguiente registra algunos mensajes y devuelve una cadena.
<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="15.0">
<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>
Estas tareas insertadas pueden combinar rutas y obtener el nombre de archivo.
<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="15.0">
<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>
Proporcionar compatibilidad con versiones anteriores
RoslynCodeTaskFactory
está disponible por primera vez en la versión 15.8 de MSBuild. Imaginemos un escenario en el que quiere admitir versiones anteriores de Visual Studio y MSBuild, cuando RoslynCodeTaskFactory
no estaba disponible, pero CodeTaskFactory
sí lo estaba, pero quiere usar el mismo script de compilación. Puede usar una construcción Choose
que use la propiedad $(MSBuildVersion)
para decidir en tiempo de compilación si usar RoslynCodeTaskFactory
o revertir a CodeTaskFactory
, como en el ejemplo siguiente:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</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>