<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Zack McCauley</title>
    <description>The latest articles on DEV Community by Zack McCauley (@wardsparadox).</description>
    <link>https://dev.to/wardsparadox</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1480918%2F19d2aa75-5cd7-46e1-a593-3282c55c2751.jpg</url>
      <title>DEV Community: Zack McCauley</title>
      <link>https://dev.to/wardsparadox</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wardsparadox"/>
    <language>en</language>
    <item>
      <title>Marking macOS component packages available based on hardware platform type</title>
      <dc:creator>Zack McCauley</dc:creator>
      <pubDate>Fri, 10 May 2024 21:35:32 +0000</pubDate>
      <link>https://dev.to/wardsparadox/marking-macos-component-packages-available-based-on-hardware-platform-type-477m</link>
      <guid>https://dev.to/wardsparadox/marking-macos-component-packages-available-based-on-hardware-platform-type-477m</guid>
      <description>&lt;p&gt;&lt;em&gt;Cover image by SimonWaldherr, CC BY-SA 4.0 &lt;a href="https://creativecommons.org/licenses/by-sa/4.0" rel="noopener noreferrer"&gt;https://creativecommons.org/licenses/by-sa/4.0&lt;/a&gt;, via Wikimedia Commons&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On macOS, you can create two different installer package types: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flat/Component Packages&lt;/li&gt;
&lt;li&gt;Distribution Packages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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 &lt;a href="https://scriptingosx.com/" rel="noopener noreferrer"&gt;ScriptingOSX&lt;/a&gt; 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 &lt;a href="https://python.org" rel="noopener noreferrer"&gt;Python.org&lt;/a&gt; installer. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwmfqdp14crff4axfeb3k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwmfqdp14crff4axfeb3k.png" alt="python.org customizable package example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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. &lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;always&lt;/code&gt; to demonstrate the choices. I recommend for enterprise packages, marking this to &lt;code&gt;never&lt;/code&gt; to prevent this UI from showing and always respecting your default choices)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8" standalone="no"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;installer-gui-script&lt;/span&gt; &lt;span class="na"&gt;minSpecVersion=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Test Package&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;options&lt;/span&gt; &lt;span class="na"&gt;customize=&lt;/span&gt;&lt;span class="s"&gt;"always"&lt;/span&gt; &lt;span class="na"&gt;require-scripts=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;rootVolumeOnly=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;hostArchitectures=&lt;/span&gt;&lt;span class="s"&gt;"arm64,x86_64"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;choices-outline&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;choice=&lt;/span&gt;&lt;span class="s"&gt;"arm"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;choice=&lt;/span&gt;&lt;span class="s"&gt;"x86"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/choices-outline&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;choice&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"arm"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"ARM"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;pkg-ref&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"com.acme.pkg.test_arm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;test_arm-1.0.pkg&lt;span class="nt"&gt;&amp;lt;/pkg-ref&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/choice&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;choice&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"x86"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"x86"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;pkg-ref&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"com.acme.pkg.test_x86"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;test_x86-1.0.pkg&lt;span class="nt"&gt;&amp;lt;/pkg-ref&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/choice&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/installer-gui-script&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In the Installer UI, this presents as such: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0gpjf989s9vsa0dnv7q4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0gpjf989s9vsa0dnv7q4.png" alt="package with both options enabled and selected"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's not what we want. Let's explore our options. Apple documents all the options available &lt;a href="https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Distribution_XML_Ref.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Reading more, we want to use the &lt;code&gt;enabled&lt;/code&gt; attribute for the choice:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Optional.&lt;/em&gt; Specifies whether the user can select or deselect this option in the Installer customization pane. If &lt;code&gt;false&lt;/code&gt;, or a JavaScript expression that evaluates to &lt;code&gt;false&lt;/code&gt;, 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 &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So we can drive it via JavaScript... wait...&lt;strong&gt;we can use Javascript in an Installer package??!?&lt;/strong&gt; Yup. Apple has a variant of JavaScript they call &lt;a href="https://developer.apple.com/documentation/installer_js" rel="noopener noreferrer"&gt;Installer JS&lt;/a&gt;. 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. &lt;/p&gt;

