Bundling images and libraries with TclApp

Posted by kevinw on 2006-07-10 13:20
OS: Windows | Product: Tcl Dev Kit | tags: bundle image library tclapp wrap
Question: 

Do you have an easy-to-follow example of how to bundle images and libraries using TclApp?

Answer: 

A question that is frequently asked is how to bundle images and dependent libraries with TclApp, such that the whole application can be distributed as a single .exe on Windows platforms. It's a great question -- it's easy to turn off users with complex installers or applications that bomb because they can't find splash.gif.

Suppose we wanted to make a simple application that displays a pretty picture and generates a UUID (Universally Unique IDentifier). UUIDs aren't terribly useful to humans, but webservers and other programs use them fairly frequently.

First, let's grab a pretty picture. It should be a reasonably standard picture format -- GIF, JPEG, PNG, TIFF, PostScript... Any of these will do. For a full list of supported formats, see the documentation on the Img package for Tk. I'll use a GIF in this example, because it's what I have sitting around. For the purposes of this demo, we'll call the image file mylogo.gif.

The second step is to write our Tcl/Tk source file. I've attached the version I'm using, and we'll take it apart line by line.

package require Tk
package require Tcl
package require uuid


proc MyUUID {} {
	return [::uuid::uuid generate]
}


proc Main {} {
        image create photo foo -file [file join [file dirname [info script]] mylogo.gif]
#        image create photo foo -file "lib/application/Documents and Settings/activetest/Desktop/myapp/aslogo.gif"
        label .l1 -image foo -bd 1 -relief sunken800
        pack .l1 -side top
	label .l2 -text [MyUUID]
	pack .l2 -side bottom
        label .l3 -text [file dirname [info script]]
        pack .l3 -side bottom
}

Main

The first few lines are fairly standard boilerplate:

package require Tk
package require Tcl
package require uuid

These bring in the Tk, Tcl, and uuid libraries respectively. uuid is part of Tcllib, which ships as part of ActiveTcl. If you're not using ActiveTcl, you can get Tcllib from their Sourceforge project page.

proc MyUUID {} {
    return [::uuid::uuid generate]
}

Here we define a procedure that returns a UUID. This code does not have to go in a procedure, of course -- it could be written in-line.

proc Main {} {
    image create photo foo -file [file join [file dirname [info script]] mylogo.gif]
    label .l1 -image foo -bd 1 -relief sunken
    pack .l1 -side top

Next, we define the Main procedure. This isn't strictly necessary to do, but I define a Main procedure so nothing executes without it being called specifically. I find it handy for debugging purposes, but your mileage may vary.

First up in Main is to load the image. We'll call the image foo, for lack of a better name. Notice the file specification -- there is a bit of a song and dance going on there that needs a little explanation. The easiest way to read it is from the inside out. [info script] returns the full pathname of the script. [file dirname] returns the directory name of its argument, and [file join] composes a pathname from its arguments, inserting the appropriate path separator.

So why all the song and dance? When you're packing an image inside your executable, it's not always obvious as to how to find it. One thing you can usually rely on is for [info script] to return the proper path to the script that's running, and from there you can simply chop off the script name and add on the image name, as we have done above. This works for any kind of file.

The label command puts the image on a label, which can be packed into a window. In this case, we're using the toplevel window. Next, we pack the label, so it is visible.

    label .l2 -text [MyUUID]
    pack .l2 -side bottom
    label .l3 -text [file dirname [info script]]
    pack .l3 -side bottom
}

Main

Now it's time to add the UUID. Because MyUUID returns the text of the UUID, we can use the -text option to add it directly to the label.

For a finishing touch, we add the directory name as it is seen by the script, executing inside the wrapper.

Finally, we call Main -- and that's the end of the Tcl file.

The next step is to use TclApp to create the executable. Open TclApp, and you will be presented with a screen that looks like (click to view larger image):

.

Click the button with the document on it, and select the files you would like to wrap. In this case, we'll just wrap the Tcl file and the image. Next, we choose the packages we would like to wrap in the package. Click on the button with the yellow box, and select tcllib-1.8. You will see an entry for the package appear. Now hit the "Scan" button, and it should find the rest of the packages you need for you:

To make TclApp generate an executable file, we need to set the prefix file. Because we're using Tk, we'll choose base-tk-thread-win32-ix86.exe:

Don't forget to set your output file, as it's no fun to go hunting for your new application.

Last, we click over to the "Run" tab, and click the "Wrap" button. This wraps our application and shows any problems that might occur

Now you can run your application. Try renaming your image file -- the application still works, as it's using the wrapped image. You can even install your application on another machine that doesn't have ActiveTcl or the TDK installed, and there is no problem.

Of course this is a fairly trivial example. Things can get hairy when you're wrapping larger, more complex applications. If you run into any problems, try signing onto the TDK mailing list and asking around. There are great people there.

jwmiller4623 | Tue, 2007-01-23 10:23

Was this file supposed to come with my dev kit?

kevinw
ActiveState Staff
Tue, 2007-01-23 10:36

Which file are you referring to? The basekit comes with ActiveTcl, and the sample file above is just something I threw together in the spur of the moment.

Cheers,

kjw

jwmiller4623 | Tue, 2007-01-23 16:50

I found it. I just needed a newer version of TK.