Skip to content

Instantly share code, notes, and snippets.

@TommyPKeane
Created March 9, 2023 16:43
Show Gist options
  • Save TommyPKeane/752f2ab6c91d474b1e73e200c497ba04 to your computer and use it in GitHub Desktop.
Save TommyPKeane/752f2ab6c91d474b1e73e200c497ba04 to your computer and use it in GitHub Desktop.
Python Example for creating an macOS (OS X) App Container for Binary Program (executable) without XCode
#!/usr/bin/env python
# --------------------------------------------------------------------------------------------------
# Tommy P. Keane
# https://www.tommypkeane.com
# --------------------------------------------------------------------------------------------------
'''
Use a function like this to easily create the Info.plist file necessary to create your own .app
container. You need an executable binary file to go along with this!
An executable binary file is something like the output from compiling your C++ code.
On OS X an \"App\" is actually just a folder that contains the binary executable, so
you can actually just move your executable into a new *.app folder and with the proper folder
structure and an accompany properties-list file (Info.plist), the operating system will see your
program as an application.
You can also create and store an icon:
http://stackoverflow.com/a/20703594
'''
# IMPORT MODULES
# --------------------------------------------------------------------------------------------------
import os;
import sys;
import shutil;
# --------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------
# FUNCTION FOR MOVING FILES AND CREATING AN OS X APPLICATION BUNDLE
# --------------------------------------------------------------------------------------------------
def MoveApplicationFiles( ApplicationBuildPathString,
ApplicationBaseNameString,
ResourceFilesDirectoryPathNameString,
CompanyDomainNameString,
SConsEnviromentObj ):
## SETUP THE PATHS AND CONVENIENT STRINGS
# ----------------------------------------------------------------------------------------------
AppPathNameString = os.path.join( ApplicationBuildPathString, ApplicationBaseNameString + '.app' );
AppContentsPathNameString = os.path.join( ApplicationBuildPathString, ApplicationBaseNameString + '.app', 'Contents' );
AppContentsMacOSPathNameString = os.path.join( ApplicationBuildPathString, ApplicationBaseNameString + '.app', 'Contents', 'MacOS' );
AppContentsResourcesPathNameString = os.path.join( ApplicationBuildPathString, ApplicationBaseNameString + '.app', 'Contents', 'Resources' );
BinaryAppPathNameString = os.path.join( ApplicationBuildPathString, ApplicationBaseNameString );
BinaryAppExecutablePathNameString = os.path.join( ApplicationBuildPathString, ApplicationBaseNameString, 'Application' );
BinaryAppResourcesPathNameString = os.path.join( ApplicationBuildPathString, ApplicationBaseNameString, 'Resources' );
# ----------------------------------------------------------------------------------------------
## DELETE THE EXISTING VERSION (REMOVE DIRECTORY TREE) AND CREATE NEW FOLDERS
# ----------------------------------------------------------------------------------------------
try:
shutil.rmtree( AppPathNameString );
except OSError:
pass;
#yrt
os.mkdir( AppPathNameString );
os.mkdir( AppContentsPathNameString );
os.mkdir( AppContentsMacOSPathNameString );
os.mkdir( AppContentsResourcesPathNameString );
try:
shutil.rmtree( BinaryAppPathNameString );
except OSError:
pass;
#yrt
os.mkdir( BinaryAppPathNameString );
os.mkdir( BinaryAppExecutablePathNameString );
os.mkdir( BinaryAppResourcesPathNameString );
# ----------------------------------------------------------------------------------------------
## CREATE Info.plist FILE (4-SPACE TABS)
# ----------------------------------------------------------------------------------------------
InfoFileObject = open( os.path.join( AppContentsPathNameString, 'Info.plist' ), 'w' );
# SUPER STUPID BACKWARDS DOMAIN NAME (com.mycompany)
UniqueAppleApplicationNameString = ( '.'.join( ( list( reversed( CompanyDomainNameString.split( '.' ) ) ) ) ) +
'.' +
ApplicationBaseNameString );
InfoFileObject.write( '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n' );
InfoFileObject.write( '<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n' );
InfoFileObject.write( '<plist version=\"1.0\">\n' );
InfoFileObject.write( ' <dict>\n' );
InfoFileObject.write( ' <key>CFBundleGetInfoString</key>\n' );
InfoFileObject.write( ' <string>' + ApplicationBaseNameString + '</string>\n' );
InfoFileObject.write( ' <key>CFBundleExecutable</key>\n' );
InfoFileObject.write( ' <string>' + ApplicationBaseNameString.lower() + '</string>\n' );
InfoFileObject.write( ' <key>CFBundleIconFile</key>\n' );
InfoFileObject.write( ' <string>' + ApplicationBaseNameString + '</string>\n' );
InfoFileObject.write( ' <key>CFBundleIdentifier</key>\n' );
InfoFileObject.write( ' <string>' + UniqueAppleApplicationNameString + '</string>\n' );
InfoFileObject.write( ' <key>CFBundleName</key>\n' );
InfoFileObject.write( ' <string>' + ApplicationBaseNameString + '</string>\n' );
InfoFileObject.write( ' <key>CFBundleShortVersionString</key>\n' );
InfoFileObject.write( ' <string>1.0</string>\n' );
InfoFileObject.write( ' <key>CFBundleInfoDictionaryVersion</key>\n' );
InfoFileObject.write( ' <string>6.0</string>\n' );
InfoFileObject.write( ' <key>CFBundlePackageType</key>\n' );
InfoFileObject.write( ' <string>APPL</string>\n' );
InfoFileObject.write( ' <key>IFMajorVersion</key>\n' );
InfoFileObject.write( ' <integer>1</integer>\n' );
InfoFileObject.write( ' <key>IFMinorVersion</key>\n' );
InfoFileObject.write( ' <integer>0</integer>\n' );
InfoFileObject.write( ' </dict>\n' );
InfoFileObject.write( '</plist>\n' );
InfoFileObject.close();
# ----------------------------------------------------------------------------------------------
## THAT'S IT?
# ----------------------------------------------------------------------------------------------
# Technically, now you need to put your executable into the App/Contents/MacOS internal folder.
#
# You will also want to move any other associated files (data, scripts, images, etc.) into the
# internal App/Contents/Resources folder and make sure that your executable has its paths set
# to that folder -- which is sometimes easier said than done.
#
# But, as long as there is an executable (matching the name you provided) in the MacOS folder,
# then you should be able to double-click the App container and your program will run, all
# without ever having to use and setup an XCode project.
#
# NOTE !!!:
# This approach will open the app as a self-contained program, it will not open the
# terminal app as well, so anything you are printing to the commandline will be completely
# inaccessible. You will notice we created "Binary_____" paths as well, which we would use to
# store the executable in a duplicate location with a similar folder structure. This way we
# can perform debugging by directly running the executable (not the App), which does open a
# terminal instance, where we can see all outputs sent to "std::err" and "std::out" (in C/C++).
# ----------------------------------------------------------------------------------------------
# RETURN NOTHING
return ( None );
#fed
# --------------------------------------------------------------------------------------------------
@TommyPKeane
Copy link
Author

TommyPKeane commented Mar 9, 2023

Early-to-mid 2010s tommy went wild with the spaces and the closing scopes. Good stuff. Classic. Loves to see it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment