DEV Community

Cover image for Marking macOS component packages available based on hardware platform type
Zack McCauley
Zack McCauley

Posted on

Marking macOS component packages available based on hardware platform type

Cover image by SimonWaldherr, CC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0, via Wikimedia Commons

On macOS, you can create two different installer package types:

  • Flat/Component Packages
  • Distribution Packages

Flat packages are the most common used packages, but distribution packages are more robust and can contain multiple flat packages. That's enough detail for this article but if you want to know more Armin Briegel of ScriptingOSX has a great book covering a lot of the details of these package types. I highly recommend picking up a copy for reference. One of the benefits of Distribution packages is that you can include components as a choice for folks to install specific pieces. An example is provided below, courtesy of the Python.org installer.

python.org customizable package example

Despite being three years after the Apple Silicon revolution, some components or software can not be made universal yet (or the vendor has specifically chosen not to provide universal binaries). A common workaround for this is the vendor provides a flat package, where the post-install script actually installs the proper package based on the hardware platform. This is fine and does work, but hides the logic behind an installation script. This also makes managing a distribution package much messier as the packages aren't declared in the Distribution.xml file.

Let's look at a basic Distribution.xml for my "Test Package" that includes an ARM and X86 component packages. This package defines two "choices" where the user can choose to install the X86 or ARM variant. For non-technical users, this could lead to confusion where they may install the wrong item. (For reference, the distribution will have the customize option set to always to demonstrate the choices. I recommend for enterprise packages, marking this to never to prevent this UI from showing and always respecting your default choices)

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<installer-gui-script minSpecVersion="2">
    <title>Test Package</title>
    <options customize="always" require-scripts="false" rootVolumeOnly="true" hostArchitectures="arm64,x86_64"/>
    <choices-outline>
        <line choice="arm"/>
        <line choice="x86"/>
    </choices-outline>
    <choice id="arm" title="ARM">
        <pkg-ref id="com.acme.pkg.test_arm">test_arm-1.0.pkg</pkg-ref>
    </choice>
    <choice id="x86" title="x86">
        <pkg-ref id="com.acme.pkg.test_x86">test_x86-1.0.pkg</pkg-ref>
    </choice>
</installer-gui-script>
Enter fullscreen mode Exit fullscreen mode

In the Installer UI, this presents as such:

package with both options enabled and selected

That's not what we want. Let's explore our options. Apple documents all the options available here. Reading more, we want to use the enabled attribute for the choice:

Optional. Specifies whether the user can select or deselect this option in the Installer customization pane. If false, or a JavaScript expression that evaluates to false, the choice is dimmed so the user cannot select or deselect it. Re-evaluated as the user interacts with the choice tree, so a choice can be enabled or disabled based on the state of other choices. Default value is true.

So we can drive it via JavaScript... wait...we can use Javascript in an Installer package??!? Yup. Apple has a variant of JavaScript they call Installer JS. This allows you to use a limited set of JavaScript properties to enhance your package choices. Let's explore some items of Installer JS to see what our options are.

Following Apple's documentation we can see there's a class for System with a function called sysctl. It has this wonderful note:

If you are using this function to screen for hardware features at install-time, consider using the “Pre-install Requirements Property List” setting of productbuild instead. Use man productbuild in the macOS Terminal application to see a list of supported requirement keys.

This would be great...but distribution packages can not contain other distribution packages where we could use the Pre-install markers to limit the packages to architectures. Dang that's out of the way...but we can still keep going. The sysctl function matches exactly how sysctl works in Terminal or scripting.

When we run sysctl -n hw.machine we can get our expected architecture arm64...but wait node can lead to unexpected results if ran under Rosetta! We can however rely on our CPU name to tell us definitively what arch we're running on.

❯ sysctl -n hw.machine
arm64
❯ arch -x86_64 sysctl -n hw.machine
x86_64
❯ sysctl -n machdep.cpu.brand_string
Apple M1 Pro
❯ arch -x86_64 sysctl -n machdep.cpu.brand_string
Apple M1 Pro
Enter fullscreen mode Exit fullscreen mode

Great! We've identified a way to track what hardware we're running and a potential way to reference it in the Distribution.xml file. JavaScript has a string includes function to check for specific substrings. Let's put it all together. We need a script element to contain our script so we can call it per choice. We also need to remember to invert one of the options so that enabled is false! When calling the script function, we can just reference the function name in the attribute value:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!--
https://developer.apple.com/library/mac/documentation/DeveloperTools/Reference/DistributionDefinitionRef/
https://developer.apple.com/library/mac/documentation/DeveloperTools/Reference/InstallerJavaScriptRef/
-->
<installer-gui-script minSpecVersion="2">
    <title>Test Package</title>
    <options customize="always" require-scripts="false" rootVolumeOnly="true" hostArchitectures="arm64,x86_64"/>
    <script>
<![CDATA[
function is_arm() {
  if(system.sysctl("machdep.cpu.brand_string").includes("Apple")) {
    return true;
  }
  return false;
}

]]>
    </script>
    <choices-outline>
        <line choice="arm"/>
        <line choice="x86"/>
    </choices-outline>
    <choice id="arm" title="ARM" enabled="is_arm()">
        <pkg-ref id="com.acme.pkg.test_arm">test_arm-1.0.pkg</pkg-ref>
    </choice>
    <choice id="x86" title="x86" enabled="! is_arm()">
        <pkg-ref id="com.acme.pkg.test_x86">test_x86-1.0.pkg</pkg-ref>
    </choice>
</installer-gui-script>
Enter fullscreen mode Exit fullscreen mode

Packages showing proper enabled item but both selected

Success....wait a second. The package still shows as selected but enabled??? This is unexpected. Maybe a UI problem? If we hit CMD+L we can open up the install log for this installer. Switching to Show All Logs mode, then hitting next, we can see it will install both. This is not what we want.

====================================================================
 MacLaptop Installer[98327]: User picked Custom Install
 MacLaptop Installer[98327]: Choices selected for installation:
 MacLaptop Installer[98327]:    Install: "Test Package"
 MacLaptop Installer[98327]:    Install: "ARM"
 MacLaptop Installer[98327]:        test-meta.pkg#test_arm-1.0.pkg : com.acme.pkg.test_arm : 1.0
 MacLaptop Installer[98327]:    Install: "x86"
 MacLaptop Installer[98327]:        test-meta.pkg#test_x86-1.0.pkg : com.acme.pkg.test_x86 : 1.0
====================================================================
Enter fullscreen mode Exit fullscreen mode

We can adjust this further to get our results. We can also solve this in multiple ways. You can duplicate the enabled item for selected which will unselect the item for the specific arch. You can also change it from enabled to selected and add the start_enabled="false" attribute. You can modify the behavior entirely using the choice attributes on the docs. For most enterprise installations, this won't even be visible. I recommend going the enabled and selected route to ensure the package functions as expected with no customization. Like so:

Packages with proper enabled item and proper selected item

MacLaptop Installer[1808]: ================================================================================
MacLaptop Installer[1808]: User picked Custom Install
MacLaptop Installer[1808]: Choices selected for installation:
MacLaptop Installer[1808]:  Install: "Test Package"
MacLaptop Installer[1808]:  Install: "ARM"
MacLaptop Installer[1808]:      test-meta.pkg#test_arm-1.0.pkg : com.acme.pkg.test_arm : 1.0
MacLaptop Installer[1808]: ================================================================================
Enter fullscreen mode Exit fullscreen mode

I hope this can help those in need of dual packages for the same non-universal capable item.

Top comments (0)