|
Volume Number: 22 (2006)
Issue Number: 12
Column Tag: Programming
Distributing with PackageMaker
Building a distribution installer package
By José R.C. Cruz
Introduction
Software product quality is judged on several factors. Firstly, the product must provide the features needed by users to do their tasks effectively and efficiently. Secondly, it must provide a localized user interface that is both consistent and intuitive. Thirdly, it must be accompanied by an extensive online help system, explaining each feature in simple and concise terms.
Finally, the product must be easy to install or upgrade. It is a harsh fact that high-quality software is easily marred by poor installation and buggy upgrade experiences. This is especially true with Windows software, which can fail when a file is incorrectly installed, or replaced by an older and perhaps faulty version.
The MacOS X issue
Most MacOS X software is distributed as bundles. A bundle is a special directory used to contain files that comprise the software product. For a software application, the bundle will contain the executable code, driver code, graphic files, and help files. For a software driver, it will contain the executable code, and a collection of plist, or property list, files.
Because of bundles, different techniques are required to distribute MacOS X software. The simplest one is to distribute the software as an archived file. The most popular archive formats in use are .sit, .zip, and the ever-popular .gz.tar (also known as the tarball format). Users download the archive file, and decompress it to retrieve the software product. They then manually move the product to the desired directories. This technique is often used for distributing freeware and shareware products.
Another technique is to distribute the software as a disk image file. When the file is double-clicked, it is mounted on the Finder desktop like a regular disk volume. Users can then move the software from the image to the desired directory. This technique is popular for distributing single software applications.
However, both techniques expect users to know beforehand if their systems can run the software product. Both also expect users are also expected to know how to set the file or directory permissions if required. Finally, neither technique can be localized for a specific region.
To address these issues, Apple provides the tool to create an installation package that is both localizable and scriptable. The name of the tool is PackagerMaker.
Introducing PackageMaker
The PackageMaker Tool
The PackageMaker tool is used to prepare software products for distribution. It creates a specialized bundle, called a component package (.pkg) to contain the software product or payload. The package is then processed by the Installer application, which is bundled in all MacOS X systems. It is this application that extracts the payload and moves it to the appropriate directories.
To learn more about component packages, read the Software Delivery Guide manual, which is available from Apple Computers.
Two other types of bundles that can be created are metapackages and distribution packages. Both are used to contain multiple component packages. They also allow users to select which payload to install on their computers.
Until recently, a metapackage is the only way to distribute multiple payloads to the public. This is no longer the case with MacOS X 10.4, and the more manageable distribution package.
The PackageMaker Competition
PackageMaker is not the only installer tool available for the MacOS X platform. Other companies also provide their own installer products for that platform. Three of the most popular ones on the market are InstallerMaker, InstallerVise, and InstallAnywhere.
All three installer products use proprietary algorithms and file formats to contain their payloads. Also, InstallerMaker and Installer Vise will correctly handle files that have resource forks. Both Installer Vise and InstallAnywhere also provide a feature called billboards. These are used to display additional information while the payloads are being installed.
Finally, all three products can create installers that are localized for multiple regions. Also, InstallerVise and InstallAnywhere can create installers for multiple platform targets. InstallerMaker, on the other hand, supports MacOS X as its sole target platform.
Pricing for InstallerMaker and InstallerVise are based on a per volume basis. It can range from $250 to $3000 a year, depending on the distribution volume of each payload. On the other hand, InstallAnywhere can be purchased for a one-time fee of no more than $3500. InstallerVise can also be purchased for a one-time fee, but only if it is used for in-house or educational projects.
For an open-source alternative, there is the Iceberg tool from Stéphane Sudre. This tool can also be used to create component packages and metapackages. It also has a more streamlined interface for supporting localization and scripting. It can even create metapackages without the need for component packages. Its only disadvantage is that it does not support distribution packages. However, this could change in future versions. [Ed. Note - also be aware that Iceberg requires a daemon that runs as root, and that makes some people uncomfortable. That said, Iceberg has an easy to use UI that makes creating packages very easy.]
The PackageMaker Advantage
The most notable advantage of the PackageMaker tool is that it is free. Developers only have to download the latest version of Xcode to get a copy of this tool. However, the download does require a high-speed connection. This is because the entire Xcode installer itself is nearly 1GB in size. It would be more convenient for some developers if PackageMaker could be downloaded separately.
Another advantage is the ease in which the tool supports localization. Localized copies of Readme and Software License files can be loaded into the appropriate .lproj bundle. Text used by some interface elements, such as warning dialogs, can also be localized using a .string file. Also, testing the localized package is as simple as changing the preferred language setting in System Preferences.
Finally, the tool allows scripts to be added to the installer package. Each script is executed by the Installer application at each stage of installation. The scripts are often used to perform platform checks, or to customize the installation.
The Distribution Package
The package format
The distribution package is the preferred format for handling multiple payloads for MacOS X 10.4 and newer. It provides a more organized storage for localized text and installer scripts. It also allows installation choices to be easily customized.
The structure of a basic distribution package is shown in Figure 1. Notice that each component package is now stored within the Packages directory. Also, notice that the Resource directory now only stores the .lproj bundles. As mentioned earlier, these bundles contain localized installer files such as the ReadMe and Software License files. Finally, the XML file, distribution.dist, will now contain the localized text and installer scripts.
You can still use a metapackage to deliver multiple software payloads. But, be aware that this format is now deprecated, and will be phased out in future versions of MacOS X. Use it only if you plan to support versions of MacOS X earlier than 10.4.
Figure 1. Structure of a distribution package bundle.
Creating the project
To start working on a distribution package, launch PackageMaker and choose New from the File menu. Select Distribution Project from the New Project Assistant (Figure 2) and click on the OK button. PackageMaker will then display an empty project window as shown in Figure 3. Make sure to save the installer project with the appropriate name.
Figure 2. The PackageMaker Assistant.
Do not use the Assist Me button to create a project for your distribution package. At the time of this writing, assistive support only exists for component packages and metapackages. Hopefully, support for distribution packages will be added by the time you read this statement.
Figure 3. The Distribution Package project window.
Defining the interface
As shown in Figure 3, the first panel displayed in the project window is Installer Interface. This is where you assign the official title of your distribution package. It is also where you define the user interface for your package. To start defining on your interface, click on the button labeled Show Installer Interface Editor. Then follow the instructions presented by each editor panel.
Three issues to keep in mind while working on your installer interface. First, the project always assumes that the package is localized for English. This is regardless of the current language set in System Preferences. In other words, even if the current language is set to French, localized files assigned to the project will be stored within an English.lproj bundle.
Second, the project accepts background image files rendered only in one of the following formats: EPS, GIF, JPEG, PDF, and TIFF. It will not accept PNG files, nor will it convert those files to one of the supported formats.
Finally, the project does not allow additional .lproj bundles to be easily added to it. The only workaround is to first build the distribution package. Then open the package using the contextual menu command, Show Package Contents. Once opened, the .lproj bundles can then be copied into the Resources directory of that package.
Hopefully, these three issues will be corrected in future versions of the PackageMaker tool.
Defining choices
Most of the time, your installer package only has a single payload to distribute to your users. This is especially true if the payload is a single self-contained application. Sometimes, however, your package may have more than one payload. In this case, you may want your users the option of choosing which payload to install. You may also want your installer package to decide which payload can be installed onto the target platform by default.
The PackageMaker tool now allows you to define the interface for installation choices. You can specify whether to provide an Easy Install or a Custom Install option. You can also specify which payload should be selected by default. Bear in mind that this feature is available only for the distribution package format.
The simplest way to define the interface for installation choices is to use the Contents panel (Figure 4) on the Installer Interface Editor. Through this panel, you can add, edit, or remove an installation choice. For example, click on the button labeled Add Choice to add a placeholder entry for a new choice. To edit, select the choice from the listbox and change its name or default states using the Contents Inspector.
You can also use the panel to select the default installation mode. Simply select the desired mode from the drop-down menu labeled User Sees. Choices include Easy and Custom Install, Custom Install only, and Easy Install only.
Figure 4. Defining installation choices.
Finally, you can also use the panel to create a hierarchical order of installation choices. Simply select a choice and move it to the desired spot on the listbox. Currently, there are no established limits to the number of hierarchical levels. But a good rule of thumb is to have no more than three levels of choices for visual reasons.
The example shown in Figure 4 has four installation choices defined. The Single product choice will install a single payload. It is also set as the default installation choice.
The Multiple products choice will install two payloads. It is further divided into three more choices, each one associated with a different payload. Notice that two of those subchoices are set to be invisible by default. A script will be used to display the appropriate subchoice based on the target platform. This will be explained later in this article.
Adding payloads
Whenever you add an installation choice, the PackageMaker tool also adds an entry for that choice in the project window. To assign a payload to an installation choice, first select the choice from the listbox. Then choose Add Package from the Project menu to display the file selection dialog. Navigate to the component package for the payload and click on the Open button to select it. Additional details on the assigned payload are then displayed using the Package Settings panel (Figure 5).
Figure 5. Details on the assigned payload.
In the example shown, the panel identifies the assigned payload as a local component package. It displays the size, bundle identifier, and version number of that package. It also shows that the package will be located inside the Packages directory. It further shows that installing the package will have to be authorized by the system administrator. It also shows that the Installer application will simply quit after installing the payload.
You can use the Package Settings panel to specify a different location for the payload's component package. One location is the same directory as the distribution package. Use this if you want users to be able to install the payload directly from the component package.
Another location for a component package is on a removable media. Use this option if your payloads are large enough that they cannot all fit into a single CD or DVD. One usage example is the CD distribution of MacOS X.
The third and final location is on a network server. Use this option if you want to do a network installation. A good usage example is the software update feature of MacOS X.
Note that these other locations are available only if you deselect the checkbox labeled Copy Into Distribution Package.
Others settings that you can make on the Package Settings panel are the authentication and post-install actions for the payload. The former action is executed before the payload is installed. The latter action, on the other hand, is executed after the payload is successfully installed.
If the payload has its own authentication or post-install action, those actions are displayed on the Package Settings panel. You can then change the type of authentication to be performed, but you cannot disable it. You can also change the type of post-install action as well as disable it entirely. These changes then override the settings used by the payload.
Localizing choices
When choices are defined through the Contents panel (Figure 4), the PackageMaker tool always assumes that the choices are localized for English. To localize these same choices to other languages, you need to use the Choice Settings panel (marked in green, Figure 6) on the project window.
To localize a specific choice, select the choice from the listbox located on the project window. Then click on the checkbox labeled Make localizable. The tool then assigns unique string identifiers for each text field. It also replaces the English text contained by these fields with the identifiers. But the replaced text is still available for editing as seen later on.
Figure 6. The Choice Settings panel.
Now to start adding localized text, click on the button labeled Edit Localizations. This will display a sheet dialog wherein you can enter your text. It will also display the original English text on that same dialog by default (Figure 7).
Figure 7. Localising the text for the installation choice.
In the Language text field, enter the designation code assigned for the target language. For instance, if you are localizing for French, enter the word French into the field. But, if you are localizing for Portuguese, enter the characters pt into that same field. These designation codes are part of the ISO-639 standard. For a complete list of language designations, go to http://icweb.loc.gov/standards/iso639-2/englangn.html.
Figure 8 shows an example of the installation choice text localized in French. Once you have entered the localized text, click on the Close button to submit your entries. The tool will save the localized text into the distribution.dist file when it builds the distribution package.
Figure 8. Localising for French.
Once you have finished defining the interface, choices, payloads, and localizations, you can build a distribution package to see the results. To build the package, choose Build from the Project menu. Then use the ensuing dialog to save the package into the appropriate location.
The Distribution Scripts
Controlling the Installer
As mentioned earlier, the distribution package uses scripts to control the installation process. These scripts can perform a variety of tasks such as validating the target platform, or checking to see if a target volume has enough space.
Figure 9 shows how the scripts are executed in a typical installation process. User actions that are enclosed in [square brackets] are considered optional.
Figure 9. Script execution within a distribution package.
After the user authenticates the installation process, the InstallationCheck script is executed. This script checks the target platform to see if it has the requirements necessary to support the payload. If the target passes the check, the software license agreement is then displayed to the user.
When the user accepts the terms of the agreement, the VolumeCheck script is then executed. This script tests every mounted volume to see if they have the storage capacity to contain the payload. Those volumes that fail the test will have a stop icon badge appended to their disk icon.
At this point, the user selects a target volume. Then, the component package is retrieved from the Resources directory. The preflight script of the package is first executed. Then, depending on the type of installation, the preinstall or preupgrade script is executed next. Once the payload is successfully installed, the postinstall or postupgrade script is executed. Finally, the postflight script of the package is executed to finish the installation. Afterwards, the desired post-install action is then performed.
Note that if either the InstallationCheck script or one of the package scripts fail, the entire installation process is then cancelled. Also, if the component package does not have any scripts, the post-install action is then performed after the payload is installed. But if it has its own InstallationCheck or VolumeCheck scripts, they are then ignored.
For the sake of simplicity, the diagram assumes that there is only one component package. If there is more than one package, their scripts are executed in a specific order. For instance, if the package names are package1 and package2, the preflight script for package1 is executed first. This is then followed by the one for package2.
Scripting with JavaScript
Distribution scripts are all written in JavaScript. This allows the scripts to be embedded within a separate document. The distribution package, for instance, stores its scripts inside the distribution.dist file.
JavaScript enables the scripts to interact with the control widgets on the installer interface. It helps reduce the number of installation errors with its exception handling system. It also simplifies script maintenance by using a standard runtime library. Make sure to use version 1.2 of the language when writing your scripts.
Both the PackageMaker tool and the Installer application use the same runtime library. This library provides three basic objects that can be used by the distribution script. The choices object is use to interact with the installation choices defined by the Contents panel (Figure 4). The system object is used to retrieve various platform data such as file paths, and IORegistry entries. It is also used for debugging and for executing external scripts.
Finally, there is the my object. This special global object is used to reference the current context of the JavaScript process. For instance, the my.target context refers to the current target volume. The my.result context refers to the results of the last InstallationCheck or VolumeCheck script.
A complete listing of all the library methods is beyond the scope of this article. Instead, six of the most commonly used methods are listed as follows.
· my.target.availableKilobytes — Checks the amount of free space available on the target volume. Returns the value in kilobytes as an integer.
· my.target.systemVersion — Checks the version of MacOS X installed on the target volume. Returns the version number as a string.
· system.file.fileExistsAtPath(aPath) — Checks to see if the specified file is present. The input argument, aPath, is entered as a UNIX path string. Returns a TRUE if the file exists, FALSE if otherwise.
· system.gestalt(aSignature) — Checks on a specific system setting using the Gestalt Manager. The input argument, aSignature, is a four-character identifier. Returns the result as a JavaScript object.
· system.log(anArg) — Outputs a string, prefixed with the characters JS:, to the PackageMaker's debug console. The input argument, aArg, can either be a string or a JavaScript object.
· system.sysctl(aNode) — Returns the setting of a specific kernel state. The input argument aNode is the string describing the kernel state node.
For a more complete list of library methods, read the Installer Release Notes, which can be found at http://developer.apple.com/releasenotes/DeveloperTools/Installer.html.
The Global Scripts panel
The Global Script panel (Figure 10) is where you define your distribution scripts. Also, it is where you select which scripts to use as an InstallationCheck or a VolumeCheck script.
Figure 10. The Global Scripts panel.
The project template for the distribution package contains three basic scripts. Each script performs a default action, which are described as follows.
· installationCheckRAM() — Checks if the target platform has at least 128 MBytes of physical RAM installed.
· volumeCheckTiger() — Checks if the target system version is at10.4 or newer.
· choiceIsADowngrade() — Checks if the user is about to perform a downgrade installation, as opposed to an upgrade.
Both scripts return a TRUE if their conditions are valid, FALSE if otherwise.
To edit a script in the Global Script panel, select its name from the list and click on the button labeled Edit. To add a new script, click on the plus-sign button located on the lower-left corner of the list, and enter the script's name in the space provided. To remove a script, select its name from the list and click on the minus-sign button.
To select the script for the InstallationCheck, type its name into the appropriate field. Use the same procedure for selecting the script for the VolumeCheck. Alternatively, you can select the name of the script from the drop-down list next to each field.
Scripting installation choices
Scripts can reconfigure the default installation choices based on the target system environment. They can also reconfigure other choices based on user input. They can even check for specific software products, and then enable choices, such as bridge software, that will interact with those products.
For example, a distribution package carries two versions of the same software product. One version is suited only for PowerPC-based systems, while the other is for Intel-based systems. A script can check the target processor type. It can then hide and show the appropriate installation choice based on the results of the check (Listing 4).
Assigning a script to an installation choice is done through the Scripts listbox (encircled in red in Figure 6) of the Choice Settings panel. Note that the first three entries in the listbox have a start_ prefix. These correspond to the three default states of that installation choice. Usually, you set the default states using the Contents panel (Figure 4). You can also set those states by double-clicking on each entry, and entering either a TRUE or FALSE value.
The next three entries also correspond to each state of the installation choice. These ones are scriptable. To assign a script for each entry, double-click on the adjoining field to bring up the script window. Then enter the name of the global script in the window provided.
Do not define a distribution script for the installation choice through this window. If you do so, the distribution package will generate a JavaScript error at runtime. Use the Global Script panel to define the script.
Defining a distribution script
The script window (Figure 11) is where you create or edit your distribution script. It is displayed whenever you select a script from the Global Functions listbox (Figure 10) for editing.
Figure 11. Editing a distribution script.
You can enter the JavaScript code directly onto the script window, or use the built-in Requirements Editor. You can also use an external text editor to write or edit your distribution script. Regardless of which approach you use, be aware that you can only edit one script at a time.
To use an external text editor, select its name from the drop-down list, and then click on the Edit button. PackageMaker first saves a copy of the script into a temp directory. It then opens the copy using the selected editor. Any changes made to the script are automatically reflected back into the script window.
Using an external text editor allows you to take advantage of the editor's various features. For instance, BBEdit will allow you to perform search and replace operations that are not possible in the script window. It will also assign colors to each JavaScript keyword thus making the script easier to read.
At the time of this writing, PackageMaker will use one of the three external editors: BBEdit, BBEdit Lite, and TextWrangler. Future versions of the tool may allow users to add additional editors such as jEdit and Smultron. It may also allow the script to be open by default with an external editor, instead of the script window.
To use the built-in Requirements Editor, select its name from the drop-down list, and then click on the Edit button. PackageMaker then displays the editor's dialog panel as shown in Figure 12.
Figure 12. Using the Requirements Editor.
In the above example, the Requirements Editor is used to define the script named MyScript. This script executes a single subroutine named testScript. The subroutine then performs three system checks. It returns a TRUE if the target platform meets the following conditions:
· it has a processor clock speed of exactly 600 MHz,
· its processor does not support AltiVec technology, and
· its BSD subsystem is compatible with at least version 2.0 of the POSIX standard.
But, if the target platform fails to meet any of these conditions, the script will display the appropriate warning message.
When the Requirements Editor is used to define a script, it adds a property list to that script. This property list defines the editor settings that were used for that script. Avoid making changes to this list if you still want to edit the script using the Requirements Editor.
Examples of distribution scripts.
One example of a distribution script is shown in Listing 1. This script is often used to perform an InstallationCheck.
The script checks the target platform to see if the Xcode development environment is installed. If the check fails, the script then displays an error dialog to the user. Once the user replies to the dialog, the script then tells Installer to terminate the entire installation process.
Listing 1. Checking for the XCode development environment.
function checkForXCode() { var chk_flag = false; try { chk_flag = (system.files.fileExistsAtPath ¬ ('/Developer/Applications/Xcode.app') == true); } catch (e) {} if (!chk_flag) { my.result.type = 'Fatal'; my.result.title = 'XCode Not Installed'; my.result.message = ¬ 'The installer requires the XCode environment to be installed.'; } return chk_flag; }
Listing 2 shows a second example of a distribution script. This one is commonly used to perform a VolumeCheck.
The script checks to see if the target volume has a maximum capacity of less than or equal to 256 Mbytes. If the volume meets the size condition, the script returns a TRUE. If it returns a FALSE, a stop badge is displayed on the disk icon for that volume.
Listing 2. Filtering out volumes less than 256 MB.
function filterSmallVolumes() { var test_flag = false; var test_limit = 256 * 1024 * 1024; var test_size = 0; try { test_size = my.target.availableKilobytes; test_flag = (test_size <= test_limit); system.log('filterSmallVolumes:size:' + test_size); } catch (e) { system.log(e.name); system.log(e.message); } if (!test_flag) { my.result.type = 'Fatal'; my.result.title = ''; my.result.message = ''; } return (test_flag); }
A third example of a distribution script is shown in Listing 3. This example shows how a script can perform a device check.
The script uses the IORegistry to see if a specific USB device is installed on the target platform. In this example, the device happens to be a Macally graphics tablet. If the tablet is installed, the script returns a TRUE. Otherwise, it returns a FALSE, and displays a warning dialog to the user. Unlike Listing 1, the script does not terminate the installation process after the user replies to the dialog.
Listing 3. Checking for a USB graphics tablet.
function checkTablet() { var chk_flag = false; // perform the hardware check try { var chk_test = system.ioregistry.fromPath('IOService:/MacRISC2PE ¬ /pci@f2000000/AppleMacRiscPCI/usb@1B/AppleUSBOHCI ¬ /Macally Mini Tablet@1b100000'); chk_flag = (chk_test != null); } catch (e) { // handle the resulting exception my.result.type = 'Fatal'; my.result.title = e.name; my.result.message = e.message; } // was the check successful? if (!chk_flag) { my.result.type = 'Warning'; my.result.title = 'No graphics tablet'; my.result.message = 'The software being installed requires a ¬ graphics tablet for the best user experience.'; } return (chk_flag); }
Finally, Listing 4 is an example of a script that will interact with an installation choice. This script is used by entering its name into the visible field of the Scripts listbox (Figure 13) of the Choice Settings panel.
The script takes a single input argument named univBinary. It first checks to see if the current system version is less than 10.4. Next, it checks univBinary to see if it is set to TRUE. If this is case, the payload is a Universal Binary, and the variable chk_ppc is then inverted. The script then returns the final state of chk_ppc.
Listing 4. Selecting an installation choice.
function sysCheck(univBinary) { var chk_ppc = false; try { chk_ppc = (system.version.ProductVersion < '10.4'); if (univBinary) chk_ppc = !chk_ppc; } catch (e) {} return chk_ppc; }
Figure 13. Selecting a choice.
Concluding Remarks
The PackageMaker tool is used to prepare software products for installation. This tool is available free from Apple as part of its Xcode development environment. It builds an installer package that is localized for different languages. It also uses scripts to enhance the installation process.
The latest version of the tool now supports the distribution package format. This new format is the preferred approach for handling multiple payloads on MacOS X 10.4 and newer. It is easier to localize, and allows installation choices to be customized. It also adopts JavaScript as its scripting language. This enables its scripts to interact with the installer interface, and makes the scripts easier to maintain.
The distribution package is the preferred format for installing multiple software payloads. Though it has some issues, it is still easier to localize compared to a metapackage. It allows multiple installation choices to be customized, and it simplifies the assignment of payloads to each choice. The package format also uses JavaScript as its scripting language. This allows the scripts to interact with the user interface, and perform tasks that are difficult to do using either shell or Perl.
The examples shown in this article are part of the DemoDistribution project. This project, together with its payloads, can be downloaded from the MacTech website at the following URL: ftp://ftp.mactech.com/src/mactech/volume22_2006/22.11.sit.
Bibliography and References
Apple Computers. Installer Release Notes. Copyright 2005. Apple Computers, Inc. Online: http://developer.apple.com/releasenotes/
DeveloperTools/Installer.html.
Apple Computers. "Language and Locale Designations". Internationalization Programming Topics. Copyright 2003, 2005. Apple Computers, Inc.
Apple Computers. Software Distribution. 2005 Aug 11. Copyright 2006. Apple Computers, Inc.
Apple Computers. Software Delivery Guide. 2006 Jul 24. Copyright 2006. Apple Computers, Inc.
Sudre, Stéphane. PackageMaker How-To. Published: 2004 June 22. Online: http://s.sudre.free.fr/Stuff/
PackageMaker_Howto.html
JC is a freelance engineering consultant and writer currently residing in North Vancouver, British Columbia. He divides his time between developing custom OS X applications, writing technical articles, and teaching origami at his local district's public library. He can be reached at anarakisware@cashette.com.
- SPREAD THE WORD:
- Slashdot
- Digg
- Del.icio.us
- Newsvine