MSBuild Part 1


I took some time this week from working on my Thesis to work on MSBuild again, I know it’s slow going, but unfortunately this project is not my highest priority task atm (I thank you all for your patience). The result is the following

The new compile and project pipeline for a .cabal file now looks as follows:

[.cabal file] <-> [IDE] <-> [Cabal2MSBuild] <-> [MSBuild]

The execution looks like

  1. You double click on a .cabal file which launches visual studio
  2. The IDE then invokes Cabal2MSBuild on the .cabal file to get a .hsproj file
  3. This .hsproj file is use strictly internally. It’s only purpose is to instruct the IDE which files to include, references, Author etc. Any changes done to these fields will be also directly written to the .cabal file via Cabal.
  4. When you want to build a project, a MSBuild task is run, this task even though defined in the .hsproj files will use no information from this file. It calls cabal-install on the original .cabal file.
  5. Errors and warnings are captured and translated into something the IDE can understand.

This is a much simpler and stabler approach than the first one which was to deeply and directly integrate .cabal files inside the IDE. This requires a immense amount of work, And I never could get it to work 100%.

Having finished my MSBuild book (which was really the only *complete and coherent* source of information on MSBuild) I was able to create the Cabal2MSBuild tool. This tool will create a .hsproj file from a .cabal file which contains all information that was present in the .cabal file but also a full listing of which files to include in the project. References etc. the last line of this file is an important line

<Import Project="Haskell.Cabal.targets"/>

This line imports a set of predefined Cabal specific tasks for MSBuild. These tasks as they’re called wrap calls to the cabal-install tool. The exposed functionalities are

Build, Run, Configure, Deploy, Clean and Update. (As a side note, this Target file can be used by any msbuild compatible build system. So this means using Team Foundation Server as a source control server and having continuous builds going on should be possible).

These Tasks are all exposed by a custom Task file “Cabal.MSBuild.Tasks.dll”

(The end of this post will contain the full project and Task files)

To show that this all works, here’s a example output

Phyx>cabal2msbuild Cabal2MSBuild.cabal Test.proj
Done.

Phyx>msbuild /target:Configure /property:CabalProjectFile="Cabal2MSBuild.cabal"
Test.proj
Microsoft (R) Build Engine Version 4.0.30319.1
[Microsoft .NET Framework, Version 4.0.30319.1]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 10/12/2010 12:32:50.
Project "C:\Users\Phyx\Documents\Haskell\Cabal2MSBuild\Test.proj" on node 1 (Configure target(s)).
Configure:
  VSH2010 Configuring project file...
  VSH2010 Using Cabal 'C:\Users\Phyx\AppData\Roaming\cabal\bin\Cabal.exe'
  Running Cabal...
TSKCONFIGURE : warning : The package list for 'hackage.haskell.org' is 29 days old. [C:\Users\Phyx\Documents\Haskell\Cabal2MSBuild\Test.proj]
  Run 'cabal update' to get the latest list of available packages.
TSKCONFIGURE : warning : Cabal2MSBuild.cabal: Unknown fields: other-modules (line 17) [C:\Users\Phyx\Documents\Haskell\Cabal2MSBuild\Test.proj]
  Fields allowed in this section:
  name, version, cabal-version, build-type, license, license-file,
  copyright, maintainer, build-depends, stability, homepage,
  package-url, bug-reports, synopsis, description, category, author,
  tested-with, data-files, data-dir, extra-source-files,
  extra-tmp-files
TSKCONFIGURE : warning : Cabal2MSBuild.cabal: Unknown fields: other-modules (line 17) [C:\Users\Phyx\Documents\Haskell\Cabal2MSBuild\Test.proj]
  Fields allowed in this section:
  name, version, cabal-version, build-type, license, license-file,
  copyright, maintainer, build-depends, stability, homepage,
  package-url, bug-reports, synopsis, description, category, author,
  tested-with, data-files, data-dir, extra-source-files,
  extra-tmp-files
TSKCONFIGURE : warning : Cabal2MSBuild.cabal: Unknown fields: other-modules (line 17) [C:\Users\Phyx\Documents\Haskell\Cabal2MSBuild\Test.proj]
  Fields allowed in this section:
  name, version, cabal-version, build-type, license, license-file,
  copyright, maintainer, build-depends, stability, homepage,
  package-url, bug-reports, synopsis, description, category, author,
  tested-with, data-files, data-dir, extra-source-files,
  extra-tmp-files
