App Development

Automatic Build Numbering with Xamarin

blog-featured-image automatic build numbering with xamarin

In any software product, it’s important to have continuous integration set up to manage builds for the team. One important responsibility of a continuous integration server is uniquely identifying each build. For a mobile project, this means having a build number in both the Android APK file and the iOS IPA file.

We ran into an issue where our CI solution for a cross-platform Xamarin project had every artifact marked as version 1.0. This caused issues for QA when trying to communicate which builds presented bugs. We’ve solved this issue for native development by using build time Gradle properties on Android and agvtool on iOS. However, there was not a simple solution for Xamarin. We had to come up with one on our own.

What we ended up doing is adding the following XML snippets to the Android csproj file and the iOS csproj file:

android:
  <Target Name="BeforeBuild" Condition=" $(SetVersion) == true ">
        <GetAssemblyIdentity AssemblyFiles="$(AssemblyPath)">
            <Output TaskParameter="Assemblies" ItemName="AssemblyInfo" />
        </GetAssemblyIdentity>
        <PropertyGroup>
            <VersionNumber>$([System.Text.RegularExpressions.Regex]::Match(%(AssemblyInfo.Version), `[^.][^.]*.[^.]*.[^.]*`))</VersionNumber>
        </PropertyGroup>
        <XmlPoke XmlInputPath="Properties\AndroidManifest.xml" Namespaces="&lt;Namespace Prefix='android' Uri='http://schemas.android.com/apk/res/android' /&gt;" Query="manifest/@android:versionCode" Value="$(BuildNumber)" />
        <XmlPoke XmlInputPath="Properties\AndroidManifest.xml" Namespaces="&lt;Namespace Prefix='android' Uri='http://schemas.android.com/apk/res/android' /&gt;" Query="manifest/@android:versionName" Value="$(VersionNumber).$(BuildNumber)" />
  </Target>

iOS:
    <Target Name="BeforeBuild" Condition=" $(SetVersion) == true ">
        <GetAssemblyIdentity AssemblyFiles="$(AssemblyPath)">
            <Output TaskParameter="Assemblies" ItemName="AssemblyInfo" />
        </GetAssemblyIdentity>
        <PropertyGroup>
            <VersionNumber>$([System.Text.RegularExpressions.Regex]::Match(%(AssemblyInfo.Version), `[^.][^.]*.[^.]*.[^.]*`))</VersionNumber>
        </PropertyGroup>
        <XmlPoke XmlInputPath="Resources/Info.plist" Query="//dict/key[. = 'CFBundleVersion']/following-sibling::string[1]" Value="$(BuildNumber)" />
        <XmlPoke XmlInputPath="Resources/Info.plist" Query="//dict/key[. = 'CFBundleShortVersionString']/following-sibling::string[1]" Value="$(VersionNumber)" />
  </Target>

…and adding /p:SetVersion=true /p:BuildNumber={build number} /p:AssemblyPath={path to assembly file with shared assembly version} to the list of MSbuild arguments.

This adds a new step to MSbuild to properly set the build number before compiling the code. Let’s go into what each bit does.

Before executing the step, it checks the build property SetVersion If it’s missing or set to false, this step is skipped entirely. The reason we have this is so that developers don’t change their own files when building locally.

For the Version Number, rather than having to come into each project file to manually edit each incremental version change, and possibly having mismatched Version Numbers, we are using the version number from the Xamarin Project’s AssemblyInfo file. Since this Assembly Version is always 4 segments, and we are generating our own Build Number, we use regex to pull out the Major.Minor.Patch number from the Assembly Version and store that in a variable to use later.

Next, XmlPoke runs an xPath query on a given XML file and replaces the matches with the provided value. On Android, this targets the AndroidManifest.xml file, which contains the “versionCode” attribute. On iOS, the value is CFBundleVersion in the Info.plist file. Fortunately, the plist is stored as XML, so we can use the same technique to edit it.

After updating versionCode and CFBundleVersion, we use XmlPoke again to update versionName and CFBundleShortVersionString. These values are updated to “{Version number} {Build number}” for Android and “{Version number}” for iOS due to differences in versioning standards for each platform.

By applying this solution we were able to put our CI system’s build number directly into the build artifacts automatically, and keep our developers from having to update the value manually. In the end, we were able to leverage Microsoft’s MSbuild tooling to solve a mobile-specific problem.

Quickstart-Guide-to-Kotlin-Multiplatform

A Quick Start Guide to Kotlin Multiplatform

Kotlin Multiplatform, though still experimental, is a great up-and-coming solution...

Read the article