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).
Prerequisites
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/libdirectory. (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:
- Clean - Execute
MSBuild /t:Cleanin addition to built-in clean task - Prepare - Identify dependencies and copy/extract artifacts into specified location under project root (currently{{PROJECT_ROOT/lib}}
- Build - Execute
MSBuild /t:Buildtask which does the actual job of compiling and assembling - Install - Collect build artifacts (DLL, EXE), create a ZIP and install into Maven repo so it can be used for deployment or as dependency
- Publish - Copy artifacts and dependencies to IIS or file server
- Clean - Execute
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.
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-pluginduringprepare-packagephase and collected artifacts (DLL, EXE, PDB) are placed (flattened) intoPROJECT_ROOT/target/dll-stagingdirectory - Next
maven-assembly-plugincreates 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
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
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-resourceslifecycle 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/libdirectory - To make your .NET code aware of extracted artifact(s) update
<HintPath/>for each dependency in you Foo.csproj filefoo.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
- 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
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
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.
- Maven - activate child profile based on property
- Maven - skip parent project build
- Maven creating flat zip assembly
- Streamline .NET projects with Msbuild
About author
I run Mea Cup O' Jo and DroidIn - Android app for LinkedIn blogs.