TSKCONFIGURE : warning : This package indirectly depends on multiple versions of the same [C:\Users\Phyx\Documents\Haskell\Cabal2MSBuild\Test.proj]
  package. This is highly likely to cause a compile failure.
  package ghc-6.12.1 requires Cabal-1.8.0.2
  package bin-package-db-0.0.0.0 requires Cabal-1.8.0.2
  package Cabal2MSBuild-0.2.2 requires Cabal-1.9.2
  VSH2010 Configuring done.
Done Building Project "C:\Users\Phyx\Documents\Haskell\Cabal2MSBuild\Test.proj"
 (Configure target(s)).


Build succeeded.

"C:\Users\Phyx\Documents\Haskell\Cabal2MSBuild\Test.proj" (Configure target) (1) ->
(Configure target) ->
  TSKCONFIGURE : warning : The package list for 'hackage.haskell.org' is 29 days old. [C:\Users\Phyx\Documents\Haskell\Cabal2MSBuild\Test.proj]
  TSKCONFIGURE : warning : Cabal2MSBuild.cabal: Unknown fields: other-modules (line 17) [C:\Users\Phyx\Documents\Haskell\Cabal2MSBuild\Test.proj]
  TSKCONFIGURE : warning : Cabal2MSBuild.cabal: Unknown fields: other-modules (line 17) [C:\Users\Phyx\Documents\Haskell\Cabal2MSBuild\Test.proj]
  TSKCONFIGURE : warning : Cabal2MSBuild.cabal: Unknown fields: other-modules (line 17) [C:\Users\Phyx\Documents\Haskell\Cabal2MSBuild\Test.proj]
  TSKCONFIGURE : warning : This package indirectly depends on multiple versions of the same [C:\Users\Phyx\Documents\Haskell\Cabal2MSBuild\Test.proj]

    5 Warning(s)
    0 Error(s)

Time Elapsed 00:00:28.65

This exposes a few problems. While it works, the feedback is inaccurate. The problem is cabal-install has no set format for warnings and errors. They’re all freeform so MSBuild will only recognize the first line and this is due to the “warning: “ prefix of the lines. Ideally we want the entire warning since that’s what we’re going to report back to the user.

This same problem exists for GHC as well, however GHC is a bit more structured, mostly errors are pretty printed and have some kind of structure to them, which you can parse by looking at the indentation of text.

As I’m trying to modify as little as possible (It’ll be easier to maintain in the future and takes the burden of maintenance of me) I’m writing a set of parser to augment MSBuild’s build in parsers to support messages generated by cabal-install and GHC.

The current .hsproj file is a valid project file, and though I haven’t integrated it into the IDE yet, when I do, It should just work, since there is full support for MSBuild project files already there (again, less to maintain, and no reflection hacks this time).

Eventually the Cabal2MSBuild tool will be available on hackage and the build tasks on codeplex since hackage doesn’t allow binary dependencies.

And now, as promised the full files, note however that both of these files are work in progress.

Appendix A: Target file

<?xml version="1.0" encoding="UTF-8"?>

<!–

***********************************************************************************************

Haskell.Cabal.targets

WARNING:  DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have

          created a backup copy.  Incorrect changes to this file will make it

          impossible to load or build your projects from the command-line or the IDE.

This file defines the steps in the standard build process for Haskell Cabal projects.

Copyright (C) Tamar Christina. All rights reserved.

***********************************************************************************************

–>

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

 
    <UsingTask AssemblyFile="Cabal.MSBuild.Tasks.dll" TaskName="TskRun"       />

    <UsingTask AssemblyFile="Cabal.MSBuild.Tasks.dll" TaskName="TskBuild"     />

    <UsingTask AssemblyFile="Cabal.MSBuild.Tasks.dll" TaskName="TskConfigure" />

    <UsingTask AssemblyFile="Cabal.MSBuild.Tasks.dll" TaskName="TskDeploy"    />

    <UsingTask AssemblyFile="Cabal.MSBuild.Tasks.dll" TaskName="TskClean"     />

    <UsingTask AssemblyFile="Cabal.MSBuild.Tasks.dll" TaskName="TskUpdate"    />

    <Target Name="Clean">

        <TskClean CabalFile="$(CabalProjectFile)" />

    </Target>

   
    <Target Name="Run">

        <TskRun CabalFile="$(CabalProjectFile)" />

    </Target>

   
    <Target Name="Deploy">

        <TskDeploy CabalFile="$(CabalProjectFile)" Target="$(CabalDeployedFile)" />

    </Target>

   
    <Target Name="Configure">

        <TskConfigure CabalFile="$(CabalProjectFile)" User="$(CabalUserConfigure)" />

    </Target>

   
    <Target Name="Build">

        <TskBuild CabalFile="$(CabalProjectFile)" />

    </Target>   
   
    <Target Name="Update">

        <TskUpdate/>

    </Target>

   
    <Target Name="Make">

        <TskConfigure CabalFile="$(CabalProjectFile)" User="$(CabalUserConfigure)" />

        <TskBuild CabalFile="$(CabalProjectFile)" />       
    </Target>

    <Target Name="MakeRun">

        <TskConfigure CabalFile="$(CabalProjectFile)" User="$(CabalUserConfigure)" />

        <TskBuild CabalFile="$(CabalProjectFile)" />

        <TskRun CabalFile="$(CabalProjectFile)" />

    </Target>

