SharpDevelop Community

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

Matt Ward

MSBuild and Mono's GAC

SharpDevelop2 now supports Mono's GAC, so this post takes a low level look into how it uses MSBuild to do this.

Our starting point is a previous Custom MSBuild Targets for Mono post, which introduced an MSBuild task (Mcs), which was used to compile the code using Mono's Mcs compiler, and an MSBuild targets file (Mono.Mcs.targets).  We will be using the ICSharpCode.Build.Tasks assembly that ships with SharpDevelop2 which contains the custom MSBuild tasks.  We will not be using the SharpDevelop2's target files since these target more than just the Mono framework.

Mono GAC References

The original custom MSBuild task and targets file did not support Mono GAC references.  In fact they were the bare minimum required to compile code using Mcs.  The only MSBuild target that was overridden was the CoreCompile target, which was changed to use the Mcs task instead of Microsoft's Csc task.  Without support for Mono's GAC any assembly references needed to have a HintPath

<Reference Include="gtk-sharp">
   <HintPath>..\..\Program Files\Mono\lib\mono\gtk-sharp\gtk-sharp.dll</HintPath>
</Reference>

In order to add support for Mono's GAC we will need to look at the following Microsoft targets and properties:
  • AssemblySearchPaths
  • GetFrameworkPaths
  • ResolveAssemblyReferences
First let us see if we can get a simple GAC reference to System.Xml to work.  With the old tasks and targets a reference of the form

<Reference Include="System.Xml"/>
   
is resolved to "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll".

To fix this we need to override the GetFrameworkPaths target and set the TargetFrameworkDirectory property  correctly.  This can be done by adding the following to the Mono.Mcs.targets file

<Target Name="GetFrameworkPaths">
    <!-- Get the path to the target Mono Framework directory. -->
    <GetMonoFrameworkPath TargetFrameworkVersion="Mono v1.1">
        <Output TaskParameter="Path" PropertyName="TargetFrameworkDirectory"/>
        <Output TaskParameter="Path" ItemName="_TargetFrameworkDirectoryItem"/>
    </GetMonoFrameworkPath>

    <!-- Get the path to the target the Mono SDK directory. -->
    <GetMonoFrameworkSDKPath>
        <Output TaskParameter="Path" PropertyName="TargetFrameworkSDKDirectory"/>
        <Output TaskParameter="Path" ItemName="_TargetFrameworkSDKDirectoryItem"/>
    </GetMonoFrameworkSDKPath>
</Target>

    
This override must come after the

<Import Project="$(MSBuildBinPath)\Microsoft.Common.targets" />

statement, so this should be moved to the beginning of the Mono.Mcs.targets file.  It also uses two custom  tasks that need to be made known to MSBuild

<UsingTask TaskName="ICSharpCode.Build.Tasks.GetMonoFrameworkPath"
           AssemblyFile="ICSharpCode.Build.Tasks.dll"/>
<UsingTask TaskName="ICSharpCode.Build.Tasks.GetMonoFrameworkSdkPath"
           AssemblyFile="ICSharpCode.Build.Tasks.dll"/>
 
 
The GetFrameworkPaths target sets a few more things than just the TargetFrameworkDirectory, this is done for completeness based on what the Microsoft.Common.targets file does, but only the TargetFrameworkDirectory property needs to be set.  The TargetFrameworkDirectory is set by calling the GetMonoFrameworkPath task and passing in the target framework version, which in this case is "Mono v1.1".  This task uses the registry to locate the Mono framework directory (e.g. "C:\Program Files\Mono\lib\mono\1.0").

AssemblySearchPaths and ResolveAssemblyReference

Now what about an assembly that only exists in Mono's GAC and cannot be found in Mono framework's directory?  A reference to glib-sharp is a good place to start.

<Reference Include="glib-sharp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f"/>

We need to modify the AssemblySearchPaths which is being passed to the ResolveAssemblyReference task.  In the Microsoft's target files this is defined as:

<PropertyGroup>
    <!--
    The SearchPaths property is set to find assemblies in the following order:

        (1) Files from current project - indicated by {CandidateAssemblyFiles}
        (2) $(ReferencePath) - the reference path property, which comes from the .USER file.
        (3) The hintpath from the referenced item itself, indicated by {HintPathFromItem}.
        (4) The directory of MSBuild's "target" runtime from GetFrameworkPath.
            The "target" runtime folder is the folder of the runtime that MSBuild is a part of.
        (5) Registered assembly folders, indicated by {Registry:*,*,*}
        (6) Legacy registered assembly folders, indicated by {AssemblyFolders}
        (7) Look in the application's output folder (like bin\debug)
        (8) Resolve to the GAC.
        (9) Treat the reference's Include as if it were a real file name.
    -->        
    <AssemblySearchPaths Condition=" '$(AssemblySearchPaths)' == '' ">
        {CandidateAssemblyFiles};
        $(ReferencePath);
        {HintPathFromItem};
        {TargetFrameworkDirectory};
        {Registry:$(FrameworkRegistryBase),$(TargetFrameworkVersion),$(AssemblyFoldersSuffix)$(AssemblyFoldersExConditions)};
        {AssemblyFolders};
        {GAC};
        {RawFileName};
        $(OutputPath)
    </AssemblySearchPaths>                       
</PropertyGroup>