&lt;p&gt;Following Apple's documentation we can see there's a class for &lt;code&gt;System&lt;/code&gt; with a function called &lt;code&gt;sysctl&lt;/code&gt;. It has this wonderful note:&lt;/p&gt;

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

&lt;p&gt;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 &lt;code&gt;sysctl&lt;/code&gt; function matches exactly how &lt;code&gt;sysctl&lt;/code&gt; works in Terminal or scripting. &lt;/p&gt;

&lt;p&gt;When we run &lt;code&gt;sysctl -n hw.machine&lt;/code&gt; we can get our expected architecture &lt;code&gt;arm64&lt;/code&gt;...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. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

❯ sysctl &lt;span class="nt"&gt;-n&lt;/span&gt; hw.machine
arm64
❯ &lt;span class="nb"&gt;arch&lt;/span&gt; &lt;span class="nt"&gt;-x86_64&lt;/span&gt; sysctl &lt;span class="nt"&gt;-n&lt;/span&gt; hw.machine
x86_64
❯ sysctl &lt;span class="nt"&gt;-n&lt;/span&gt; machdep.cpu.brand_string
Apple M1 Pro
❯ &lt;span class="nb"&gt;arch&lt;/span&gt; &lt;span class="nt"&gt;-x86_64&lt;/span&gt; sysctl &lt;span class="nt"&gt;-n&lt;/span&gt; machdep.cpu.brand_string
Apple M1 Pro


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;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 &lt;code&gt;includes&lt;/code&gt; function to check for specific substrings. Let's put it all together. We need a &lt;code&gt;script&lt;/code&gt; 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 &lt;code&gt;enabled&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt;! When calling the script function, we can just reference the function name in the attribute value:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

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

]]&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;choices-outline&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;choice=&lt;/span&gt;&lt;span class="s"&gt;"arm"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;choice=&lt;/span&gt;&lt;span class="s"&gt;"x86"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/choices-outline&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;choice&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"arm"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"ARM"&lt;/span&gt; &lt;span class="na"&gt;enabled=&lt;/span&gt;&lt;span class="s"&gt;"is_arm()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;pkg-ref&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"com.acme.pkg.test_arm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;test_arm-1.0.pkg&lt;span class="nt"&gt;&amp;lt;/pkg-ref&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/choice&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;choice&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"x86"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"x86"&lt;/span&gt; &lt;span class="na"&gt;enabled=&lt;/span&gt;&lt;span class="s"&gt;"! is_arm()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;pkg-ref&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"com.acme.pkg.test_x86"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;test_x86-1.0.pkg&lt;span class="nt"&gt;&amp;lt;/pkg-ref&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/choice&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/installer-gui-script&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6foimfys8q71jfz9c5q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6foimfys8q71jfz9c5q.png" alt="Packages showing proper enabled item but both selected"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

====================================================================
 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
====================================================================


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We can adjust this further to get our results. We can also solve this in multiple ways. You can duplicate the &lt;code&gt;enabled&lt;/code&gt; item for &lt;code&gt;selected&lt;/code&gt; which will unselect the item for the specific arch. You can also change it from &lt;code&gt;enabled&lt;/code&gt; to &lt;code&gt;selected&lt;/code&gt; and add the &lt;code&gt;start_enabled="false"&lt;/code&gt; attribute. You can modify the behavior entirely using the &lt;code&gt;choice&lt;/code&gt; attributes on the &lt;a href="https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Distribution_XML_Ref.html#//apple_ref/doc/uid/TP40005370-CH100-SW10" rel="noopener noreferrer"&gt;docs&lt;/a&gt;. For most enterprise installations, this won't even be visible. I recommend going the &lt;code&gt;enabled&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;selected&lt;/code&gt; route to ensure the package functions as expected with no customization. Like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fauoxw0x10t9zeudwd2fk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fauoxw0x10t9zeudwd2fk.png" alt="Packages with proper enabled item and proper selected item"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

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]: ================================================================================


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I hope this can help those in need of dual packages for the same non-universal capable item.&lt;/p&gt;

</description>
      <category>macos</category>
      <category>packaging</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
