Monday, 22 April 2013

Building a wildcard containing asterisks in an MSBuild CreateItem

Suppose you want to use CreateItem to build a string containing a pattern, based on the value of a property being passed to your Target (say, to make an OpenCover Filter group).
If the Include attribute value includes an asterisk (even if escaped as hex) then a pattern match is attempted against the filesystem, and you end up with nothing in the group.
After several hours of messing about, I came up with a solution building the pattern with the word STAR instead of an asterisk, and using an inline C# Target to create a TaskItem with the correct text.
Here is the result, in a .proj file at the solution level and using the NuGet packages for OpenCover, NUnit.Runners and ReportGenerator.
<?xml version="1.0" encoding="utf-8"?>

<Project DefaultTargets="Clean" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">

    <PropertyGroup>

        <OpenCoverVersion>4.5.1403</OpenCoverVersion>

        <NUnitVersion>2.6.2</NUnitVersion>

        <ReportGeneratorVersion>1.8.0.0</ReportGeneratorVersion>

    </PropertyGroup>

 

    <ItemGroup>

        <TestedNamespace Include="MyCode">

            <TestsNamespace>MyCode.Tests</TestsNamespace>

        </TestedNamespace>

        <TestedNamespace Include="MyWebStuff">

            <TestsNamespace>MyWebStuff.Tests</TestsNamespace>

        </TestedNamespace>

        <ReportFiles Include="@(TestedNamespace->'OpenCover\%(TestsNamespace).opencover.xml')"/>

    </ItemGroup>



    <PropertyGroup>

        <Configuration Condition="'$(Configuration)' == ''">Release</Configuration>

        <OpenCoverPath>$(MSBuildProjectDirectory)\packages\OpenCover.$(OpenCoverVersion)</OpenCoverPath>

        <OpenCoverMSBuildTasksPath>$(OpenCoverPath)\MSBuild</OpenCoverMSBuildTasksPath>

        <OpenCoverPath>$(MSBuildProjectDirectory)\packages\OpenCover.$(OpenCoverVersion)</OpenCoverPath>

        <OpenCoverMSBuildTasksPath>$(OpenCoverPath)\MSBuild</OpenCoverMSBuildTasksPath>

        <NUnitRunner>$(MSBuildProjectDirectory)\packages\NUnit.Runners.$(NUnitVersion)\tools\nunit-console-x86.exe</NUnitRunner>

        <ReportGenerator>$(MSBuildProjectDirectory)\packages\ReportGenerator.$(ReportGeneratorVersion)\ReportGenerator.exe</ReportGenerator>

    </PropertyGroup>

 

    <Import Project="$(OpenCoverMSBuildTasksPath)\OpenCover.targets"/>

    <UsingTask AssemblyFile="$(ReportGenerator)" TaskName="ReportGenerator"/>

 

    <UsingTask TaskName="TurnSTARintoActualStar" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">

        <ParameterGroup>

            <Input ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />

            <Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />

        </ParameterGroup>

        <Task>

            <Code Type="Fragment" Language="cs">

                <![CDATA[

    if (Input.Length > 0)

    {

        Result = new TaskItem[Input.Length];

        for (int i = 0; i < Input.Length; i++)

        {

            ITaskItem item = Input[i];

            Result[i] = new TaskItem(item.GetMetadata("Identity").Replace("STAR", "*"));

        }

    }

]]>

            </Code>

        </Task>

    </UsingTask>

 

    <Target Name="Clean">

        <RemoveDir Directories="OpenCover" />

        <CallTarget Targets="Report"/>

    </Target>

 

    <Target Name="Report" DependsOnTargets="Build">

        <ReportGenerator

            TargetDirectory="OpenCover\Report"

            ReportTypes="Html"

            ReportFiles="@(ReportFiles)"

            />

    </Target>

    

    <Target Name="Build" Inputs="@(TestedNamespace)" Outputs="OpenCover\%(TestsNamespace).opencover.xml">

        <MakeDir Directories="OpenCover" />

 

        <CreateItem Include="+[%(TestedNamespace.Identity)STAR]STAR;-[%(TestedNamespace.TestsNamespace)]STAR;-[%(TestedNamespace.Identity)STAR]%(TestedNamespace.Identity).Properties.STAR">

            <Output TaskParameter="Include" ItemName="SafeFilters" />

        </CreateItem>

        

        <CreateItem Include="$(MSBuildProjectDirectory)\%(TestedNamespace.TestsNamespace)\bin\$(Configuration)\%(TestedNamespace.TestsNamespace).dll">

            <Output TaskParameter="Include" PropertyName="TestsAssemblyPath" />

        </CreateItem>

 

        <CreateItem Include='"$(TestsAssemblyPath)" /noshadow /xml:"OpenCover\%(TestedNamespace.TestsNamespace).nunit.xml"'>

            <Output TaskParameter="Include" PropertyName="NUnitArgs" />

        </CreateItem>

 

        <CreateItem Include="@(TestedNamespace->'OpenCover\%(TestsNamespace).opencover.xml')">

            <Output TaskParameter="Include" PropertyName="OpenCoverXml" />

        </CreateItem>

 

        <TurnSTARintoActualStar Input="@(SafeFilters)">

            <Output ItemName="Filters" TaskParameter="Result" />

        </TurnSTARintoActualStar>

 

        <OpenCover 

            ToolPath="$(OpenCoverPath)"

            Register="True" 

            Target="$(NUnitRunner)" 

            TargetArgs='$(NUnitArgs)'

            Output="$(OpenCoverXml)"

            CoverByTest="$(TestsAssemblyPath)"

            Filter="@(Filters)"/>

    </Target>

 

</Project>


1 comment:

  1. Great Article
    Cyber Security Projects

    projects for cse

    Networking Security Projects

    JavaScript Training in Chennai

    JavaScript Training in Chennai

    The Angular Training covers a wide range of topics including Components, Angular Directives, Angular Services, Pipes, security fundamentals, Routing, and Angular programmability. The new Angular TRaining will lay the foundation you need to specialise in Single Page Application developer. Angular Training

    ReplyDelete