Over the last year or so, I’ve created a number of packages, all of which have been created using the Umbraco UI. Whilst there is nothing wrong with doing it this way, I’ve been wanting to find a way to automate this for some time.
I started out by asking the awesome Umbraco community, which thanks to a tip-off by Sebastiaan Janseen, led me to the source code for Blog4Umbraco which has an example of creating a package using NAnt. This was a great start, but I was ideally looking for something using MSBuild, and I felt it could be streamlined a little. With that, I thought I’d have a go at porting it over, and making the improvements I thought it needed.
Prerequisites
As I’m using MSBuild, you’ll obviously need to have the .NET framework installed.
Package.xml
The first thing you’ll need to create is a base manifest file. If you’ve ever looked inside a package file, this will probably look fairly familiar to you. The manifest file contains the details of everything to be installed. Much of this can be created by hand, so the idea is to fill in the static bits, and use automation to fill in the bits that change more frequently. In the example demonstrated in this article, this will be the package version number, the readme info and the package files.
What follows is an example manifest file, as used in my Universal Media Picker package.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<umbPackage>
<info>
<package>
<name>Universal Media Picker</name>
<version>0.0</version>
<license url="http://www.opensource.org/licenses/mit-license.php">MIT license</license>
<url>http://our.umbraco.org/projects/backoffice-extensions/universal-media-picker</url>
<requirements>
<major>4</major>
<minor>5</minor>
<patch>0</patch>
</requirements>
</package>
<author>
<name>Matt Brailsford</name>
<website>blog.mattbrailsford.com</website>
</author>
<readme><![CDATA[]]></readme>
</info>
<DocumentTypes />
<Templates />
<Stylesheets />
<Macros />
<DictionaryItems />
<Languages />
<DataTypes />
<Actions>
<Action runat="install" alias="addApplicationTree" silent="false" initialize="false" sortOrder="0" applicationAlias="media" treeAlias="UniversalMediaPicker" treeTitle="Media" iconOpened="folder_o.gif" iconClosed="folder.gif" assemblyName="TheOutfield.UmbExt.UniversalMediaPicker" treeHandlerType="UniversalMediaPickerAppTree" action="" />
</Actions>
<control>/umbraco/plugins/TheOutfield/UniversalMediaPicker/ProviderInstaller.ascx</control>
<files />
</umbPackage>
Package.build.xml
The next thing you’ll need is the build script itself. I’m not going to go into the syntax of it, as there are already plenty of examples of this online, but to outline the example that follows, we first import the custom MSBuild tasks and define a number of properties, and then go on to fire a series of targets in the following order.
- Clean: Cleanup any previous builds
- Compile: Compile the Visual Studio project
- PrepairFiles: Copy the files to package up into a temporary folder, arranging them into their final folder struture
- Manifest: Update the manifest file with the version number, readme information and file references
- Package: Generate a package file
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Package">
<!--
****************************************
* IMPORTS
****************************************
-->
<Import Project="$(MSBuildProjectDirectory)\Lib\MSBuildUmbracoTasks\MSBuild.Umbraco.Tasks.Targets" />
<!--
****************************************
* PROPERTIES
****************************************
-->
<PropertyGroup>
<PackageVersion>1.0</PackageVersion>
</PropertyGroup>
<PropertyGroup>
<RootDir>$(MSBuildProjectDirectory)</RootDir>
<BuildDir>$(RootDir)\Build</BuildDir>
<PackageDir>$(RootDir)\Package</PackageDir>
</PropertyGroup>
<!--
****************************************
* TARGETS
****************************************
-->
<!-- CLEAN -->
<Target Name="Clean">
<RemoveDir Directories="$(BuildDir)" Condition="Exists('$(BuildDir)')" />
<RemoveDir Directories="$(PackageDir)" Condition="Exists('$(PackageDir)')" />
<MakeDir Directories="$(BuildDir)" />
<MakeDir Directories="$(PackageDir)" />
</Target>
<!-- COMPILE -->
<Target Name="Compile" DependsOnTargets="Clean">
<MSBuild Projects="TheOutfield.UmbExt.UniversalMediaPicker.csproj" />
</Target>
<!-- PREPAIRE FILES -->
<Target Name="PrepairFiles" DependsOnTargets="Compile">
<ItemGroup>
<BinFiles Include="$(RootDir)\Bin\TheOutfield.UmbExt.UniversalMediaPicker.dll" />
<BinFiles Include="$(RootDir)\Bin\TheOutfield.UmbExt.UniversalMediaPicker.pdb" />
<PluginFiles Include="$(RootDir)\Images\*.*" />
<PluginFiles Include="$(RootDir)\Dialogs\*.aspx" />
<PluginFiles Include="$(RootDir)\Controls\*.ascx" />
<PackageFile Include="$(RootDir)\Package.xml" />
</ItemGroup>
<Copy SourceFiles="@(BinFiles)" DestinationFolder="$(BuildDir)\bin" />
<Copy SourceFiles="@(PluginFiles)" DestinationFolder="$(BuildDir)\umbraco\plugins\TheOutfield\UniversalMediaPicker" />
<Copy SourceFiles="@(PackageFile)" DestinationFolder="$(BuildDir)" />
</Target>
<!-- MANIFEST -->
<Target Name="Manifest" DependsOnTargets="PrepairFiles">
<ItemGroup>
<ManifestFiles Include="$(BuildDir)\**\*" Exclude="$(BuildDir)\Package.xml" />
</ItemGroup>
<ReadLinesFromFile File="$(RootDir)\Readme.txt">
<Output TaskParameter="Lines" ItemName="Readme" />
</ReadLinesFromFile>
<ManifestUpdate ManifestFile="$(BuildDir)\Package.xml"
WorkingDirectory="$(BuildDir)"
PackageVersion="$(PackageVersion)"
Readme="@(Readme->'%(Identity)', '%0a%0d')"
Files="@(ManifestFiles)" />
</Target>
<!-- PACKAGE -->
<Target Name="Package" DependsOnTargets="Manifest">
<ItemGroup>
<PackageFiles Include="$(BuildDir)\**\*.*" />
</ItemGroup>
<Package ManifestFile="$(BuildDir)\Package.xml"
WorkingDirectory="$(BuildDir)"
OutputDirectory="$(PackageDir)"
Files="@(PackageFiles)" />
</Target>
</Project>
Package.build.cmd
Now that we have the build script defined, you’ll want an easy way to fire it off to generate the package file. The simplest way to do this, is to just create a simple DOS command file as follows:
C:\WINDOWS\Microsoft.NET\Framework\v3.5\msbuild.exe Package.build.xml
Once this is set up, any time you want to create a new package file, all you need to do is double-click the .cmd file, and bobs your uncle.
The next step would be to integrate it with a build automation server like Cruise Control or Team City, but it definitely feels like a step in the right direction.
Download
You can download the main files here, but if you’d like to see an example of it in a project, you may want to checkout the source for my Universal Media Picker package.