Creare un'attività inline di MSBuild con RoslynCodeTaskFactory
Analogamente all'attività CodeTaskFactory, l'attività RoslynCodeTaskFactory usa i compilatori Roslyn multipiattaforma per generare assembly di attività in memoria da usare come attività inline. Le attività RoslynCodeTaskFactory sono destinate a .NET Standard e possono funzionare in runtime .NET Framework e .NET Core, nonché in altre piattaforme come Linux e macOS.
Nota
RoslynCodeTaskFactory è disponibile solo in MSBuild 15.8 e versioni successive. Le versioni di MSBuild seguono le versioni di Visual Studio, quindi RoslynCodeTaskFactory è disponibile in Visual Studio 2017 versione 15.8 e successive.
Struttura di un'attività inline con RoslynCodeTaskFactory
Le attività inline RoslynCodeTaskFactory vengono dichiarate nello stesso modo delle attività CodeTaskFactory. L'unica differenza è che le prime sono destinate a .NET Standard. L'attività inline e l'elemento UsingTask
che la contiene sono generalmente inclusi in un file con estensione targets e all'occorrenza vengono importati in altri file di progetto. Di seguito è riportata un'attività inline di base. Si noti che non esegue alcuna operazione.
<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>
L'elemento UsingTask
nell'esempio ha tre attributi che descrivono l'attività e la factory dell'attività inline che la compila.
L'attributo
TaskName
assegna un nome all'attività, in questo casoDoNothing
.L'attributo
TaskFactory
assegna un nome alla classe che implementa la factory dell'attività inline.L'attributo
AssemblyFile
assegna la posizione della factory dell'attività inline. In alternativa, è possibile usare l'attributoAssemblyName
per specificare il nome completo della classe factory dell'attività inline, che in genere si trova nella Global Assembly Cache (GAC).
Gli elementi rimanenti dell'attività DoNothing
sono vuoti e vengono specificati per illustrare l'ordine e la struttura di un'attività inline. Un esempio più concreto è riportato più avanti in questo argomento.
L'elemento
ParameterGroup
è facoltativo. Se specificato, consente di dichiarare i parametri per l'attività. Per altre informazioni sui parametri di input e output, vedere Parametri di input e output più avanti in questo argomento.L'elemento
Task
descrive e contiene il codice sorgente dell'attività.L'elemento
Reference
specifica i riferimenti agli assembly .NET usati nel codice. Equivale all'aggiunta di un riferimento a un progetto in Visual Studio. L'attributoInclude
specifica il percorso dell'assembly di riferimento.L'elemento
Using
indica gli spazi dei nomi a cui si vuole accedere. Ricorda l'istruzioneUsing
in Visual C#. L'attributoNamespace
specifica lo spazio dei nomi da includere.
Gli elementi Reference
e Using
sono indipendenti dal linguaggio di programmazione. Le attività inline possono essere scritte in uno dei linguaggi .NET CodeDOM supportati, ad esempio Visual Basic, Visual C#.
Nota
Gli elementi contenuti in Task
sono specifici della factory dell'attività, in questo caso la factory dell'attività del codice.
Elemento coda
L'ultimo elemento figlio visualizzato all'interno dell'elemento Task
è l'elemento Code
. L'elemento Code
contiene o individua il codice che deve essere compilato in un'attività. Ciò che si inserisce nell'elemento Code
dipende da come si vuole scrivere l'attività.
L'attributo Language
specifica il linguaggio di programmazione in cui è scritto il codice. I valori accettabili sono cs
per C#, vb
per Visual Basic.
L'attributo Type
specifica il tipo di codice rilevato nell'elemento Code
.
Se il valore di
Type
èClass
, l'elementoCode
contiene il codice per una classe che deriva dall'interfaccia ITask.Se il valore di
Type
èMethod
, il codice definisce un override del metodoExecute
dell'interfaccia ITask.Se il valore di
Type
èFragment
, il codice definisce il contenuto del metodoExecute
ma non la firma o l'istruzionereturn
.
Il codice in genere è visualizzato tra un indicatore <![CDATA[
e un indicatore ]]>
. Poiché il codice si trova in una sezione CDATA, non è necessario preoccuparsi dell'escape dei caratteri riservati, ad esempio "<" o ">".
In alternativa, è possibile usare l'attributo Source
dell'elemento Code
per specificare il percorso di un file che contiene il codice per l'attività. Il codice nel file di origine deve essere del tipo specificato dall'attributo Type
. Se l'attributo Source
è presente, il valore predefinito di Type
è Class
. Se Source
non è presente, il valore predefinito è Fragment
.
Nota
Quando si definisce la classe dell'attività nel file di origine il nome della classe deve concordare con l'attributo TaskName
dell'elemento UsingTask corrispondente.
Hello World
Di seguito è riportata un'attività inline più efficiente con RoslynCodeTaskFactory. L'attività HelloWorld visualizza "Hello, world!" nel dispositivo di registrazione degli errori predefinito, che in genere è la console di sistema o la finestra Output di Visual Studio. L'elemento Reference
dell'esempio è incluso solo ai fini della spiegazione.
<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>
È possibile salvare l'attività HelloWorld in un file denominato HelloWorld.targets e richiamarla da un progetto come indicato di seguito.
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="HelloWorld.targets" />
<Target Name="Hello">
<HelloWorld />
</Target>
</Project>
Parametri di input e output
I parametri dell'attività inline sono elementi figlio di un elemento ParameterGroup
. Ogni parametro accetta il nome dell'elemento che lo definisce. Il codice seguente definisce il parametro Text
.
<ParameterGroup>
<Text />
</ParameterGroup>
I parametri possono avere uno o più degli attributi seguenti:
Required
è un attributo facoltativo che èfalse
per impostazione predefinita. Setrue
, il parametro è obbligatorio e deve avere un valore assegnato prima di chiamare l'attività.ParameterType
è un attributo facoltativo che èSystem.String
per impostazione predefinita. Può essere impostato su qualsiasi tipo completo che sia un elemento o un valore che può essere convertito in e da una stringa usando System.Convert.ChangeType. In altre parole, qualsiasi tipo che può essere passato a e da un'attività esterna.Output
è un attributo facoltativo che èfalse
per impostazione predefinita. Setrue
, il parametro deve avere un valore assegnato prima della restituzione da parte del metodo Execute.
ad esempio:
<ParameterGroup>
<Expression Required="true" />
<Files ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
<Tally ParameterType="System.Int32" Output="true" />
</ParameterGroup>
definisce questi tre parametri:
Expression
è un parametro di input obbligatorio del tipo System.String.Files
è un parametro di input obbligatorio dell'elenco di elementi.Tally
è un parametro di output del tipo System.Int32.
Se l'elemento Code
ha come attributo Type
Fragment
o Method
, le proprietà vengono create automaticamente per ogni parametro. In RoslynCodeTaskFactory, se l'elemento Code
ha l'attributo Type
di Class
, non è necessario specificare ParameterGroup
, poiché viene dedotto dal codice sorgente (si tratta di una differenza rispetto a CodeTaskFactory
). In caso contrario, le proprietà devono essere dichiarate in modo esplicito nel codice sorgente dell'attività e devono corrispondere esattamente alle relative definizioni di parametro.
Esempio
L'attività inline seguente registra alcuni messaggi e restituisce una stringa.
<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>
Queste attività inline possono combinare i percorsi e ottenere il nome del file.
<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>
Garantire la compatibilità con le versioni precedenti
RoslynCodeTaskFactory
è diventato disponibile per la prima volta in MSBuild versione 15.8. Si supponga di avere una situazione in cui si desidera supportare le versioni precedenti di Visual Studio e MSBuild, quando RoslynCodeTaskFactory
non era disponibile, ma CodeTaskFactory
si vuole usare lo stesso script di compilazione. È possibile usare un Choose
costrutto che usa la $(MSBuildVersion)
proprietà per decidere in fase di compilazione se usare o eseguire il RoslynCodeTaskFactory
fallback a CodeTaskFactory
, come nell'esempio seguente:
<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>