How to tell if app is running as macOS app Universal?

Within a macOS app, I can tell if it is #TargetDesktop or #TargetWeb. Plus I can tell if it is running as ARM with SystemInformationMBS.isARM or Intel on ARM with SystemInformationMBS.IsTranslated.

But how can I tell if the application itself is:

  1. an Intel-only app running on Intel, or
  2. a Universal app, without having to use an external application (eg How to know if .app is Universal, Intel or ARM without using Lipo)?

you could read the first bytes of an executable.

If they have the multi architecture header, the first bytes should make a difference to an executable with just one architecture.

Or just check file size.

you have #Targetx86 and #TargetARM you can use !

So @Jean-Yves_Pochez, are you saying that if #Targetx86 and #TargetARM are both True then the app is Universal?

I would have thought only one would be true in a Universal app, depending on the CPU you are running on at the time.

If you want to store an information in the App how you have built it, then you could use a PreBuild Script along this way:

Dim sBuildTarget As String = Str(CurrentBuildTarget)
if (ConstantValue("modAppConstants.constBuildTarget") <> sBuildTarget) then
  ConstantValue("modAppConstants.constBuildTarget") = sBuildTarget
end if

This will set the value of the Constant constBuildTarget in modAppConstants.

So in your code you can e.g.:

// App Build is...
select case modAppConstants.constBuildTarget
case 9
  return "macOS Universal"
case 16
  return "macOS Intel"
case 24
  return "macOS ARM"
end select

// Running as...
#if TargetX86 then
  return "X86 (Intel)"
#elseif TargetARM then
  return "ARM"
#endif

1 Like

#Targetx86 and #TargetARM are never both True.

Setting my test macOS app to Universal and running it on my ARM MBP, it still thinks its an ARM-only app:

Building and running as Intel and ARM work as expected.

Just to be clear, there is no such thing as “running as universal”. A universal binary contains both ARM and Intel portions. Only Intel works on Intel machines. ARM works natively on Apple Silicon machines and Intel runs under Rosetta 2.

2 Likes

I’m writing a generic updater app for the MBS conference. It will allow you to keep macOS users as Intel-only, ARM-only or Universal in regards the new app version they download. Of course Universal is easier, but eventually we may all go ARM-only. I just want to give users the flexibility now.

Similarly, you can keep Window and Linux 32-bit users on their own specific versions.

oups you’re right they are not true at the same time
you can use the lipo shell command to detect the architecture of an app

I think the Build XojoScript might be the only way to go, unless someone has a different solution.

I built my test app as Intel-only, ARM-only and Universal, and they were identical file for file, except the Contents>MacOS>AppName executable file size.

The more important thing is to avoid installing a future ARM only app on an Intel Mac. That would break it. And avoid installing a newer version on an older macOS version where it doesn’t run there.

This is precisely why I want to be able to allow the user to choose a CPU lane and keep to their lane, and not upgrade Intel Macs/PCs to ARM versions, or 32-bit versions to 64-bit, or keep Universal apps around for too many years.

Doesn’t answer the question what platform your app runs on, but uses code made for that specific one.

For macOS the executable starts with

CF FA ED FE 0C 00 00 01 for ARM

CF FA ED FE 07 00 00 01 for Intel

CA FE BA BE 00 00 00 02 for Universal

as far as I see. The value are little endian and match the enumeration in the Headers:

enum {

NSBundleExecutableArchitectureI386      = 0x00000007,

NSBundleExecutableArchitecturePPC       = 0x00000012,

NSBundleExecutableArchitectureX86_64    = 0x01000007,

NSBundleExecutableArchitecturePPC64     = 0x01000012,

NSBundleExecutableArchitectureARM64  = 0x0100000c

};

1 Like

For future reference, here is a Method that works:

