SharpDevelop Community

Get your problems solved!
Welcome to SharpDevelop Community Sign in | Join | Help
in Search

Matt Ward

Custom MSBuild Tasks for Mono

SharpDevelop ships with two custom MSBuild tasks for Mono, one for Mcs and the other for Gmcs, both of which live in the ICSharpCode.Build.Tasks assembly.  With the recent Mono release, 1.1.9, the command line options and compiler error message formats are the same for Mcs and Gmcs, earlier versions differed.  This means that the Mcs and Gmcs classes do not do much apart from determine the name of the compiler to use.  The MonoCompilerTask class does the hard work of compiling the code and reporting error messages back.

Simple C# Console Project

HelloMonoWorld.csproj:

<Project DefaultTargets="Build" 
         xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <AssemblyName>HelloMonoWorld</AssemblyName>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup>
    <OutputPath>bin</OutputPath>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="Main.cs" />
    <Compile Include="AssemblyInfo.cs" />
  </ItemGroup>
  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

Main.cs:

using System;

namespace HelloMonoWorld
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

Running MSBuild, passing the .csproj as a command line argument, will build the console app using Microsoft's Csc and targets the .NET Framework 2.0.

Using the Mcs Task Directly

Let us modify the project file so it calls the Mcs task directly.  I am assuming that the ICSharpCode.Build.Tasks.dll exists in the same folder as the project file.

<Project DefaultTargets="Build" 
         xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <AssemblyName>HelloMonoWorld</AssemblyName>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup>
    <OutputPath>bin</OutputPath>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="Main.cs" />
    <Compile Include="AssemblyInfo.cs" />
  </ItemGroup>
  
  <UsingTask TaskName="ICSharpCode.Build.Tasks.Mcs"
             AssemblyFile="ICSharpCode.Build.Tasks.dll"/>

  <Target Name="Build">
    <Mcs
      OutputAssembly="$(OutputPath)\$(AssemblyName).exe"
      References="@(ReferencePath)"
      Sources="@(Compile)"
      TargetType="$(OutputType)"/>
  </Target>
</Project>

The project file above is a bit limited, it does not have standard targets such as "Rebuild" and "Clean".  We could write everything ourselves, but instead why not use the standard .targets that Microsoft provides and just override the bare minimum?

Mono.Mcs.targets

So what is the bare minimum .targets file we need in order to get MSBuild to use our custom Mono tasks?

First let us look at the modified project file:

<Project DefaultTargets="Build" 
         xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <AssemblyName>HelloMonoWorld</AssemblyName>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup>
    <OutputPath>bin</OutputPath>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="Main.cs" />
    <Compile Include="AssemblyInfo.cs" />
  </ItemGroup>
  <Import Project="Mono.Mcs.targets" />
</Project>

The Mono.Mcs.targets file, which is based on the Microsoft.CSharp.targets file, overrides the CoreCompile target:

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

    <UsingTask TaskName="ICSharpCode.Build.Tasks.Mcs"
               AssemblyFile="ICSharpCode.Build.Tasks.dll"/>
    
   <PropertyGroup>
        <MSBuildAllProjects>$(MSBuildAllProjects);Mono.Mcs.targets</MSBuildAllProjects>
        <DefaultLanguageSourceExtension>.cs</DefaultLanguageSourceExtension>
        <Language>C#</Language>
    </PropertyGroup>
    
    <Target Name="CreateManifestResourceNames"/>

    <PropertyGroup>
        <DebugSymbols Condition=" '$(DebugType)' == 'none' ">false</DebugSymbols>
        <DebugType    Condition=" '$(DebugType)' == 'none' "></DebugType>    
    </PropertyGroup>

    <ItemGroup>
        <DocFileItem Include="$(DocumentationFile)" Condition="'$(DocumentationFile)'!=''">
            <InProject>false</InProject>
        </DocFileItem>
    </ItemGroup>

    <PropertyGroup>
        <CoreCompileDependsOn>_ComputeNonExistentFileProperty</CoreCompileDependsOn>
    </PropertyGroup>
    
    <Target
        Name="CoreCompile"
        Inputs="$(MSBuildAllProjects);
                @(Compile);
                @(ManifestResourceWithNoCulture);
                $(ApplicationIcon);
                $(AssemblyOriginatorKeyFile);
                @(ManifestNonResxWithNoCultureOnDisk);
                @(ReferencePath);
                @(CompiledLicenseFile)"
        Outputs="@(DocFileItem);
                 @(IntermediateAssembly);
                 $(NonExistentFile)"
        DependsOnTargets="$(CoreCompileDependsOn)"
    >
       
        <Mcs
              AdditionalLibPaths="$(AdditionalLibPaths)"
              AddModules="@(AddModules)"
              AllowUnsafeBlocks="$(AllowUnsafeBlocks)"
              CheckForOverflowUnderflow="$(CheckForOverflowUnderflow)"
              CodePage="$(CodePage)"
              DebugType="$(DebugType)"
              DefineConstants="$(DefineConstants)"
              DelaySign="$(DelaySign)"
              DisabledWarnings="$(NoWarn)"
              DocumentationFile="@(DocFileItem)"
              EmitDebugInformation="$(DebugSymbols)"
              KeyContainer="$(KeyContainerName)"
              KeyFile="$(KeyOriginatorFile)"
              LangVersion="$(LangVersion)"
              MainEntryPoint="$(StartupObject)"
              NoConfig="true"
              NoLogo="$(NoLogo)"
              NoStandardLib="$(NoStdLib)"
              Optimize="$(Optimize)"
              OutputAssembly="@(IntermediateAssembly)"
              References="@(ReferencePath)"
              Resources="@(ManifestResourceWithNoCulture);@(ManifestNonResxWithNoCultureOnDisk);@(CompiledLicenseFile)"
              ResponseFiles="$(CompilerResponseFile)"
              Sources="@(Compile)"
              TargetType="$(OutputType)"
              ToolPath="$(McsToolPath)"
              TreatWarningsAsErrors="$(TreatWarningsAsErrors)"
              WarningLevel="$(WarningLevel)"
              Win32Icon="$(ApplicationIcon)"
              Win32Resource="$(Win32Resource)" />
    </Target>
    
    <Import Project="$(MSBuildBinPath)\Microsoft.Common.targets" />
</Project>

The differences between this is and the standard Microsoft.CSharp.targets file is that a few properties have been removed (NoWarn and UseHostCompilerIfAvailable) and the CreateManifestResourceNames target has essentially been removed, the target is defined, but it does nothing.  This targets file is good enough to compile our console application, as long as we do not add any embedded resources to it.

SharpDevelop

How does this compare with the .targets files that ship with SharpDevelop?  Since SharpDevelop can target more than just Mono, it supports the older .NET frameworks too, things are a little more complicated.  This will be covered in another post.
Published Oct 15 2005, 03:15 PM by MattWard
Filed under: ,

Comments

 

ComputerZen.com - Scott Hanselman said:

November 5, 2005 9:09 AM
 

ComputerZen.com - Scott Hanselman said:

November 5, 2005 10:52 AM
 

ComputerZen.com - Scott Hanselman said:

November 10, 2005 3:41 AM
 

Matt Ward said:

SharpDevelop2 now supports Mono's GAC, so this post takes a low level look into how it uses MSBuild to...
January 11, 2006 10:42 PM
Powered by Community Server (Commercial Edition), by Telligent Systems
Don't contact us via this (fleischfalle@alphasierrapapa.com) email address.