</Project>

Appendix B: hsproj file

<?xml version="1.0" encoding="UTF-8"?>

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

<PropertyGroup>

<Configuration Condition=" ‘$(Configuration)’ == ” ">Build</Configuration>

<SchemaVersion>2.0</SchemaVersion>

<ProjectGuid>{99999999-9999-9999-9999-999999999999}</ProjectGuid>

<RootNamespace>Hs2lib</RootNamespace>

<AssemblyName>Hs2lib</AssemblyName>

<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>

<License>BSD3</License>

<LicenseFile></LicenseFile>

<Maintainer>Tamar Christina &lt;…@zhox.com&gt;</Maintainer>

<Author>Tamar Christina &lt;…@zhox.com&gt;</Author>

<Stability>experimental</Stability>

<Homepage>http://www.zhox.com/projects/haskell/hs2lib</Homepage>

<PkgUrl></PkgUrl>

<BugReports></BugReports>

<Synopsis>A Library and Preprocessor that makes it easier to create shared libs (note: only tested on windows) from Haskell programs.</Synopsis>

<Description>The supplied PreProcessor can be run over any existing source and would generate FFI code for every function marked to be exported by the special notation documented inside the package. It then proceeds to compile this generated code into a windows DLL.

The Library contains some helper code that’s commonly needed to convert between types, and contains the code for the typeclasses the PreProcessor uses in the generated code to keep things clean.

It will always generated the required C types for use when calling the dll, but it will also generate the C# unsafe code if requested.

Read http://www.zhox.com/projects/haskell/hs2lib.pdf

Current Restrictions:

– Does not automatically resolve missing datatype declarations using hackage. Future releases will search library code for the types you need to resolve this but currently you’ll get a missing instance error.

– You cannot export functions which have the same name (even if they’re in different modules because 1 big hsc file is generated at the moment, no conflict resolutions)

– You cannot export datatypes with the same name, same restriction as above.

– Does not support automatic instance generation for infix constructors yet

</Description>

<Category>Development</Category>

<DataDir></DataDir>

</PropertyGroup>

<PropertyGroup Condition=" ‘$(Configuration)’ == ‘GHCi’ ">

<DebugSymbols>true</DebugSymbols>

<OutputPath>bin\Debug</OutputPath>

<OutputType>HI</OutputType>

</PropertyGroup>

<PropertyGroup Condition=" ‘$(Configuration)’ == ‘Build’ ">

<DebugSymbols>false</DebugSymbols>

<OutputPath>bin\Build</OutputPath>

<OutputType>EXE</OutputType>

</PropertyGroup>

<ItemGroup>

<Content Include="$(DataDir)\Templates\main.template-unix.c"/>

<Content Include="$(DataDir)\Templates\main.template-win.c"/>

<Content Include="$(DataDir)\Includes\Tuples.h"/>

<Content Include="$(DataDir)\Includes\Instances.h"/>

</ItemGroup>

<ItemGroup>

<Compile Include="$(DataDir)\WinDll\*.hs"/>

<Compile Include="$(DataDir)\*.hs"/>

<Compile Include="$(DataDir)\Includes\*.h"/>

</ItemGroup>

<ItemGroup>

<Reference Include="base"/>

<Reference Include="syb"/>

</ItemGroup>

<ItemGroup>

<!–

<None Include="*.Cabal">

    <Type>Library</Type>

    <Exposed>True</Exposed>

</None>

–>

<Compile Include="WinDll\Lib\Converter.*" />

<Compile Include="WinDll\Lib\NativeMapping.*" />

<Compile Include="WinDll\Lib\Tuples.*" />

<Compile Include="WinDll\Structs\Types.*" />

<Compile Include="WinDll\Lib\InstancesTypes.*" />

</ItemGroup>

<!–

<ItemGroup Type="Executable" ExeName="" Name="Hs2lib" ModulePath="Hs2lib.hs"/>

–>

<Import Project="Haskell.Cabal.targets"/>

</Project>