[Pythonmac-SIG] The where's-my-app problem
bob at redivi.com
Thu Nov 18 17:47:52 CET 2004
On Nov 18, 2004, at 4:17 PM, Charles Hartman wrote:
> Over on the wxPython-users list, there has been endless discussion of
> this problem (see
> I want to know the directory from which my app was launched, so as to
> locate a couple of data files that need to be editable and therefore
> separate from the bundle, painlessly and without dialog boxes in the
> normal case (the files haven't been moved, the app is started by
> double-clicking or whatever is most obvious to a casual user). It
> turns out to be strangely difficult to do this in a way that works for
> (a) apps launched from the Terminal and (b) apps frozen by py2app on
> Mac and (c) py2exe apps on Windows.
> In several of the approaches proposed, the py2app "frozen" app seems
> to be the odd one out. IF I've got this straight, these are the two
> that work on Win2K running from Python and in a py2exe app, and on
> OS/X running from Python, but not from a bundlebuilder (or, I think,
> py2app) app:
<the given methods are either totally unreliable or incomplete at best>
I'm just going to spew a short article's worth of information about
this topic. A subset of this will certainly answer your question.
Feel free to reproduce this in any way you see fit (i.e. a FAQ or Wiki
would be a good place for this information), but be warned that this
code is written from Mail.app and is untested, so it may have typos.
== Mac OS X and paths ==
From a Mac OS X application's perspective, the "directory from which
your app was launched" is completely ambiguous. Is is so ambiguous, in
fact, that when a Mac OS X application starts up by way of
LaunchServices (i.e. Finder, /usr/bin/open, etc.) it will have a
current directory of /. Yes, that's right. It starts in the root of
your filesystem, a completely useless place. The reason for this is
that on Mac OS X, you should always always always always ALWAYS ALWAYS
find files in one of the following ways:
- Inside your application bundle (via CFBundle or NSBundle functions)
- A predefined system path (via FSFindFolder or
NSSearchPathForDirectoriesInDomains, NOT by hardcoding something) or a
subdirectory of one (i.e. ~/Library/Application Support/MyApplication)
- Chosen by the user with some kind of system dialog
- A "remembered" (using an alias) location that was chosen by the user
with some kind of dialog at one point another (system dialogs do this,
so that they remember where the user last chose files from)
That's it. You should ALMOST NEVER use any other way to find stuff.
ALL data files of a correctly constructed Mac OS X application should
be in the Resources folder of the bundle. If you want to have user
editable versions of these files, then on startup you should make
copies of the in-Resources files somewhere like ~/Library/Application
Support/MyApplication if they do not exist and use those. You may also
add functionality to your application to reveal this path to the user.
For example, the Scripts menu in Mail.app has an "Open Scripts Folder"
== Canonical way of detecting py2app, py2exe ==
frozen = getattr(sys, 'frozen', None)
if not frozen:
# COULD be certain cx_Freeze options or bundlebuilder, nothing
to worry about though
elif frozen in ('dll', 'console_exe', 'windows_exe'):
elif frozen in ('macosx_app',):
elif frozen is True:
# it doesn't ALWAYS set this
return '<unknown packager: %r>' % (frozen,)
It's possible to build cx_Freeze-frozen applications that are not
picked up by this (i.e. ConsoleKeepPath). Bundlebuilder is never
really detectable, but you should NEVER use bundlebuilder. py2app is
capable of performing all of the operations that bundlebuilder can do
(and then some), with the added bonus that its implementation is more
correct. I hope to deprecate bundlebuilder in Python 2.5.
== Canonical way of finding your main script ==
Finding your main script should be done in the same way that your main
script determines if it is main or not! For applications frozen with
py2exe, there IS effectively no on-disk main script (the bytecode is
shoved into the executable). Finding your main script is not a useful
thing to do from a packaged application anyway, so only use this when
find_packager() returns None.
== Default invariants for py2app ==
- sys.frozen == 'macosx_app'
- os.getcwd() is the Resources directory of your application's bundle
when the application starts. This is a convenience to make most
scripts work "out of the box" (but probably after using the
- os.environ['RESOURCEPATH'] is the absolute path to the Resources
directory of your application's bundle when it started
- The user's "site-packages" directory WILL NOT be considered by
default, so you
- Any Python code, dynamic libraries, and frameworks that are
determined to be used, aren't explicitly excluded, and aren't located
in a system path (/usr/lib or /System) WILL end up somewhere accessible
from the runtime in your application bundle. You SHOULD ONLY depend on
the fact that the code can be located at runtime, NOT where it is in
the application bundle.
- NO non-code will be included from packages UNLESS the package is
explicitly included using the "packages" option. This can also happen
implicitly by way of a recipe.
- Such explicitly included packages are included as-is, but may be
stripped of 'CVS' and '.svn' subdirectories. You CAN depend on data
files that are in such packages, but you should ONLY reference them
relative to the dirname of the package's __file__.
== Don't use bundlebuilder ==
bundlebuilder does a number of things incorrectly and incompletely.
Don't use it anymore, ever. A typical py2app setup script is quite a
bit simpler than the bundlebuilder version, anyway.
== Using Python packages as data file containers ==
It's common to include necessary data files inside of packages, for
To locate such data files, you should always do something equivalent to
This method will not work in the face of zip imports, so such packages
must be explicitly included as-is with packaging solutions such as
py2app or py2exe.
== If you *actually* want to find something relative to the
Note that this code only belongs in a poorly designed OS X application,
but I'm going to tell you anyway since this seems to be what you asked
for after a painful reading of the referenced wxPython-users thread:
# use find_packager() above
packager = find_packager()
if packager == 'py2exe':
# note that another approach is
elif packager == 'py2app':
# from path/Foo.app/Contents/Resources -> path, even if
something in your application chdir'ed in the meantime
Also note that this isn't bulletproof.
For example, in one of my applications, I use NSIS on top of py2exe to
provide a "single file executable" (for which the above code would
return a nonsense temporary directory). In the Mac OS X version of
this application all of my resources are located in the Resources
folder of the application bundle, and I locate them using OS X specific
APIs (NSBundle, etc.) since the application is written with PyObjC. In
the Win32 version, NSIS sets the current directory to the location of
the "single file executable", and the windows version searches that
directory for the Mac OS X application, and uses the resources folder
of its application bundle for data files. I emulate many of the
localization and file-finding capabilities of Cocoa in the Windows
code. This is of course a strange way to do things on Windows, since
the canonical way is to use an installer, but in this case the
application *is* an installer (to put data on a peripheral device, not
more software for the machine) and is rarely executed, so it makes
perfect sense to do it in this manner.
Hopefully this covers everything you need. Feel free to ask additional
questions, but I'm pretty busy with the pypy sprint and I'm in a weird
timezone (gmt+2) for the next week, so it may take a while to respond.
More information about the Pythonmac-SIG