Updated 2005-01-21 13:15:48 by lwv

21Jan2005 - PS

This code was created for a starpack based installer/updater project we have at Unitas Software. I may have updated versions available, drop me a line if you are curious/want to have it.

- Pascal Scheffers
    # kitsplit -*- tcl -*-
    #
    #   Splits a starpack into the original executable tclkit.exe
    #   and a *.kit file or .vfs/ directory.
    #
    # Copyright (C) 2005 Unitas Software B.V. <info@unitas.nl>
    #    Author: Pascal Scheffers <pascal@scheffers.net>
    #
    # This file includes code written by Jean-Claude Wippler, Richard Suchenwirth
    # and others which was previously released to the public domain. The code in
    # question has comments to indicate.
    #
    # The authors hereby grant permission to use, copy, modify, distribute,
    # and license this software and its documentation for any purpose, provided
    # that existing copyright notices are retained in all copies and that this
    # notice is included verbatim in any distributions. No written agreement,
    # license, or royalty fee is required for any of the authorized uses.
    # Modifications to this software may be copyrighted by their authors
    # and need not follow the licensing terms described here, provided that
    # the new terms are clearly indicated on the first page of each file where
    # they apply.
    #
    # IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
    # FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
    # ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
    # DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
    # POSSIBILITY OF SUCH DAMAGE.
    #
    # THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
    # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
    # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
    # IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
    # NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
    # MODIFICATIONS.
    #
    #
    # Usage:
    #
    # Include kitsplit.tcl and a suitable pkgIndex.tcl in your starpack
    #
    # API Documentation:
    #
    #   kitsplit /TclkitToWrite/ /ApplicationkitToWrite/
    #      Writes a tclkit.exe and application.kit file from the currently running
    #      starpack. Existing files will be overwritten.
    #
    #      /TclkitToWrite/ full path for the tclkit.exe
    #
    #      /ApplicationkitToWrite/ full path for the application.kit
    #                              if the name ends with .vfs, an unwrapped version
    #                              will be written instead.
    #
    #  writeKitExe /TclkitToWrite/
    #      Writes a tclkit.exe running starpack.
    #      If the tclkit.exe already exists an error is raised.
    #
    #      /TclkitToWrite/ full path for the tclkit.exe
    #
    #  writeKitFile /ApplicationkitToWrite/ ?/wrapped/?
    #      Writes a application.kit from the running starpack.
    #      If the application.kit already exists an error is raised.
    #
    #      /ApplicationkitToWrite/ full path for the application.kit
    #      /wrapped/ Boolean value, 0 = write .vfs directory, 1=(default) write .kit file
    #
    #  unwrap /VFSDirToWrite/
    #      Writes a application.vfs/ from the running starpack.
    #      If the application.vfs/ already exists an error is raised.
    #
    #      /VFSDirToWrite/ full path for the application.vfs/
    #
    #  runningAsStarpack
    #      Returns 1 if the currently running application is a starpack, 0 otherwise.
    #      Query $::starkit::mode to figure out other states such as unwrapped,
    #      starkit, ...
    #
    # Example:
    #  package require kitsplit
    #  namespace import kitsplit::*
    #
    #  if { [runningAsStarpack] } {
    #     kitsplit c:/path/to/desired/tclkit.exe c:/path/to/desired/application.kit
    #  }
    #
    # contents of pkgIndex.tcl:
    #      package ifneeded kitsplit 1.0 [list source [file join $dir kitsplit.tcl]]

    namespace eval kitsplit {
        # Which files belong to tclkit.exe? (glob patterns)
        variable tclkitContent {
            boot.tcl
            config.tcl
            tclkit.ico
            lib/itcl*/*
            lib/vfs/*
        }
        lappend tclkitContent lib/tcl$::tcl_version/*
        lappend tclkitContent lib/tk$::tcl_version/*

        proc glob-r {{dir .}} {
            # By Richard Suchenwirth
            set res {}
            foreach i [lsort [glob -nocomplain -dir $dir *]] {
                if {[file type $i]=="directory"} {
                        eval lappend  res [glob-r $i]
                } else {
                        lappend res $i
                }
            }
            set res
        }

        proc isKitFile { filename } {
            variable tclkitContent
            foreach pattern $tclkitContent {
                if { [string match $pattern $filename] } {
                    return 1
                }
            }
            return 0
        }

        proc writeKitExe { path } {
            if { [file exists $path] } {
                error "writeKitExe will not over write an existing tclkit.exe file."
            }

            # Write the tclkit.exe from the current tclkit name-of-executable
            #vfs::unmount
            set exefile $::starkit::topdir
            set exefile z:/pascaltesting/unitasupdate.exe
            set tmpfile $path.writeKitTmp

            switch $::tcl_platform(platform) {
                windows {
                    exec $::env(COMSPEC) /c copy [file nativename $exefile] [file nativename $tmpfile]
                }
                default {
                    exec cp [file nativename $exefile] [file nativename $tmpfile]
                }
            }
            #file copy -force -- $::starkit::topdir $path
            set mnt ${exefile}_work
            vfs::mk4::Mount $tmpfile $mnt
            set cutlen [string length $mnt]
            incr cutlen

            set files [glob-r $mnt]
            set dirs {}
            foreach file $files {
                if { ![isKitFile [string range $file $cutlen end]] } {
                    file delete $file
                    if { [llength [glob -nocomplain -dir [file dirname $file] *]]==0 } {
                        file delete [file dirname $file]
                    }
                }
            }
            vfs::unmount $mnt
            mkpack $tmpfile $path
            file delete -force $tmpfile
        }

        proc unwrap { path } {
            # Copy out the entire application specific VFS to $path as a metakit.kit
            # file.
            writeKitFile $path 0
        }

        proc writeKitFile { path {wrapped 1} } {
            # Write the application.kit from the currenty running tclkit
            # will not over write an existing kit file.
            # returns the number of files written to the new kit file.
            if { [file exists $path] } {
                error "writeKitFile will not over write an existing (kit) file."
            }

            if { $wrapped } {
                set mnt $path.mntpoint
                vfs::mk4::Mount $path $mnt
            } else {
                set mnt $path
            }

            set files [glob-r $::starkit::topdir]
            set cutlen [string length $::starkit::topdir]
            incr cutlen
            set count 0
            foreach file $files {
                set filename [string range $file $cutlen end]
                if { ![isKitFile $filename] } {
                    if { ![file exists [file dirname [file join $mnt $filename]]] } {
                        file mkdir [file dirname [file join $mnt $filename]]
                    }
                    file copy $file [file join $mnt $filename]
                    incr count
                }
            }

            if { $wrapped } {
                vfs::unmount $mnt
            }
            return $count
        }

        proc mkpack {infile outfile} {
            # Adapted from sdx.kit

            # Take infile, copy the bit upto the actual mk file into outfile,
            # mk::file open the infile and write a new mk file appended to outfile.

            # returns the number of bytes shaved of the original. Negative value
            # indicates the new files is bigger.

            if {[file normalize $infile] eq [file normalize $outfile]} {
              error "input and output may not be the same file"
            }
            if {![file exists $infile]} {
              error "file does not exist"
            }
            if {![file isfile $infile]} {
              error "this is not a regular file (perhaps mounted as VFS?)"
            }
            set end [file size $infile]
            if {$end < 27} {
              error "file too small, cannot be a datafile"
            }

            set fd [open $infile]
            fconfigure $fd -translation binary -encoding binary
            seek $fd -16 end
            binary scan [read $fd 16] IIII a b c d

            #puts [format %x-%d-%x-%d $a $b $c $d]

            if {($c >> 24) != -128} {
              error "this is not a Metakit datafile"
            }

            # avoid negative sign / overflow issues
            if {[format %x $a] eq "80000000"} {
              set start [expr {$end - 16 - $b}]
            } else {
              # if the file is in commit-progress state, we need to do more
              error "this code needs to be finished..."
            }

            seek $fd $start
            switch -- [read $fd 2] {
              JL { set endian little }
              LJ { set endian big }
              default { error "failed to locate data header" }
            }
            seek $fd 0

            mk::file open db $infile -readonly

            set ofd [open $outfile w]
            fconfigure $ofd -translation binary -encoding binary
            fcopy $fd $ofd -size $start
            mk::file save db $ofd
            mk::file close db
            close $ofd
            close $fd
            return [expr {[file size $infile]-[file size $outfile]}]
        }

        proc kit_compare { kit1 kit2 } {
            # compares the two kitfiles file-by-file and lists the differences.
            # Mainly for debugging.

            if {[file normalize $kit1] eq [file normalize $kit2]} {
              error "input and output may not be the same file"
            }

            if { ![file isdirectory $kit1] } {
                vfs::mk4::Mount $kit1 $kit1
            }
            if { ![file isdirectory $kit2] } {
                vfs::mk4::Mount $kit2 $kit2
            }

            set _kit1files [glob-r $kit1]
            set _kit2files [glob-r $kit2]
            set kit1files {}
            set kit2files {}
            # Trim off the base path names:
            foreach kitno {1 2} {
                set cutlen [string length [set kit$kitno]]
                incr cutlen
                foreach name [set _kit${kitno}files] {
                    lappend kit${kitno}files [string range $name $cutlen end]
                }
            }

            # files in kit2 not in kit1
            set additions [list_extras $kit1files $kit2files]
            # files in kit1 not in kit2
            set missing [list_extras $kit2files $kit1files]

            if { ![file isdirectory $kit1] } {
                vfs::unmount $kit1
            }
            if { ![file isdirectory $kit2] } {
                vfs::unmount $kit2
            }

            return [list additions $additions missing $missing]
        }

        proc list_extras { list1 list2 } {
            # used by kit_compare
            # returns items in list2 not in list1
            set extras {}
            set list1 [lsort $list1]
            foreach item $list2 {
                if { [lsearch -sorted $list1 $item] == -1 } {
                    lappend extras $item
                }
            }
            return $extras
        }

        proc kitsplit { tclkitexe applicationkit } {
            # Write out the tclkit exe and the applicationkit.
            # If the applicationkit file name ends in .vfs write out as .vfs
            # directory, otherwise write out as metakitvfs.kit file.

            set wrapped [expr {[string tolower [file extension $applicationkit]] ne ".vfs"} ]

            if { [file exists $tclkitexe.splittmp] } {
                file delete -force $tclkitexe.splittmp
            }
            writeKitExe $tclkitexe.splittmp
            file rename -force $tclkitexe.splittmp $tclkitexe

            if { [file exists $applicationkit.splittmp] } {
                file delete -force $applicationkit.splittmp
            }
            writeKitFile $applicationkit.splittmp $wrapped
            file rename -force $applicationkit.splittmp $applicationkit
        }

        proc runningAsStarpack {} {
            return [expr { $::starkit::mode eq "starpack" } ]
        }

        namespace export kitsplit writeKitFile writeKitExe unwrap runningAsStarpack
    }

    package provide kitsplit 1.0

[ Category Package | Category Application | Category Tclkit ]