Skip to end of metadata
Go to start of metadata

Preamble

Disclaimer: I'm not a .NET programmer. The company I'm currently working for has large number of tools written in various languages (C/C++, C#, Perl, Java, etc.). We are in process of unifying builds of these various tools under Maven. This article describes how to add Maven build to your .NET project(s).

NPanday

Icon

You may also consider using NPanday, which provides direct support for building .NET projects using Maven.

Prerequisites

Icon

This tutorial will not teach you basics of Maven. You should be aware of Maven lifecycle, standard directory layout, plugin (mojo) mechanism and profiles

  • Before you can add Maven build to your project it has to confirm to few simple rules:
    • Code for a single project should be under single root. (No you don't have to confirm to Maven standard directory layout)
    • Your sub-projects should appear under one common root that contains parent POM
    • You project must use msbuild.exe utility, and the tutorial assumes you can build your code from command line
  • The machine running the build must have .NET 3.5 framework and MS SDK installed
  • Tutorial refers to FOO as "umbrella" project that contains number of child projects some of which may have dependency on each other

Process

  • This tutorial assumes that you have parent-child type of project, so it frequently refers to parent POM vs. child POM.
  • The parent POM is pom.xml file that resigns in the root directory hosting your projects. For example for FOO it is in FOO/trunk/Projects/pom.xml
  • The child POM is pom.xml in the root directory of each subprojects nested under parent project. For example FOO/trunk/Projects/CalendarView/pom.xml
  • The "Mavenizing" of .NET project is done in least-invasive fashion. The only convention that .NET project needs to follow is to expect all project dependency files to appear in PROJECT_ROOT/lib directory. (More about that in the following sections)
  • Maven not really "building" your .NET artifacts in terms of compiling and assembling. That is done by MSBuild which is consuming .NET-specific XML descriptors. The job of Maven is to:
    1. Clean - Execute MSBuild /t:Clean in addition to built-in clean task
    2. Prepare - Identify dependencies and copy/extract artifacts into specified location under project root (currently{{PROJECT_ROOT/lib}}
    3. Build - Execute MSBuild /t:Build task which does the actual job of compiling and assembling
    4. Install - Collect build artifacts (DLL, EXE), create a ZIP and install into Maven repo so it can be used for deployment or as dependency
    5. Publish - Copy artifacts and dependencies to IIS or file server

Build phases in details

Command line

You can build each individual sub-project by executing maven from the directory that contains child POM. The tutorial covers building multiple projects at once from parent level. The main difference in syntax is - when you build child you mustn't use "-rf ModuleName" command line option.

Icon

The following examples assume that you are building multiple projects from parent level

Build follows Maven build lifecycle so if you want your project to be packaged, use package goal, but if you want to use that as dependency you need install or deploy. You can chain goals as in the following example
Build is executed with this special syntax (note: subject to change)

Profile build is enabled by default so you don't have to enforce it with "-P" switch. By default - the Release configuration is build. You can change it by defining foo.config property ether in settings.xml or simply by adding switch to command line

Clean

Clean goal runs regular Maven clean-up (deletes PROJECT_ROOT/target directory and then executes MSBuild /t:Clean task. This and any other tasks that involve MSBuild utility are handled by exec-maven-plugin

Prepare

Optionally, before .NET build is run, Maven pulls any dependencies from the Maven repo and unpacks these to PROJECT_ROOT/lib directory. The .NET dependency artifacts are stored in the maven repo as ZIP archives. Each version is clearly marked with group ID, artifact ID and version number. For example REPO/mycompany/dept/foo/CalendarView/1.0/CalendarView-1.0.zip. ZIP contains all the artifacts (dll, exe, etc.) in the top directory of the archive. This task is handled by maven-dependency-plugin

Build

Build is handled by exec-maven-plugin which simply executes MSBuild.exe and provides runtime parameters. See follow-up configuration section for details

Install

Install/deploy creates ZIP archive of project artifacts and installs ZIP into local M2 repo or deploys ZIP to the SVN-backed repo. The process is done in two steps:

  • First the project directory is scanned by maven-antrun-plugin during prepare-package phase and collected artifacts (DLL, EXE, PDB) are placed (flattened) into PROJECT_ROOT/target/dll-staging directory
  • Next maven-assembly-plugin creates a ZIP archive during package phase based on reusable assembly descriptor (assembly.xml) which is located in the parent project root. Then the created archive is handled by standard maven install or deploy phase and placed into local M2 repo and (if deploying) into Maven repository designated in the parent POM

Publish

To publish artifacts to IIS you need to execute publish profile. Please note that publish will not run MSBuild's clean or build so before you execute publish you need run build profile with at least compile task, e.g. mvn compile -rf ReadCalendar

The main assumption of publishing a particular project is that MSBuild configuration for the project contains custom tasks t:/FOOPublish. Note that FOOPublish is a custom publish task for FOO project, you will have to define your own and you can call it anything as far as it is reflected correctly in the POM. Not all sub-projects under parent are publishable so profile section in the parent POM defines it's own module list (see Configuration section for details)

The command to publish. Note addition "-P publish" switch

Configuration

The complete configuration is split between the parent POM and at least 2 flavors of child POM depending if the child project is self-sufficient or specifies some dependencies

Please note that all plugin configurations described in the follow-up sections are placed into <profiles/> section of POM

pom.xml/profiles

Dependencies

The dependencies are configured in child POM only. Since dependencies stored as ZIP files we cannot use common dependency mechanism provided by Maven. Instead we are using maven-dependency-plugin unpack artifact feature. Here's complete configuration from FOO/CalendarView project that defines dependency on FOO/ReadCalendar. Note that we have ability to specify a particular version

CalendarView/pom.xml

In this case - complete configuration is contained within child POM. The way it reads from top to bottom:

  • Define plugin maven-dependency-plugin
  • Define plugin's execution with ID = unpack
  • Execution will happen in Maven's process-resources lifecycle phase
  • Plugin's unpack goal will be executed
  • The goal will get specified artifactItem from Maven repo (the ZIP file) and exctract it into PROJECT_ROOT/lib directory
  • To make your .NET code aware of extracted artifact(s) update <HintPath/> for each dependency in you Foo.csproj file
    foo.csproj

Cleanup and Build

This is "the meat" of the whole process. The configuration is split between the parent POM and child(ren) in such way that parent POM contains (in majority of cases) complete definition of profile(s) which keeps children POMs clean from repeatable code.
To suppress execution of profile in the parent you always need to execute parent-based build with --resume-from or -rf switch

Parent pom.xml
  • Two execution targets are defined: clean and build
  • Note how dynamic parameters ${foo.config} and ${foo.cpu} are used to be able to change configuration at run time

Packaging (for Install or Deploy)

The packaging of artifacts requires two plugins and one assembly descriptor. The configuration is split between 2 files: parent POM and assembly descriptor.

Pre-packaging

Step 1 is to scan project for build artifacts and collecting these to lib directory. It's done by maven-antrun-plugin. Here's configuration

Parent pom.xml

As you can see DLL, EXE and PDB files are collected to child's PROJECT_ROOT/target/dll-staging directory

Notice how this task is run at prepare-package phase

ZIPping up

This is done by maven-assembly-plugin

Parent pom.xml
assembly.xml

Note that <include> and <exclude> configuration here is redundant since files are already filtered by pre-packaging step.
<includeBaseDirectory>false</includeBaseDirectory> is used to ensure that zipped files will go to the top directory of archive

References

There are few discussions on stackoverfow.com that I started during writing of this tutorial that you may find useful.

About author

I run Mea Cup O' Jo and DroidIn - Android app for LinkedIn blogs.

  • No labels