Creating Xamarin.Mac App Distribution in DMG File

A simple guide on how to generate DMG bundle from Xamarin.Mac application using bash script

When you finished developing a macOS desktop app using Xamarin and want to distribute it to end user, you have 3 choices using Visual Studio: Apple App Store distribution, save to disk as .app file, and portable .pkg installer. Except for .app distribution, you’re going to need an Apple Developer ID to create them.

Now, what if you currently for some reasons don’t have an Apple Developer ID.

One solution is you can just zip the .app file and distribute it. But personally, I think that’s not an elegant solution because some users don’t know that they should put the file to /Applications/ folder. Even though that’s not mandatory, it’s better to organize all the apps inside /Applications/ folder.

The other solution is to create a .dmg file installer, which is probably more familiar for macOS users. Usually using a background image to instruct the user to drag and drop the .app file to /Applications/ folder. But unfortunately, Visual Studio doesn’t provide this out of the box.

That’s why in this tutorial, I’ll show you how to create a .app file and bundle it in .dmg file automatically using the shell script.

For your illustration, this is how the final distribution file we’re going to make.

Xamarin Mac app bundled in dmg
Xamarin Mac app bundled in dmg

Now let’s get started with the solution.

We’re going to use andreyvit/create-dmg for our solution because it’s written in bash that makes it less dependency. Which mean you can just put it inside your repository and just execute on any machine.

First, let’s assume this is how your project structured. Put solution file and projects files under src folder.

.
└── src
    ├── XamMacDemo
    │   ├── AppDelegate.cs
    │   ├── Assets.xcassets
    │   ├── Info.plist
    │   ├── Main.cs
    │   ├── MainMenu.xib
    │   ├── MainWindow.cs
    │   ├── MainWindow.designer.cs
    │   ├── MainWindow.xib
    │   ├── MainWindowController.cs
    │   ├── MainWindowController.designer.cs
    │   ├── XamMacDemo.csproj
    └── XamMacDemo.sln

Now let’s add the script to scripts/create-dmg directory. Since I’m using git for everything, I’ll show you an example using git subtree command.

git subtree add --prefix scripts/create-dmg https://github.com/andreyvit/create-dmg.git master --squash

If the process is executed successfully, the output would look like this.

git fetch https://github.com/andreyvit/create-dmg.git master
warning: no common commits
remote: Counting objects: 179, done.
remote: Total 179 (delta 0), reused 0 (delta 0), pack-reused 179
Receiving objects: 100% (179/179), 42.24 KiB | 94.00 KiB/s, done.
Resolving deltas: 100% (92/92), done.
From https://github.com/andreyvit/create-dmg
 * branch            master     -> FETCH_HEAD
Added dir 'scripts/create-dmg'

Then this is how your folder structure will look after adding the create-dmg script.

.
├── scripts
│   └── create-dmg
└── src
    └── XamMacDemo

If you don’t use git, that’s fine. You can also just download them manually and put them under the scripts folder.

Next, we’re going to use a background image that has an arrow in it. To quickly test this script, I download and use background image from here. Let’s name it dmg-background.png and put it in the root folder.

After that, we’re going to create a new bash script file on the root folder, too. You can create it using the command line with the following snippet. Don’t forget to execute chmod +x, that’s important to make the file executable.

touch create-app-dmg.sh
chmod +x create-app-dmg.sh

Open the bash script file using your favorite editor then copy following script to your file.

#!/bin/bash

PROJECT_NAME=XamMacDemo
APP_NAME=Xamarin\ Mac\ Demo
APP_VERSION=1.0.0

CURRENT_DIR=$PWD

cd $CURRENT_DIR/src/

# restore NuGet packages
nuget restore

# build project in Release mode
msbuild ${PROJECT_NAME}.sln \
    /t:"${PROJECT_NAME}:rebuild" \
    /p:Configuration="Release"

cd $CURRENT_DIR/scripts/create-dmg/

mkdir -p $CURRENT_DIR/setup/app

rm -rf $CURRENT_DIR/setup/*.dmg

cp -r \
    "$CURRENT_DIR/src/${PROJECT_NAME}/bin/Release/${APP_NAME}.app" \
    "$CURRENT_DIR/setup/app/"

./create-dmg \
    --volname "$APP_NAME Installer" \
    --background $CURRENT_DIR/dmg-background.png \
    --window-size 660 400 \
    --icon-size 160 \
    --app-drop-link 480 170 \
    --icon "$APP_NAME.app" 180 170 \
    "$CURRENT_DIR/setup/${APP_NAME}-v${APP_VERSION}.dmg" \
    $CURRENT_DIR/setup/app

rm -rf $CURRENT_DIR/setup/app/

So what does this script do actually?

  1. Execute NuGet package restoration to your project.
  2. Build final executable using msbuild in Release mode.
  3. Create and bundle .app the file generated from the previous step to .dmg file.
  4. Put the .dmg file under setup/ directory.

Now depending on your project file, you need to change some variables.

  • PROJECT_NAME: this is your Xamarin.Mac project name, usually the same as your .csproj file. For example, I use XamMacDemo because it’s the project name I created.
  • APP_NAME: This is the final generated .app file name. This is usually defined under Info.plist file.
  • VERSION: This is to define what version to build and distribute.

After you changed those variables, you can try executing the script to generate a .dmg file.

./create-app-dmg.sh

If finished successfully, a .dmg file will appears under setup/ folder like the following figure.

.
├── create-app-dmg.sh
├── dmg-background.png
├── scripts
│   └── create-dmg
├── setup
│   └── Xamarin\ Mac\ Demo-v1.0.0.dmg
└── src
    ├── XamMacDemo
    └── XamMacDemo.sln

This is one portable solution I really like because you don’t need a special dependency to build on every machine. Next, I’ll show you an alternative without creating a bash script.

Alternative Solution

A quick Google search for command line tool to generate dmg file show me results with not one, but two scripts called create-dmg.

The first one is the one we used for the previous solution, the other one is sindresorhus/create-dmg.

This is actually the easiest command line tool I’ve ever found. All you have to do is to install it using npm.

npm install --global create-dmg

Then continue with shell execution like the following snippet.

create-dmg ${APP_NAME}.app

Finally, a DMG file will be magically generated, the same result with the previous solution.

The only drawback is that it requires you to install Node.js v8 or later on your machine. This won’t be a problem for a personal or single machine. But asking every clients or colleague to install Node.js on their machine is somehow troublesome if the purpose is just to use create-dmg (not to mention they also have to install Visual Studio on their machine).

But the choice is yours, for portability I would use andreyvit/create-dmg and for “It just works” I would use sindresorhus/create-dmg.

Summary

As you can see, even though Visual Studio doesn’t provide DMG creation out of the box, we can create it easily using an external shell script. Using shell script not just automate DMG creation, but also build the executable file automatically.

Fork or download the completed project produced by this tutorial on GitHub.