wiki:howto/CreateInstallers

How to create standalone installers with MacPorts

Table of Contents

  1. Audience
  2. Non-default prefix
  3. Virtual machines for development and testing
  4. Variants
  5. No post-activate
  6. preinstall / postinstall scripts
  7. Startupitems
  8. Work directories
  9. Useful utilities

Audience

This page summarizes some experience from building an installer with MacPorts facilities. The reader is assumed to be relatively comfortable using MacPorts and the command line.

Building an installer package may unearth dependency issues or the need for packaging scripts (see following sections). You may need to work with the maintainer of the port you wish to package (if you are not that port's maintainer). You may also need to work with the maintainers of ports that your target port depends on.

Non-default prefix

As the official documentation says, "make sure you do not use a standard installation of MacPorts in /opt/local."

https://guide.macports.org/#using.binaries.binary-packages

The reason is simple. An installer can easily overwrite newer software with older--possibly incompatible--versions creating havoc on the end user's system.

Even with a custom prefix, software installed by a MacPorts-created installer may still conflict with software installed directly by MacPorts due to competing use of the /Applications or /Library/Launchdaemons folders. For example, if your software package needs a daemon, your installer will need a launchd plist in /Library/Launchdaemons. The installer may therefore overwrite a plist installed during a sudo port install foo, or vice versa.

Virtual machines for development and testing

I have found using virtual machines to be essential for developing and testing an installer due to the issues with conflicting launchd plists, etc, discussed in the previous section. Further, using VM's makes it possible to build under one Mac OS version and test deployment in a different version or even into multiple versions.

The environment for building the installer will need MacPorts installed in a non-default prefix and thus, of course, XCode and the XCode command line tools.

The environment for testing deployment of the packaged software likely should not have MacPorts installed. This will help identify missing dependencies.

I've used Parallels as, at the time I started, it seemed to have the best support for running a variety of Mac OS guest operating systems. It appears that Virtual Box may have improved in this regard recently and could therefore be a less-expensive option.

Variants

Variants can have a significant impact on the dependencies that incorporated into your installer. For example, I found (early on) that both python26 and python27 were being incorporated into my installer. By requesting a +python27 variant, there was a substantial savings in the size of the installer created.

You have to specify your desired variants each time you run mpkg or mdmg. Otherwise, the default variants will be used. For example, to produce an installer for mythtv.28, I use:

sudo port mpkg mythtv.28 -x11+mariadb+mariadb55+python27+perl5_24+startupitem

No post-activate

Some ports define steps to be run at the time the port is activated, usually in a post-activate { ... } block. An installer needs another way to achieve the same result since it has no way to run the post-activate code. The normal method is to either use a postinstall script or a pre-pkg action depending on whether the steps can be taken before the installer is created or after the payload has been copied in place. See the following sections on scripts and startupitems.

If you have a significant number of dependencies, it can be difficult to know which, if any, actually utilize post-activate steps. My approach is as follows:

port cat rdepof:foo +somevariant-someother |edit

The above command concatenates all the Portfiles of all the dependencies of port 'foo' and pipes the output into TextWrangler's edit. Now, I simply search for -activate to locate any such blocks of code. You can then determine the appropriate way to handle the required steps.

For example, fontconfig has the following post-activate block:

post-activate {
    # fc-cache can fail due to /Network/Library/Fonts being unavailable, so force success.
    system "${prefix}/bin/fc-cache -sv || true"
    system "${prefix}/bin/fc-cache -v || true"
}

The fc-cache utility can't be run at the time the package is created since the user may have some fonts installed on their system (outside the installer). Thus we should use a postinstall script to achieve. See the example in the following section.

preinstall / postinstall scripts

MacPorts supports including preinstall and/or postinstall scripts for each component package.

These scripts are run at the obvious times before and after the payload is copied into place. The scripts run with superuser privileges. What is less obvious is that they are run in a sandbox where certain locations are inaccessible, such as /tmp. If your script works in testing but fails when run by the installer, the sandbox may be the cause.

Also, if a preinstall or postinstall script returns a non-zero result code, the installer will report that the install failed.

To include a preinstall or postinstall script in the package, it needs to be copied to a particular folder in a pre-pkg phase. The following example is from fontconfig:

pre-pkg {
    xinstall -m 0755 ${filespath}/postinstall ${package.scripts}/
    reinplace -locale C "s|@PREFIX@|${prefix}|g" ${package.scripts}/postinstall
    long_description-append  Install prefix: ${prefix}
}

Keep in mind that the installer may be:

  • running on a virgin system, or
  • re-installing over top of a previous install, or
  • upgrading from an older version to a newer one.

If your script copies a default configuration file into place on a virgin system, you probably want to check if the file already exists before replacing it with the default version. Otherwise you may undo your user's modifications.

Another issue, at least currently, is that MacPorts-created installers seem to execute the scripts twice. Again, your scripts should avoid repeating actions that may already be done. In the case of fontconfig, it doesn't hurt anything if the fc-cache utility is run multiple times. Thus the postinstall script is simply:

#!/bin/sh

# fontconfig installer support, postinstall script
# runs as root after installer successfully copies payload to destination
# thus picks up _any_ fonts that were delivered with this installer

# fc-cache can fail due to /Network/Library/Fonts being unavailable, so force success.
@PREFIX@/bin/fc-cache --system-only --verbose || true

When debugging preinstall / postinstall scripts, you can find output from the script in the installer log. In the Installer app, got to Window > Installer Log.

Startupitems

If the software you are packaging relies on daemons set up with 'startupitem.create yes', they need a MacPorts-provided utility (daemondo) that will not necessarily be present in your package. The hack to get around this problem is to force a copy of the daemondo binary into the package. The following is an example from the apache2 port:

if {[info exists pkg.asroot]} {
        pkg.asroot      yes
}

pre-pkg {
    if {![info exists pkg.asroot]} {
        ui_error "Packaging ${name} ${version} requires MacPorts 2.3.5 or greater (pkg.asroot support)"
        return -code error "Incompatible MacPorts version"
    }

    # at this point the destroot has been pruned and bin doesn't exist
    xinstall -d -m 0755 ${destroot}${prefix}/bin
    # apache2 needs daemondo; ram a copy into the destroot so it will be packaged
    xinstall -m 0755 ${prefix}/bin/daemondo ${destroot}${prefix}/bin/
}

We need root permissions to carry out this step. Before MacPorts version 2.3.5, the pre-pkg step wasn't able to run as root. 'pkg.asroot' was added for 2.3.5 to fix this problem.

Work directories

The first time sudo port mpkg foo +variant1-variant2 is run will take extra time and disk space. Each dependency is processed to the destroot stage and therefore nearly doubles the disk space consumed. Subsequent runs will proceed much more quickly.

The installer for 'foo' (when it is successfully created) will be in the work directory for foo. port work foo will give you the path; open $(port work foo) will open a Finder window for that directory.

If you wish to clean up or 'start fresh' for any reason, sudo port clean rdepof:foo to delete all the work directories of the dependencies. Of course, sudo port clean foo will clean that work directory and delete any installer that might be in there.

Useful utilities

You may wish to look at the contents of an installer. One choice is Pacifist (free to try but nags for registration). A free alternative is Suspicious Package:

http://www.mothersruin.com/software/SuspiciousPackage/


<- Back to the HOWTO section

Last modified 10 months ago Last modified on Feb 17, 2017, 6:12:35 PM