The {Registry} part is Microsoft specific and can be removed for Mono.  The {GAC} part is also Microsoft specific.  When the ResolveAssemblyReference task encounters this string it knows to look in Microsoft's GAC.  We will replace this with {MonoGAC} which Microsoft's ResolveAssemblyReference task knows nothing about.

<PropertyGroup>
    <AssemblySearchPaths>
        {CandidateAssemblyFiles};
        $(ReferencePath);
        {HintPathFromItem};
        {TargetFrameworkDirectory};
        {AssemblyFolders};
        {MonoGAC};
        {RawFileName};
        $(OutputPath)
    </AssemblySearchPaths> 
</PropertyGroup>

Now we need to somehow replace the {MonoGAC} with a set of directories.  We could write our own ResolveAssemblyReference task, but this seems like a lot of work.  Instead let us update the AssemblySearchPaths just before the ResolveAssemblyReference task is called.  We do this by overriding the ResolveAssemblyReferenceDependsOn target and making it call our custom AddMonoAssemblySearchPaths target.

<PropertyGroup>
    <ResolveAssemblyReferencesDependsOn>
        GetFrameworkPaths;
        GetRedistLists;
        PrepareForBuild;
        AddMonoAssemblySearchPaths
    </ResolveAssemblyReferencesDependsOn>
</PropertyGroup>
<Target Name="AddMonoAssemblySearchPaths">
    <AddMonoAssemblySearchPaths 
        Assemblies="@(Reference)"
        Paths="$(AssemblySearchPaths)">
        <Output TaskParameter="Paths" PropertyName="AssemblySearchPaths"/>
    </AddMonoAssemblySearchPaths>
</Target>

Again this custom task needs to be included via

<UsingTask TaskName="ICSharpCode.Build.Tasks.AddMonoAssemblySearchPaths"
           AssemblyFile="ICSharpCode.Build.Tasks.dll"/>

The AddMonoAssemblySearchPath task looks for the {MonoGAC} item in the AssemblySearchPaths property and replaces it with any Mono GAC directories that need to be searched.  The GAC directories are determined by looking at the assembly references passed in via the Assemblies property.  If multiple GAC references are located then multiple directories are inserted into the AssemblySearchPaths property.

Now we can use MSBuild to build a project with references to assemblies in Mono's GAC using SharpDevelop2's ICSharpCode.Build.Tasks assembly and the Mono.Mcs.targets file.

Mono.Mcs.targets

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

    <Import Project="$(MSBuildBinPath)\Microsoft.Common.targets" />

    <UsingTask TaskName="ICSharpCode.Build.Tasks.Mcs"
               AssemblyFile="ICSharpCode.Build.Tasks.dll"/>
     <UsingTask TaskName="ICSharpCode.Build.Tasks.GetMonoFrameworkPath"
               AssemblyFile="ICSharpCode.Build.Tasks.dll"/>
     <UsingTask TaskName="ICSharpCode.Build.Tasks.GetMonoFrameworkSdkPath"
               AssemblyFile="ICSharpCode.Build.Tasks.dll"/>
     <UsingTask TaskName="ICSharpCode.Build.Tasks.AddMonoAssemblySearchPaths"
               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>
    
   <!-- Override AssemblySearchPaths property and remove Microsoft specific search paths -->
   <PropertyGroup>
        <AssemblySearchPaths>
            {CandidateAssemblyFiles};
            $(ReferencePath);
            {HintPathFromItem};
            {TargetFrameworkDirectory};
            {AssemblyFolders};
            {MonoGAC};
            {RawFileName};
            $(OutputPath)
        </AssemblySearchPaths> 
    </PropertyGroup>
    
    <!-- Modify what the ResolveAssemblyReferences tasks depends on so the
         AssemblySearchPaths can be modified to use the Mono GAC -->
    <PropertyGroup>
        <ResolveAssemblyReferencesDependsOn>
            GetFrameworkPaths;
            GetRedistLists;
            PrepareForBuild;
            AddMonoAssemblySearchPaths
        </ResolveAssemblyReferencesDependsOn>
    </PropertyGroup>
    <Target Name="AddMonoAssemblySearchPaths">
        <AddMonoAssemblySearchPaths 
            Assemblies="@(Reference)"
            Paths="$(AssemblySearchPaths)">
            
            <Output TaskParameter="Paths" PropertyName="AssemblySearchPaths"/>
        </AddMonoAssemblySearchPaths>
    </Target>
    
    <Target    Name="GetFrameworkPaths">
        <!-- Get the path to the target Mono Framework directory. -->
        <GetMonoFrameworkPath TargetFrameworkVersion="Mono v1.1">
            <Output TaskParameter="Path" PropertyName="TargetFrameworkDirectory"/>
            <Output TaskParameter="Path" ItemName="_TargetFrameworkDirectoryItem"/>
        </GetMonoFrameworkPath>
    
        <!-- Get the path to the target the Mono SDK directory. -->
        <GetMonoFrameworkSDKPath>
            <Output TaskParameter="Path" PropertyName="TargetFrameworkSDKDirectory"/>
            <Output TaskParameter="Path" ItemName="_TargetFrameworkSDKDirectoryItem"/>
        </GetMonoFrameworkSDKPath>
    </Target>
    
    <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>
</Project>

Published Jan 11 2006, 09:13 PM by MattWard
Filed under: ,

Comments

No Comments
Powered by Community Server (Commercial Edition), by Telligent Systems
Don't contact us via this (fleischfalle@alphasierrapapa.com) email address.