Tcl Source Code

tclsh initialization sequence
Login

Below is the initialization sequence for tclsh 9.0.2 on Windows. I assume Unix will be very similar.

Call tree (9.0.2)
main
    setlocale(LC_ALL, C)
    Change \ to / in argv[0]
    TCL_LOCAL_MAIN_HOOK(argc, argv) ifdef TCL_LOCAL_MAIN_HOOK
    TclZipfs_AppHook
    |   Tcl_FindExecutable
    |   |   Tcl_InitSubSystems
    |   |   |   if subsystemsInitialized == 0
    |   |   |   |   TclInitThreadStorage
    |   |   |   |      TclInitThreadAlloc
    |   |   |   |   TclpInitPlatform
    |   |   |   |       WSAStartup
    |   |   |   |       TclpGetCodePage
    |   |   |   |   TclInitDoubleConversion
    |   |   |   |   TclInitObjSubsystem
    |   |   |   |       Tcl_InitHashTable(&typeTable...)
    |   |   |   |       Tcl_RegisterObjType()+ for all the core types
    |   |   |   |   TclInitIOSubsystem
    |   |   |   |       TCL_TSD_INIT(&dataKey) only
    |   |   |   |   TclInitEncodingSubsystem
    |   |   |   |       Tcl_InitHashTable(&encodingTable)
    |   |   |   |       Tcl_CreateEncoding()+ for base encodings
    |   |   |   |       defaultEncoding = systemEncoding = iso8859-1
    |   |   |   |   TclInitNamespaceSubsystem
    |   |   |   endif
    |   |   |   TclInitNotifier (Irrespective of subsystemsInitialized value -> per-thread initialization)
    |   |   TclpSetInitialEncodings
    |   |   | Tcl_SetSystemEncoding(Tcl_GetEncodingNameFromEnvironment)
    |   |   TclpFindExecutable
    |   |   | TclSetObjNameOfExecutable
    |   |   |   TclSetProcessGlobalValue(&executableName)
    |   |   Tcl_GetNameOfExecutable
    |   TclZipfs_Init
    |   |   ZipfsSetup
    |   |     Tcl_FSRegister(&zipfsFilesystem)
    |   |     Initialize internal hash tables
    |   |   interp is null so no interp-specific initialization
    |   TclZipfs_Mount(Mount from application exe. Fails unless static build)
    Tcl_SetPanicProc
    |   Tcl_InitSubsystems (second invocation!) (why does Tcl_SetPanicProc call it?)
    |       TclInitNotifier (second invocation!) - does not do anything additional
    Tcl_CreateInterp
    |   Tcl_InitSubsystems (third invocation!)
    |       TclInitNotifier (third invocation!)
    |   Tcl_InitHashTable(&cancelTable)
    |   if (commandTypeInit == 0)
    |       TclRegisterCommandTypeName()+ for proc, ensemble, alias etc.
    |   endif
    |   Create and allocate interp
    |   TclInitLiteralTable
    |   TclCreateExecEnv
    |   Tcl_AsyncCreate
    |   TclResetRewriteEnsemble
    |   TclInitLimitSupport
    |   create core commands
    |   TclInterpInit
    |   TclSetupEnv
    |   TclInitEmbeddedConfigurationInformation
    |   Init platform array and version globals
    |       TclpSetVariables
    |           TclGetProcessGlobalValue
    |               InitializeDefaultLibrary -  sets global tclDefaultLibrary
    |   Tcl_PkgProvide(Tcl)
    |   TclTommath_Init
    |   TclOOInit
    |   TclZlibInit
    Tcl_MainEx
    |   TclpSetInitialEncodings (second invocation!)
    |   TclpFindExecutable(argv[0]) (second invocation)
    |   |   TclSetObjNameOfExecutable
    |   |     TclSetProcessGlobalValue(&executableName)
    |   Tcl_InitMemory
    |   Tcl_GetStartupScript(NULL)
    |   Tcl_GetStartupScript(&encodingName)
    |   set ::argv and ::argc
    |   tcl_interactive = isatty()
    |   Tcl_Preserve(interp)
    |   Tcl_AppInit
    |   |   Tcl_Init
    |   |   |   TclInitPkgFiles
    |   |   |     Tcl_SetAssocData(interp, "tclPkgFiles")
    |   |   |   Tcl_EvalEx(tclPreInitScript) if tclPreInitScript not NULL
    |   |   |   Tcl_EvalEx(run tclInit script - see below - to find init.tcl and run it. tclInit is application definable)
    |   |   |   TclpSetInitialEncodings (third invocation)
    |   |   Registry_Init (if static build)
    |   |   Dde_Init (if static build)
    |   |   Tcl_EvalEx(set tcl_rcFileName) - TODO - why eval, use C API
    |   Check Tcl_InterpDeleted
    |   Check Tcl_LimitExceeded
    |   if TclFullFinalizationRequested() Tcl_CreateExitHandler(FreeMainInterp)
    |   path = Tcl_GetStartupScript(&encodingName) - have to refetch since appInitProc might have changed it (why get it initially with NULL arg then?)
    |   if path != NULL
    |   |   Tcl_FSEvalFileEx(path, encodingName) - run script passed on command line
    |   else (path == NULL)
    |   |   Tcl_SourceRCFile(interp)
    |   |   Tcl_LinkVar("tcl_interactive", &is.tty)
    |   |   while ()
    |   |   |   mainLoopProc = TclGetMainLoop()
    |   |   |   if mainLoopProc == NULL
    |   |   |       read from stdin and run command
    |   |   |   else
    |   |   |       mainLoopProc()
    |   |   |       Tcl_SetMainLoop(NULL)
    |   |   |   endif
    |   |   endwhile
    |   endif
    |   mainLoopProc = TclGetMainLoop()
    |   if (mainLoopProc)
    |       mainLoopProc()
    |       Tcl_SetMainLoop(NULL)
    |   endif
    |   Tcl_EvalObjEx(interp, "exit") if interp not deleted
    |   Tcl_Exit() (if above does not exit itself)

