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).
|
You may also consider using NPanday, which provides direct support for building .NET projects using Maven. |
This tutorial will not teach you basics of Maven. You should be aware of Maven lifecycle, standard directory layout, plugin (mojo) mechanism and profiles |
FOO/trunk/Projects/pom.xmlFOO/trunk/Projects/CalendarView/pom.xmlPROJECT_ROOT/lib directory. (More about that in the following sections)MSBuild /t:Clean in addition to built-in clean taskMSBuild /t:Build task which does the actual job of compiling and assemblingYou 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)
mvn clean install |
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
mvn clean install -Dfoo.config=Debug |
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
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 is handled by exec-maven-plugin which simply executes MSBuild.exe and provides runtime parameters. See follow-up configuration section for details
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:
maven-antrun-plugin during prepare-package phase and collected artifacts (DLL, EXE, PDB) are placed (flattened) into PROJECT_ROOT/target/dll-staging directorymaven-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 POMTo 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
mvn verify \-P publish \-rf CalendarView |
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
<project>
...
<profiles>
<profile>
<id>build</id>
<build>
<plugins>
<!-- Plugins configuration here -->
</plugins>
</build>
</profile>
</profiles>
</project>
|
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
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack</id>
<phase>process-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>mycompany.dept.foo</groupId>
<artifactId>ReadCalendar</artifactId>
<version>1.0-SNAPSHOT</version>
<type>zip</type>
<outputDirectory>${project.basedir}/lib</outputDirectory>
<overWrite>true</overWrite>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
|
In this case - complete configuration is contained within child POM. The way it reads from top to bottom:
maven-dependency-pluginprocess-resources lifecycle phasePROJECT_ROOT/lib directory<HintPath/> for each dependency in you Foo.csproj file
<Reference Include="ReadCalendar, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\lib\ReadCalendar.dll</HintPath>
</Reference>
|
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
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<executable>${dotnet.path}/msbuild</executable>
</configuration>
<executions>
<execution>
<id>clean</id>
<phase>clean</phase>
<configuration>
<arguments>
<argument>/p:Configuration=${foo.config}</argument>
<argument>/p:Platform=${foo.cpu}</argument>
<argument>/t:Clean</argument>
</arguments>
</configuration>
<goals>
<goal>exec</goal>
</goals>
</execution>
<execution>
<id>build</id>
<phase>compile</phase>
<configuration>
<arguments>
<argument>/p:Configuration=${foo.config}</argument>
<argument>/p:Platform=${foo.cpu}</argument>
<argument>/t:Build</argument>
</arguments>
</configuration>
<goals>
<goal>exec</goal>
</goals>
</execution>
</executions>
</plugin>
|
The packaging of artifacts requires two plugins and one assembly descriptor. The configuration is split between 2 files: parent POM and assembly descriptor.
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
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.3</version>
<configuration>
<tasks>
<copy todir="${project.build.directory}/dll-staging">
<fileset dir="${basedir}">
<include name="**/*in/${foo.config}/*.dll" />
<include name="**/*in/${foo.config}/*.pdb" />
<include name="**/*in/${foo.config}/*.exe" />
<exclude name="**/Test*" />
<exclude name="**/test*" />
</fileset>
<flattenmapper />
</copy>
</tasks>
</configuration>
<executions>
<execution>
<id>copy</id>
<phase>prepare-package</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
|
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
This is done by maven-assembly-plugin
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>../assembly.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>zip</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
|
<assembly>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${project.build.directory}/dll-staging</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>*.dll</include>
<include>*.exe</include>
<include>*.pdb</include>
</includes>
<excludes>
<exclude>Test*</exclude>
<exclude>test*</exclude>
</excludes>
</fileSet>
</fileSets>
</assembly>
|
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
There are few discussions on stackoverfow.com that I started during writing of this tutorial that you may find useful.
I run Mea Cup O' Jo and DroidIn - Android app for LinkedIn blogs.