[MonoDevelop] Custom tools on multiple files

Michael Hutchinson m.j.hutchinson at gmail.com
Wed Jan 5 19:29:58 EST 2011


On Wed, Jan 5, 2011 at 8:02 AM, Federico Di Gregorio <fog at initd.org> wrote:
> On 04/01/2011 21:40, Michael Hutchinson wrote:
> [snip]
>> Doing it via xbuild is possibly better because it will work with
>> xbuild, msbuild and VS, but currently will only work with MD if you
>> enable "experimental xbuild support". Fo this, you simply need a
>> custom targets file. Define a custom target and inject its name into
>> the PrepareForRunDependsOn property. Its inputs would be
>> @(YourBuildAction) - the entire set of items with that action. Your
>> target can then invoke a task - either Exec or some custom task - to
>> do the compile. You may also wish to add the generated file to the
>> FileWrites items so it gets removed by a Clean.
>
> We're already using xbuild so I think I'll go for this one.
>
> I already have some code that implementes ITask and does the packing but
> right now, to test it, I wrote che XML csproj by hand. If I understand
> it correctly I need to have my MD addin add to the project two items:
>
> 1/ <UsingTask> to locate my task assmbly
> 2/ <CssPack> items inside an <ItemGroup>
>
> What's the best way of doing that? It is possible to add properties to
> file types already managed by MD (like css, XML or js)?

It would be cleaner to have a targets file that you can easily import
into multiple project.

Suppose your project has to define a property for the packed css name:

<PropertyGroup>
        <PackedCssFile>$(OutDir)\packed.css</PackedCssFile>
</PropertyGroup>

Then some items:

<ItemGroup>
    <PackableCssFile Include="Foo.css" />
    <PackableCssFile Include="Bar.css" />
</ItemGroup>

then import your targets:

<Import Project="Path\ToSomeCompany.Custom.targets" \>

You could use an absolute or relative path, or use $(MSBuildBinPath)
and install the target file into the shared targets directory.

Your targets file will first pull in your task assembly:

<UsingTask TaskName="SomeCompany.PackCss"
AssemblyFile="SomeCompany.PackCss.dll" />

Then redefine PrepareForRunDependsOn to inject a custom target into
the build sequence, but only if the project has defined the property
and items:

<PropertyGroup Condition=" '$(PackedCssFile)' != '' And
'@(PackableCssFile)' != '' ">
        <PrepareForRunDependsOn>PackCss;$(PrepareForRunDependsOn)</PrepareForRunDependsOn>
</PropertyGroup>

Then define your targets. The first one it always runs if it's in the
build sequence, and simply adds the output file to FileWrites, which
means it magically gets cleaned automatically, even if its name
changes.

<Target Name="PackCss" DependsOnTargets="_PackCss">
        <CreateItem Include="$(PackedCssFile)"
Condition="Exists('$(PackedCssFile)')">
                <Output TaskParameter="Include" ItemName="FileWrites" />
        </CreateItem>
</Target>

Note the ugly CreateItem MSBuild 2.0 syntax. Unfortunately xbuild does
not yet support the MSBuild 3.5 feature to allow defining ItemGroups
and PropertyGroups within targets, else we would have done

<Target Name="PackCss" DependsOnTargets="_PackCss">
        <ItemGroup Condition="Exists('$(PackedCssFile)')">
                <FileWrites Include="$(PackedCssFile)" />
        </ItemGroup>
</Target>

The task we defined above depends on the real target, which has
specified input and output files, so the targets will be skipped
entirely if the outputs are newer than the inputs. This target invokes
your task:

<Target Name="_PackCss"
Inputs="$(MSBuildAllProjects);@(PackableCssFiles)"
Outputs="$(PackedCssFile)" >
        <PackCss SourceFiles="$(PackableCssFiles)" Output="$(PackedCssFile)" />
</Target>

In answer to your second question, yes, you can add arbitrary metadata
to items in MD, though you cannot edit them in the GUI unless you
write a propertygrid extender.

For example, you could have

<CssFile Include="Foo.css">
    <PackInto>SomePackedFile.css</PackInto>
</CssFile>

Then write more complex targets that would aggregate items with
identical "PackInto" metadata and therefore be able to pack into
multiple files. That would be considerable more complex though.

Or you could define the packed files as items (though they would then
show up in the project) and have each define a set of sources. Easy to
implement, but somewhat messy to use.

<PackedCssFile Include="SomePackedFile.css">
    <Sources>Foo.css;Bar.css</Sources>
</PackedCssFile>

-- 
Michael Hutchinson
http://mjhutchinson.com


More information about the Monodevelop-list mailing list