Starkit - How To's

This page gathers together in one place examples and descriptions of how to develop a Starkit application. If you are a first time Starkit user then go to the Starkit home page (see [L1 ]) for an overview of Starkit and information on what tools you will need and where to download them.

Feel free to add your own How To or improve an existing one.

Tom Krehbiel



1. How does one convert an application to a Starkit?

A. Most tcl applications can be converted to a Starkit with little or no modification. The primary requirement is that all code exist under one node in the file system directory structure. A recommended Starkit directory structure is illustrated on the "Anatomy of a starkit" page (see [L2 ]). This recommended structure should be used for all new applications, but when converting an application, the only real requirement is that the file main.tcl exist at the top of the directory structure. Beyond this requirement you may want to put all packages in a lib directory at the top level because this directory is automatically added to the tcl auto_path by tclkit and doing this in the main.tcl script is a real pain.

Once you have fixed the directory structure of your application, you can proceed wrapping your application into a Starkit or Starpack.


2. How does one wrap/unwrap a Starkit or Starpack?

A. A starkit (or starpack) is wrapped using the sdx application. This application implements a lot of commands, but the wrap command is used to convert a directory containing an application into a starkit or starpack. To recover the original directory from a wrapped starkit or starpack use the sdx unwrap command.

The "How to assemble a starkit" page (see [L3 ]) illustrates how to use the sdx wrap and unwrap commands.

Where to put things in a starkit explains where various components go.


3. What kinds of things should one ensure are in the main.tcl for a starkit?

A. The following description is paraphrased from comments made by Jean-Claude Wippler.

These two lines should always be the first two in main.tcl:

package require starkit
if {[starkit::startup] eq "sourced"} return

Usually, main.tcl contains just one more line after that (where "myapp" is replaced by whatever your app is called):

package require app-myapp

That way, you can run things 4 ways:

  1. unwrapped: tclkit myapp.vfs/main.tcl
  2. starkit: tclkit myapp.kit
  3. starpack: myapp
  4. sourced: source myapp.vfs/main.tcl
  5. plugin: no, not yet, but who knows - one day?

The string returned by startup is one of the above, and indicates to the main.tcl script how it was launched. The check and return on "sourced" is a convenience; it prevents main.tcl from actually starting anything up. The effect is that the starkit's lib/ directory will have been added to the path, so all packages are available to the context where "source" was called. This auto_path change is part of the logic in starkit::startup.

The other thing starkit::startup does is to set the starkit::topdir variable to the directory where main.tcl resides.

There's a "gotcha" in the current startup logic. If one starkit sources another, say a collection of packages such as kitten, then the starkit::topdir variable will end up pointing to kitten. That's usually not what you want, so you have to save topdir in your scripts before sourcing other starkits:

set mytopdir $starkit::topdir
source somepath/kitten.kit

NOTE: The VFS in a Starkit is case-sensitive. If working on Windows, do not rename files in an unwrapped Starkit as it can lead to failures of the wrapped application which don't show up in the unwrapped application (because the Windows file system is not case sensitive.). -- CLN


4. How do I create/use a Starkit in a Starkit?

A. The following description was provided by Jean-Claude Wippler.

 > Could you elaborate a little on the "three-file mode". I'm only 
 > familiar with the 2-file mode. What is the correct method for loading 
 > extentions from a third starkit.

It's what Kitten does. One file is tclkit, one file is the application starkit, and a third file contains a bunch of extensions; it could come from another source, or contain things one wants to have around in multiple projects anyway.

Here's a quick summary of steps to create/use a separate starkit for extensions:

  • create myexts.vfs/main.tcl containing just these two lines (i.e. setup & return):
     package require starkit
     starkit::startup
  • place all your extensions inside myexts.vfs/lib/
  • wrap it up using "sdx wrap myexts.kit"
  • to use these extensions, all you need to do is "source myexts.kit"

A few possible refinements:

  • if you want it to work as above but also list all packages when launched, use:
     package require starkit
     if {[starkit::startup] eq "sourced"} return
     puts [lsort [package names]]
     # note that you'll also see other packages on auto_path
  • to source myexts.kit when it lives next to the launched app starkit:
     source [file join [file dirname $starkit::topdir] myexts.kit]
  • to avoid unwanted interaction between two starkits, perhaps make that:
     set savetop $starkit::topdir
     source [file join [file dirname $savetop] myexts.kit]
     set starkit::topdir $savetop

5. How do I display a Console window when a starkit is run on Windows?

A. The following example illustrates how to display a Console window when a starkit (or starpack) is run on Windows.

package require starkit
starkit::startup

if { $tcl_platform(platform) eq "windows" } {
    package require Tk
    wm withdraw .
    console show
}

... application code starts here ...

6. Why doesn't my file command working inside a starkit?

A. Commands such as file may not work as expected on files within the Virtual File System, which is the way a starkit provides access to the files within the starkit.

[More details?]


7. What are the issues for locating the current directory within a starkit?

A. The root {i.e. mount point} for a starkit is stored in the $starkit::topdir variable. The cd and pwd commands work as you would expect them to work.


8. Can one use relative path names within Tcl commands like source?

A. Yes.


9. Can one exec another file that resides within a starkit?

A. No, but the executable file can be copied to /tmp (or some other location) on the host and then exec'ed. (An example of how to do this: Matthias Hoffmann - Tcl-Code-Snippets - Starkit/pack-related.)

DKF: Be aware that some OS deployments might have a security policy in place that prevents files in temporary directories from being executable.


10. Can someone show an example of how one could put a text file into a starkit, and then read it?

