Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing array to custom MSBuild task

I thought this would be quite simple but then realised that I couldnt find any information on it anywhere.

I have a custom task like so:

public class MyCustomTask : Task
{
    [Required]
    public string[] SomeStrings {get;set;}

    public override bool Execute()
    {
        // Do something with strings...
    }
}

The matching MSBuild stuff is basically like so:

<UsingTask TaskName="MyCustomTask" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildBinPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
      <SomeStrings ParameterType="System.String[]" Required="true" />
    </ParameterGroup>
    <Task>
    ... 
    </Task>
</UsingTask>

<Target Name="DoSomething">
    <MyCustomTask SomeStrings="????" />
</Target>

Dont have any idea of what to put in the SomeStrings parameter, thought maybe it would understand if I did "xxx,xxx,xxx" so can anyone shed any light on this. The basic scenario is alot like tokenizing so I require a list of strings then some comparison strings so I need to pass in 2 lists/arrays, but just stumped.

like image 678
Grofit Avatar asked Oct 19 '11 11:10

Grofit


3 Answers

@BrianKretzler is exactly dead on in using ITaskItem, since it's what MSBuild uses when you declare an <ItemGroup>.

I just wanted to flush out the answer with a full working example, since I found this post while I was trying to accomplish the same thing and it helped me out. (It's very hard to search for these problems, because the keywords are used in different contexts, so hopefully this will help someone else).

<UsingTask TaskName="MyCustomTask" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
        <SomeStrings ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
    </ParameterGroup>
    <Task>
        <Code Type="Class" Language="cs"><![CDATA[
            using System;
            using Microsoft.Build.Framework;
            using Microsoft.Build.Utilities;

            public class MyCustomTask : Task
            {  
                public ITaskItem[] SomeStrings { get; set; }

                public override bool Execute()
                {
                    foreach (var item in SomeStrings)
                    {
                        Log.LogMessage(MessageImportance.High, 
                                       "Got item {0}",
                                       item.ItemSpec);
                        Log.LogMessage(" -> {0} -> {1}", 
                                       item.GetMetadata("Comparison"),
                                       item.GetMetadata("MoreDetail"));
                    }
                    return true;
                }
            }
        ]]></Code>
    </Task>
</UsingTask>

Now you can call this task with:

<Target Name="DoSomething">
    <ItemGroup>
       <SomeStrings Include="first string">
          <Comparison>first</Comparison>
       </SomeStrings>
       <SomeStrings Include="second string">
          <Comparison>2nd</Comparison>
          <MoreDetail>this is optional</MoreDetail>
       </SomeStrings>
    </ItemGroup>
    <MyCustomTask SomeStrings="@(SomeStrings)" />
</Target>

and the output is:

Microsoft (R) Build Engine Version 4.0.30319.1
[Microsoft .NET Framework, Version 4.0.30319.269]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 2012-10-19 5:41:22 PM.
Got first string
 -> first -> 
Got second string
 -> 2nd -> this is optional

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:00.12

You can of course also use something like <ItemGroup><SomeStrings Include="**\*.txt" /></ItemGroup> and you'll get the list of filenames that are matched, and of course you can use GetMetadata() to access the well-known file metadata

like image 65
gregmac Avatar answered Sep 27 '22 19:09

gregmac


It isn't quite clear what you are trying to do; you have the C# code for a custom task, but also the MSBuild code for the same task as an inline task -- you do realize you only need to do one of those, correct? If you are trying to create a task in an assembly, the <UsingTask> in your MSBuild should be an empty element, without the <ParameterGroup> and <Task> children. If you are trying to use an inline task, you don't need the C# code and need to specify your own assembly as the AssemblyFile, and not specify the TaskFactory as you have.

I'd declare the parameter as type ITaskItem[], so you can then pass in the value(s) as,

<MyCustomTask SomeStrings="@(SomeStrings)" />

You could set up the comparison strings as a second item array in a second parameter, or as metadata on the first parameter, e.g.

<ItemGroup>
   <SomeStrings Include="first string">
      <Comparison>first</Comparison>
   </SomeStrings>
   <SomeStrings Include="second string">
      <Comparison>2nd</Comparison>
   </SomeStrings>
</ItemGroup>

If you are using inline code, you'll need to <Reference> the proper MSBuild assemblies and fully qualify the ParameterType. Get it working in a compiled assembly first even if your eventual intent is to use inline code.

like image 40
Brian Kretzler Avatar answered Sep 27 '22 17:09

Brian Kretzler


Since this is currently the first hit on Google, here's the other way of doing it (as alluded to by @alastair-maw's comment) as answered in another SO thread:

MSBuild tasks can accept ITaskItem, primitives, string or an array of any of those for parameters. You declare the type in your task and then the values will be converted before passed to the task. If the value cannot convert to the type, then an exception will be raised and the build will be stopped.

For example, if you have a task which accepts an int[] named Values then you could do:

<Target Name="MyTarget">
    <MyTask Values="1;45;657" />
    <!-- or you can do -->
    <ItemGroup>
        <SomeValues Include="7;54;568;432;79" />
    </ItemGroup>

   <MyTask Values="@(SomeValues) />
</Target>
like image 21
csrowell Avatar answered Sep 27 '22 18:09

csrowell