Protected Function isMacOSUniversalBuild() As Boolean
  'forum.xojo.com/t/how-to-tell-if-app-is-running-as-macos-app-universal/87387/14
  #If TargetMacOS Then
    Var f As FolderItem = App.ExecutableFile
    
    If f = Nil Or Not f.Exists Then
      Return False
    End If
    
    'For macOS the executable starts With
    'CF FA ED FE 0C 00 00 01 for ARM
    'CF FA ED FE 07 00 00 01 for Intel
    'CA FE BA BE 00 00 00 02 for Universal
    
    Var AppString As String = CommonFolders.getFileToString(f, True).Left(8)
    If AppString.MiddleBytes(0, 1).AscByte.ToHex = "CA" And _
      AppString.MiddleBytes(1, 1).AscByte.ToHex = "FE" And _
      AppString.MiddleBytes(2, 1).AscByte.ToHex = "BA" And _
      AppString.MiddleBytes(3, 1).AscByte.ToHex = "BE" And _
      AppString.MiddleBytes(4, 1).AscByte.ToHex = "0" And _
      AppString.MiddleBytes(5, 1).AscByte.ToHex = "0" And _
      AppString.MiddleBytes(6, 1).AscByte.ToHex = "0" And _
      AppString.MiddleBytes(7, 1).AscByte.ToHex = "2" Then
      Return True 'Universal
      
    ElseIf AppString.MiddleBytes(0, 1).AscByte.ToHex = "CF" And _
      AppString.MiddleBytes(1, 1).AscByte.ToHex = "FA" And _
      AppString.MiddleBytes(2, 1).AscByte.ToHex = "ED" And _
      AppString.MiddleBytes(3, 1).AscByte.ToHex = "FE" And _
      AppString.MiddleBytes(4, 1).AscByte.ToHex = "C" And _
      AppString.MiddleBytes(5, 1).AscByte.ToHex = "0" And _
      AppString.MiddleBytes(6, 1).AscByte.ToHex = "0" And _
      AppString.MiddleBytes(7, 1).AscByte.ToHex = "1" Then
      Return False 'ARM only
      
    ElseIf AppString.MiddleBytes(0, 1).AscByte.ToHex = "CF" And _
      AppString.MiddleBytes(1, 1).AscByte.ToHex = "FA" And _
      AppString.MiddleBytes(2, 1).AscByte.ToHex = "ED" And _
      AppString.MiddleBytes(3, 1).AscByte.ToHex = "FE" And _
      AppString.MiddleBytes(4, 1).AscByte.ToHex = "7" And _
      AppString.MiddleBytes(5, 1).AscByte.ToHex = "0" And _
      AppString.MiddleBytes(6, 1).AscByte.ToHex = "0" And _
      AppString.MiddleBytes(7, 1).AscByte.ToHex = "1" Then
      Return False 'Intel only
      
      'Else
      'MessageBox "I don't know what app CPU I am: " + AppString.MiddleBytes(0, 1).AscByte.ToHex + "," +AppString.MiddleBytes(1, 1).AscByte.ToHex + "," +AppString.MiddleBytes(2, 1).AscByte.ToHex + "," +AppString.MiddleBytes(3, 1).AscByte.ToHex + "," +AppString.MiddleBytes(4, 1).AscByte.ToHex + "," +AppString.MiddleBytes(5, 1).AscByte.ToHex + "," +AppString.MiddleBytes(6, 1).AscByte.ToHex + "," +AppString.MiddleBytes(7, 1).AscByte.ToHex
    End If
  #EndIf
  
  Return False
  
End Function
3 Likes

Since the tag is 8 bytes, we can improve the code. Rather than reading the entire App executable into a string (which may be 100s of MB) we just read the first 8 bytes:

Public Function MacOSUniversialBuildType() As String
  var f as FolderItem = app.ExecutableFile
  var bs as BinaryStream = BinaryStream.Open(f)
  var tag as integer = bs.ReadUInt64
  var tagHex as string = tag.ToHex
  select case tagHex
  case "CAFEBABE00000002"
    return "Universal"
  case "CFFAEDFE07000001"
    return "Intel"
  case "CFFAEDFE0C000001"
    return "ARM"
  else
    return "Unknown"
  end select
  
  
End Function
6 Likes