Version 9 of Building Stand-Alone Tcl/Tk Applications under Mac OS X

Updated 2006-01-11 18:34:29

See also here [L1 ], if you want to make a bundle using the stand-alone version of the Tcl/Tk Aqua distribution.

See here [L2 ] about how to "Aquify" a Tcl/Tk Application


MAK This is a summary of steps required to build a stand-alone Tcl/Tk application under Mac OS X. Most of this is gleaned from tcl-mac list archives and through experimentation. By stand-alone I mean an application that will run on any system, regardless of whether or not Tcl/Tk is already installed. I've described the process assuming that you've got a binary application as opposed to just wanting to wrap a Tcl script, since that's what I'm trying to do. Other tips and information, which as what to do if you just want to wrap a Tcl script, would be welcome.

1. Use the embedded builds ("make embedded" from the tcl/macosx and tk/macosx directories). This does three things for you.

First, the extra cruft of documentation and other non-essential files are excluded from the Tcl.framework and Tk.framework directories, so that when you include them in your application bundle they do not make the size of the bundle larger than necessary.

Second, it creates a stand-alone "Wish Shell.app" application bundle, which you may choose to use as a template for building your own application bundle. It contains the Tcl and Tk frameworks inside it so this app can be used on any system, regardless of whether or not Tcl/Tk is installed. This is, I believe, how the TclTkAquaStandalone [L3 ] distribution is built. (You could of course just use that distribution, but if, like me, you've made any patches/bug fixes to Tcl/Tk Aqua you'll want to know how to properly build it for stand-alone yourself.)

Third, the binaries in the bundle (including the Wish Shell binary and the Tcl and Tk frameworks) are linked with executable-relative paths to each other using the @executable_path magic keyword. That is, if you run 'otool -L "Wish Shell.app/Contents/MacOS/Wish Shell"' the output looks like:

 Wish Shell:
        @executable_path/../Frameworks/Tk.framework/Tk (compatibility version 8.4.0, current version 8.4.0)
        @executable_path/../Frameworks/Tcl.framework/Tcl (compatibility version 8.4.0, current version 8.4.0)
        /usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 71.1.1)

This is essential for the application to use the included Tcl/Tk binaries instead of trying to find them in the usual system-wide location (/Library/Frameworks/Tcl.framework and Tk.framework).

2. You need an application bundle. As mentioned above, the embedded build will create a stand-alone "Wish Shell.app" which you can use as a template, but it is useful to know how the bundle is structured, and to be able to build it yourself. Some references that may be helpful:

  • Anatomy of a Modern Bundle [L4 ]
  • Information Property List Files [L5 ]

The basic structure that you'll need for the bundle, including an applicaton-specific icon, is as follows:

 MyApp.app/
    Contents/
        Info.plist
        Frameworks/
            Tcl.framework/
            Tk.framework/
        MacOS/
            MyApp
        Resources/
            MyApp.icns

Here, MyApp is the name of your application. The Info.plist file is the Information Property List file. It's an XML file that provides information to the operating system about your application, such as the name of the binary (MyApp), the application's version, and the name of the icon file, possibly document file types that it opens, etc.

3. Copy Tcl.framework and Tk.framework from "Wish Shell.app" into the place shown above. You should not need to do anything else with them, since they have already been linked with the @executable_path keyword in them.

4. Your application binary (MyApp) will undoubtedly point to the wrong location for the frameworks, rather than using @executable_path as the frameworks do. This is a problem that you need to fix or else your application won't run on another system without Tcl/Tk installed, or installed in some other location. You can fix this with the install_name_tool command line tool provided with OS X's development tools:

   install_name_tool -change old new file

'old' is the currently existing path in the binary, which you can obtain using "otool -L MyApp.app/Contents/MacOS/MyApp". I'm not sure how to automate this yet so you can fix the paths with a makefile (anyone?), but what you need to do is:

   install_name_tool -change /old/path/to/Tcl \
       @executable_path/../Frameworks/Tcl.framework/Tcl \
       MyApp.app/Contents/MacOS/MyApp
   install_name_tool -change /old/path/to/Tk \
       @executable_path/../Frameworks/Tk.framework/Tk \
       MyApp.app/Contents/MacOS/MyApp

Your application's binary should now find Tcl/Tk in the right location, no matter where or on what system the application is placed.

There is a lot more that you can do with the application bundle, such as adding binaries targetted for different MacOS versions, language localization, etc. Some of the details can be found at the above references, but unless you're needing to do that the above should be enough to get you started, hopefully.


Ashley Ward Here's a way of automating the install_name_tool step.

  install_name_tool -change `otool -L MyApp.app/Contents/MacOS/MyApp | \
    sed -n 's/\(^[^/]*\(\/[^(]*Tcl\) .*$\)/\2/p'` \
    @executable_path/../Frameworks/Tcl.framework/Tcl \
    MyApp.app/Contents/MacOS/MyApp

That unreadable piece of sed finds the path given by otool -L to the (presumably currently system) Tcl shared library.

  sed -n 's/\(^[^/]*\(\/[^(]*Tcl\) .*$\)/\2/p'

means, for each line of input:

    find
    \(
        ^ -- beginning of line, followed by
        [^/]* -- zero-or-more characters that are not '/', followed by
        \(
            \/ -- the character '/', followed by
            [^(]* -- zero-or-more characters that are not '(', followed by
            Tcl -- the string 'Tcl', followed by
        \)
        a space character, followed by
        .*$ -- zero-or-more characters to end of line
    \)
    for an expression located within the first (outer) parentheses (NB which
    were escaped, thus '\('),
    substitute the expression (denoted by '\2') located within the second
    (inner) parentheses,
    print the result (note that the -n option instructs sed not to print
    the result if no matches are found).

I think this works -- I'm using a more involved version in an autoconf-generated Makefile. You will need to repeat this for Tk.


See also: More MacOS X Techniques


Category Mac