I tried adding to my starkit a line like

set abc [split [read [open "data/stuff.txt" "r"] ] "\n"]

but could not find the correct directory into which I should put data/stuff.txt so that it could be found by the application.

A. Try changing that open to

open [file join $::starkit::topdir data stuff.txt] r

- snichols


11. How does one go about making a starkit writable?

A. If you want to update, add or delete items in a starkit then wrap the starkit with a command similar to the one below.

sdx wrap blah.kit -writable   ;# see: sdx help wrap

- davidw


12. How to set up starkits as CGI?

(see [L4 ])

I tried setting up a starkit as a CGI but I got:

application-specific initialization failed: couldn't open "setup.tcl": no such file or directory

What's really weird is that if I copy the starpack to /usr/bin, so that it's in the path, things seem to work.

A. Permissions: sounds like tclkit cannot re-open itself for reading, needed to get at the embedded runtime files. -jcw

re: That's not it. See the mailing list: http://www.equi4.com/pipermail/starkit/2005-June/002436.html

Turns out it was something that needed fixing in Tcl and or Starpacks, and since it could take years to get a fix into Tcl, hopefully it will be included in the starpack sources:

http://www.equi4.com/pipermail/starkit/2005-July/002465.html


13. Can code in a starkit load a file or shared object from within the same starkit?

A. Yes. Files in a starkit are mounted through VFS so the load command works as expected. I normally put the file (or shared object) that is to be load under the starkit's lib directory along with an appropriate pkgIndex.tcl file. Then use the package command to perform the load.

Discussion:

The loading of shared objects is a tricky subject.

Here's something that Steve Landers wrote during early 2007 on the starkit mailing list:

When loading a shared library on most Unix platforms, the Tcl VFS code will first copy it from the VFS to a temporary file, then dynamically load it, then remove the temporary file.

On Windows and HP-UX, it can't remove the temporary file while it is still in use by the system, so this has to happen during the Tcl shutdown sequence ...

In the past I've included some code in my applications to clean up any of these temporary files left lying around after an application or system crash. I thought that I'd wikified it, but can't locate it so I've included it here in case it is useful to you.

You'll need to set appropriate values for dir and pattern in the Windows section of the switch command. Call "Cleanup" near the start of your application and it will prevent temporary files from accumulating over time.

 #
 #  Clean up temporary files left by Tcl when loading shared libraries  from
 #  within a virtual file system (VFS).
 #
 #  Only needed on certain platforms (the Tclkit version number
 #  is in $vfs::tclkit_version)
 proc Cleanup {} {
     global tcl_platform
     switch $tcl_platform(os) {
         HP-UX {
             # Tcl uses mkstemp to create the temporary file. As at  HP-UX
             # B.11.00 this doesn't use the TMPDIR, TMP or TEMP  variables, but
             # rather places the temporary files in /var/tmp
             set dir /var/tmp
             set pattern "tcl??????"
         }
         Windows* {
             return
         }
         default {
             return
         }
     }
     # note we silently ignore any errors
     catch {
         if {[file isdirectory $dir]} {
             set user $tcl_platform(user)
             set now [clock seconds]
             # number of seconds old that a temporary file must be  before it will be
             # deleted - this is done to avoid the (extremely  unlikely) race
             # condition wherein two copies of the application are run
             # currently, and this cleanup runs between the VFS  creating the
             # temporary file and when it loads it
             set age 10
             cd $dir
             foreach file [glob $pattern] {
                 # only those files owned by the current user
                 if {[file attributes $file -owner] eq $user} {
                     if {[expr {$now - [file mtime $file]}] > $age} {
                         file delete $file
                     }
                 }
             }
         }
     } msg
     # puts stderr "msg = $msg"          ;# uncomment to debug
 }

JOB 17-04-06, this behavior (as described above) still remains for windows (and maybe on all other platforms as well).

  • To get the code working, a few modifications are required though.
  • Looks like this code should be regularily added to each starkit distribution to avoid overloading the TEMP directory.
#
#  Clean up temporary files left by Tcl when loading shared libraries  from
#  within a virtual file system (VFS).
#
proc cleanup_temp_for_win {dir {pattern {"TCL????????"}} } {

        if { $::tcl_platform(platform) != "windows"} {
                return
        }

        # note we silently ignore any errors
        catch {
                if {[file isdirectory $dir]} {
                        set now [clock seconds]
                        # number of seconds old that a temporary file must be before it will be
                        # deleted - this is done to avoid the (extremely unlikely) race
                        # condition wherein two copies of the application are run
                        # currently, and this cleanup runs between the VFS creating the
                        # temporary file and when it loads it
                        set age 10
                        cd $dir
                        
                        # -nocomplain not required, as we use catch,
                        # but {*} is required, otherwise the pattern would still contain
                        # leading and trailing (") and therefore would fail (!)
                        #
                        foreach file [glob {*}$pattern] {
                                # only those files owned by the current user
                                if { [file owned $file] != 0 } {
                                        if {[expr {$now - [file mtime $file]}] > $age} {
                                                # delete directory along with everything in it, recursively:
                                                file delete -force -- $file
                                        }
                                }
                        }
                }
        } msg
        puts stderr "msg = $msg"  ;# uncomment to debug
}


if {0} {
        # show the console window
        package require Tk
        wm withdraw .
        console show
        console eval {wm protocol . WM_DELETE_WINDOW {exit 0}}

        puts "-->  $::env(TEMP)"
        cleanup_temp_for_win $::env(TEMP)
}

Some volounteers to test and extend the code on different platforms would also be great!