[Pythonmac-SIG] The where's-my-app problem (original) (raw)

Bob Ippolito 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 http://lists.wxwidgets.org/cgi-bin/ezmlm-cgi?11:sss:33832: fkbngdnmdfjfdjpcdndi#b):

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:

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:

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"
menu.

== Canonical way of detecting py2app, py2exe ==

def find_packager(): import sys frozen = getattr(sys, 'frozen', None) if not frozen: # COULD be certain cx_Freeze options or bundlebuilder, nothing
to worry about though return None elif frozen in ('dll', 'console_exe', 'windows_exe'): return 'py2exe' elif frozen in ('macosx_app',): return 'py2app' elif frozen is True: # it doesn't ALWAYS set this return 'cx_Freeze' else: 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 ==

import os def mainScriptDir(): import main return os.path.dirname(os.path.abspath(main.file))

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

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

mypackage/ init.py mydatafile

To locate such data files, you should always do something equivalent to
the following:

import mypackage mydatafilepath =
os.path.join(os.path.dirname(os.path.abspath(mypackage.file)),
'mydatafile')

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

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

import os def getApplicationPath(): packager = find_packager() if packager == 'py2exe': # note that another approach is return os.path.dirname(os.path.abspath(sys.argv[0])) elif packager == 'py2app': # from path/Foo.app/Contents/Resources -> path, even if
something in your application chdir'ed in the meantime return
os.path.dirname(os.path.dirname(os.path.dirname(os.environ['RESOURCEPATH ']))) else: import main return os.path.dirname(main.file)

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.

-bob



More information about the Pythonmac-SIG mailing list