The init.tcl script that is processed as part of initialization is located as follows by the tclInit script. The tclInit script constructs a list of scripts each of which attempts to locate the init.tcl file. Each of these scripts is run in turn and the init.tcl returned by the first script that succeeds is run.

tclInit script:

if {[namespace which -command tclInit] eq ""} {
    proc tclInit {} {
        global tcl_libPath tcl_library env tclDefaultLibrary
        rename tclInit {}
        if {[info exists tcl_library]} {
            set scripts {{set tcl_library}}
        } else {
            set scripts {}
            if {[info exists env(TCL_LIBRARY)] && ($env(TCL_LIBRARY) ne {})} {
                lappend scripts {set env(TCL_LIBRARY)}
                lappend scripts {
                    if {[regexp ^tcl(.*)$ [file tail $env(TCL_LIBRARY)] -> tail] == 0} continue
                    if {$tail eq [info tclversion]} continue
                    file join [file dirname $env(TCL_LIBRARY)] tcl[info tclversion]
                }
            }
            lappend scripts {::tcl::zipfs::tcl_library_init}
            if {[info exists tclDefaultLibrary]} {
                lappend scripts {set tclDefaultLibrary}
            } else {
                lappend scripts {::tcl::pkgconfig get scriptdir,runtime}
            }
            lappend scripts {
                set parentDir [file dirname [file dirname [info nameofexecutable]]]
                set grandParentDir [file dirname $parentDir]
                file join $parentDir lib tcl[info tclversion]} \
                {file join $grandParentDir lib tcl[info tclversion]} \
                {file join $parentDir library} \
                {file join $grandParentDir library} \
                {file join $grandParentDir tcl[info tclversion] library} \
                {file join $grandParentDir tcl[info patchlevel] library} \
                {
                    file join [file dirname $grandParentDir] tcl[info patchlevel] library}
            if {[info exists tcl_libPath]
                && [catch {llength $tcl_libPath} len] == 0} {
                for {set i 0} {$i < $len} {incr i} {
                    lappend scripts [list lindex \$tcl_libPath $i]
                }
            }
        }
        set dirs {}
        set errors {}
        foreach script $scripts {
            if {[set tcl_library [eval $script]] eq ""} continue
            set tclfile [file join $tcl_library init.tcl]
            if {[file exists $tclfile]} {
                if {[catch {uplevel #0 [list source $tclfile]} msg opts]} {
                    append errors "$tclfile: $msg"
                    append errors "[dict get $opts -errorinfo]"
                    continue
                }
                unset -nocomplain tclDefaultLibrary
                return
            }
            lappend dirs $tcl_library
        }
        unset -nocomplain tclDefaultLibrary
        set msg "Cannot find a usable init.tcl in the following directories: "
        append msg "    $dirs "
        append msg "$errors "
        append msg "This probably means that Tcl wasn't installed properly. "
        error $msg
    }
}
tclInit

Most of these locator scripts above are straightforward in that they check environment variables or various file system directories. Execution of the script that checks the zipfs file system is a bit more involved and implemented by the tcl::zipfs::tcl_library_init command which invokes the ZipFSTclLibraryObjCmd C function. This function executes as follows:

ZipFSTclLibraryObjCmd
  TclZipfs_TclLibrary
    if zipfs_tcl_library_init (optimization added by Sergey)
      ScriptLibrarySetup(zipfs_literal_tcl_library)
        searchPath = zipfs_literal_tcl_library/encoding
        Tcl_EncodingSearchPath(searchPath)
          TclSetProcessGlobalValue(&encodingSearchPath, searchPath) BUG [87b69745be] -> overwrites encoding dirs effect in existing interpreters
        TclpSetInitialEncodings() (fixes bug [fccb9f322f])
      return ScriptLibrarySetup return value
    endif
    if ZIPFS_APP_MOUNT/tcl_library/init.tcl exists
      zipfs_literal_tcl_library = ZIPFS_APP_MOUNT/tcl_library/init.tcl
      ScriptLibrarySetup(zipfs_literal_tcl_library)
      return ScriptLibrarySetup return value
    endif
    if not a static build
      if ZipfsAppHookFindTclInit(tcl dll) == TCL_OK -> sets zipfs_literal_tcl_library if found
        return ScriptLibrarySetup(zipfs_literal_tcl_library)
      endif
    endif
    zipfs_tcl_library_init = 1 (so zipfs search is not repeated irrespective of failure)