PkgIndexUtil.tcl - determining a platform name

Michael Lenaghan wrote to the starkit mailing list:

Like many other people here I've built a little system for automating Starpack/Starkit construction. Like many other people here I ran into the problem of loading platform-specific libraries. Since I wanted to do "cross-compiling" of Starpacks/Starkits, I also ran into the problem of knowing various extensions on different platforms.

I borrowed and modifed some things that I saw in other kits and on the wiki. In case it's helpful I'll post here my approach. (Note that some changes are subtle. For example, I changed the "Power Macintosh" glob to Power*, because according to another page on the wiki some PowerPC platforms report as "Power PC" and those should also get switched to "ppc".)

Finally: I did modify some of the stuff I saw, and someone here may spot something I did that doesn't make sense on platform X. Oh, in fact that's pretty much a given... :-)


 # File:      PkgIndexUtil.tcl
 # Author:    Michael Lenaghan
 # Copyright: Public Domain
 # Created:   2003/09/30
 
 # Based on an idea from the wiki @ https://wiki.tcl-lang.org/8522
 
 # Add this line to the top of a pkgIndex.tcl file to use this file:
 #
 #     source [file join [file dirname [info script]] "pkgIndexUtil.tcl"]
 #
 # Then add [platform osmachine] and [platform oslibextension] where
 # required in the load statements.
 
 proc platform {args} {
    if {[llength $args] > 0} {
        set cmd [lindex $args 0]
        set arg [lindex $args 1]
        switch -- $cmd {
            machine {
                global tcl_platform
                if {$tcl_platform(os) eq "AIX" && [regexp {^[[:xdigit:]]*$} $tcl_platform(machine)]} {
                    return unknown
                } else {
                    switch -glob -- $tcl_platform(machine) {
                        arm* {
                            return arm
                        }
                        intel -
                        i*86* {
                            return x86
                        }
                        Power* {
                            return ppc
                        }
                        sun4* {
                            return sparc
                        }
                        9000* {
                            return 9000
                        }
                        default {
                            return [regsub -all {[ /]} \
                                [string tolower $tcl_platform(machine)] "-"]
                        }
                    }
                }
            }
            os {
                global tcl_platform
                switch -glob -- [lindex $tcl_platform(os) 0] {
                    Win* {
                        return windows
                    }
                    Sun* {
                        return solaris
                    }
                    default {
                        return [regsub -all {[ /]} \
                            [string tolower $tcl_platform(os)] "-"]
                    }
                }
            }
            osmachine {
                return "[platform os]-[platform machine]"
            }
            osbinextension {
                if {[llength $args] == 1} {
                    set os [platform os]
                } else {
                    set os $arg
                }
                switch -glob -- $os {
                    win* {
                        return ".exe"
                    }
                    default {
                        return ""
                    }
                }
            }
            oslibextension {
                if {[llength $args] == 1} {
                    set os [platform os]
                } else {
                    set os $arg
                }
                switch -glob -- $os {
                    darwin* {
                        return ".dylib"
                    }
                    mac* {
                        return ".shlb"
                    }
                    win* {
                        return ".dll"
                    }
                    default {
                        return ".so"
                    }
                }
            }
            default {
                error "bad option \"$cmd\": must be machine, os, osmachine, osbinextension, or oslibextension."
            }
        }
    } else {
        error "wrong # args: should be \"platform option ?arg arg ...?\""
    }
 }

Here's an example of use, from the first couple of lines in my XOTcl pkgIndex.tcl file:

 source [file join [file dirname [info script]] "pkgIndexUtil.tcl"]

 package ifneeded XOTcl 1.0 [list load [file join $dir [platform osmachine] libxotcl1.0[platform oslibextension]] XOTcl]

JMN 2003-10-08

The problem with this approach is that at the time the load statement is run, the 'platform' command may have been overwritten by a pkgIndex.tcl file in another extension.

But that doesn't matter--by that point the path would have already been resolved using this package's version of platform. In other words, platform is being used to generate the path, not to load the package.

The current system of placing little helper procs within pkgIndex files seems quite messy to me.

Note the pollution of the global namespace as demonstrated by this:

 %llength [info commands]
 86
 %package require nonexistant
 %llength [info commands]
 97

Would it be worthwhile working out some convention where helper procs placed in pkgIndex files are namespaced? For example should they all be placed under ::pkgIndex:: or should there be a separate namespace for each package? If they were all in one namespace then you'd still have the problem of subsequent pkgIndex.tcl files overwriting earlier procs... For widely applicable functions such as 'platform', would it make more sense to include this in the tcl distribution along with parray.tcl etc.. and if so, is this in the works, and what other developments are underway for the package loading mechanism?

In relation to starkits, I've started repackaging a couple of starkits so that they can be placed in the auto_path (along with a pkgIndex.tcl pointing to it of course) and then simply "package require pkgname".

This avoids the need to "source /path/to/kitfile.kit" prior to the package require but still allows them to be run directly as a starkit application too.

I'm not sure how that helps what the problem the above was meant to solve: loading the right shared library in a cross-platform Starkit.

I figure that combined with something like the sdx update facility, this might be a slightly nicer way to distribute packages.

The problems with making starkits behave this way are that a) you need a slightly different main.tcl to the one sdx qwrap generates automatically (no big deal I guess - either do it manually.. or maybe a new func could be added to sdx... 'sdx libwrap'?) b) The pkgIndex.tcl calls 'source' on the starkit (and then main.tcl inserts a new 'package ifneeded' statement earlier in the auto_path and package requires the pkg)... but of course once the kit is vfs mounted - it won't source a second time. (well it will, but you're sourcing a different thing) This means you can't load the package into another interp and/or another thread. I can get around that by detecting that the kit has already been vfs mounted and get pkgIndex.tcl to source kitfile.kit/main.tcl instead... hence the need for a helper proc, and my concern about a convention for where to put it/how to name it. c) I had to make a slight adjustment to starkit.tcl in the vfs package to avoid errors when loading in another interp.

While I'd quite like to wrap a few packages in this manner for my own use - It would be much more useful if there was a widely accepted standard for wrapping starkits as package requirable libraries.

I'm curious as to what others think of this approach to loading starkit packages.


DGP The best answer is not to use any "helper procs" in an index script. You achieve this by not putting complex logic in the index script. This is easiest by following a pattern of 1 index script per variant of a version of a package. Here variant would include platform issues.

Second best, if we really can't get people to embrace simplicity in their index scripts, would be to determine what the set of helper procs are that are needed, and gather them into one utility package for all index script to use.

I'm not sure how that fixes the problem; it just seems to move it up one level. Btw, it may not be clear, but the above was written to work with Starkits--and Starkits are capable of being cross-platform. Indeed, the above was specifically written to select the right library in a cross-platform Starkit.

Ultimately, I think there's one good solution to the problem: improve platform identification in the Core. Starkits provide good motivation for doing so.