#!/bin/sh
# the next line restarts using tclsh \
exec tclsh "$0" ${1+"$@"}

# Copyright: 2016-2025 Paul Obermeier (obermeier@tcl3d.org)
# Distributed under BSD license.

namespace eval BawtHelp {

    proc GetProcShortName { procFullName } {
        return [lindex [split $procFullName ":"] end]
    }

    proc GetProcList {} {
        set procFullNameList [list]
        foreach ns { "BawtLog" "BawtZip" "BawtFile" "BawtBuild" "BawtMain" } {
            set procList [lsort -dictionary [info commands ::${ns}::*]]
            foreach procFullName $procList {
                set procShortName [GetProcShortName $procFullName]
                if { [string match "_*" $procShortName] } {
                    # Internal procedures start with "_". Do not add to table.
                    continue
                }
                lappend procFullNameList $procFullName
            }
        }
        return $procFullNameList
    }
}

namespace eval BawtLog {

    namespace export GetLogLevel SetLogLevel
    namespace export GetOutputLogDir GetLogFile
    namespace export Log GetLastLogMsg
    namespace export SetErrorMessage
    namespace export EnableLogFile
    namespace export SetLogTiming UseLogTiming
    namespace export GetWarningList GetErrorList ErrorAppend
    namespace export HaveFatalError
    namespace export WriteBuildLog
    namespace export GetBawtLogViewerProg
    namespace export StartBawtLogViewerProg

    variable sLogLevel       3
    variable sUseLogTiming   true
    variable sUseLogFile     true
    variable sErrorList      [list]
    variable sWarningList    [list]
    variable sHaveFatalError false

    proc GetLogLevel {} {
        variable sLogLevel

        return $sLogLevel
    }

    proc SetLogLevel { logLevel { handleLogFile true } } {
        variable sLogLevel

        set sLogLevel $logLevel
        if { $handleLogFile } {
            if { $logLevel > 0 } {
                EnableLogFile true
            } else {
                EnableLogFile false
            }
        }
    }

    proc GetOutputLogDir {} {
        set dir [file join [GetOutputArchDir] "Logs"]
        return $dir
    }

    proc GetLogFile {} {
        return [file join [GetOutputLogDir] "_BawtBuild.log"]
    }

    proc SetLogTiming { onOff } {
        variable sUseLogTiming

        set sUseLogTiming $onOff
    }

    proc UseLogTiming {} {
        variable sUseLogTiming

        return $sUseLogTiming
    }

    proc EnableLogFile { { useLogFile true } } {
        variable sUseLogFile

        if { $useLogFile } {
            set sUseLogFile true
            set logFile [GetLogFile]
            CreateBackupFile $logFile false
            Log "LogFile $logFile" 0 false
        } else {
            set sUseLogFile false
        }
    }

    proc GetLastLogMsg {} {
        variable sLastLogMsg

        return $sLastLogMsg
    }

    proc Log { message { numSpaces 0 } { useTimeStr true } } {
        variable sUseLogFile
        variable sLastLogMsg
      
        set sLastLogMsg $message
        if { [expr [GetLogLevel] * 2] > $numSpaces } {
            if { $useTimeStr && [UseLogTiming] } {
                set timeStr [clock format [clock seconds] -format "%H:%M:%S"]
            } else {
                set timeStr [string repeat " " 8]
            }
            set logMsg ""
            if { $message ne "" } {
                append logMsg $timeStr
                if { $useTimeStr } {
                    append logMsg " > "
                } else {
                    append logMsg "   "
                }
                if { $numSpaces > 0 } {
                    append logMsg [string repeat " " $numSpaces]
                }
                append logMsg $message
            }

            puts $logMsg
            flush stdout

            if { $sUseLogFile } {
                if { ! [file isdirectory [GetOutputLogDir]] } {
                    file mkdir [GetOutputLogDir]
                }
                set fp [open [GetLogFile] "a"]
                puts $fp $logMsg
                close $fp
            }
        }
    }

    proc SetErrorMessage { message } {
        set caller [info frame -2]
        set procName [lindex [dict get $caller cmd] 0]
        Log "Error in $procName: $message" 2
    }

    proc GetErrorList {} {
        variable sErrorList

        return $sErrorList
    }

    proc GetWarningList {} {
        variable sWarningList

        return $sWarningList
    }

    proc ErrorAppend { msg { errorType "Error" } } {
        variable sErrorList
        variable sWarningList

        if { $errorType eq "Warning" } {
            lappend sWarningList $msg
        } else {
            lappend sErrorList $msg
        }
        if { [GetLogLevel] > 0 } {
            Log "\n$errorType > $msg" 0 false
        } else {
            puts "\n$errorType > $msg"
            flush stdout
        }
        if { $errorType eq "FATAL" } {
            if { [ExitOnFatalError] } {
                PrintSummary
                exit 1
            }
            HaveFatalError true
        }
    }
    
    proc HaveFatalError { { onOff "" } } {
        variable sHaveFatalError

        if { $onOff eq "" } {
            return $sHaveFatalError
        } else {
            set sHaveFatalError $onOff
        }
    }

    proc WriteBuildLog { libName appendToFile logMsg } {
        if { [GetLogLevel] > 0 } {
            if { [UseStage "Test"] } {
                set fileName [format "%s-Test.log" $libName]
            } else {
                set fileName [format "%s.log" $libName]
            }
            set logFile [file join [GetOutputLogDir] $fileName]
            if { $appendToFile } {
                set mode "a"
            } else {
                set mode "w"
                CreateBackupFile $logFile false
            }
            set fp [open $logFile $mode]
            puts $fp $logMsg
            close $fp
        }
    }

    proc GetBawtLogViewerProg {} {
        if { [IsDarwin] } {
            set progName "BawtLogViewer.app"
        } else {
            set progName "BawtLogViewer[GetExeSuffix]"
        }
        set logPath [file join [GetBootstrapDir] $progName]
        if { [file exists $logPath] } {
            return $logPath
        }
        if { [IsDarwin] } {
            set logCmd ""
            foreach path [split $::env(PATH) ":"] {
                if { [file isdirectory [file join $path $progName]] } {
                    set logCmd [file join $path $progName]
                    break
                }
            }
        } else {
            set logCmd [auto_execok $progName]
        }
        if { $logCmd eq "" } {
            ErrorAppend "Cannot find $progName" "Warning"
        }
        return $logCmd
    }

    proc StartBawtLogViewerProg { logFile } {
        set logProg [GetBawtLogViewerProg]
        if { $logProg eq "" } {
            return
        }
        if { [IsDarwin] } {
            eval exec open -n -a $logProg --args --nosaveonexit $logFile &
        } else {
            eval exec $logProg --nosaveonexit $logFile &
        }
    }
}

namespace eval BawtZip {

    namespace export Get7ZipDistDir
    namespace export Get7ZipProg
    namespace export GetZipDistDir
    namespace export GetZipProg
    namespace export Unzip
    namespace export TarGzip
    namespace export Bootstrap

    # Code for _Vfs* Zip procedures taken from http://wiki.tcl-lang.org/36689
    proc _VfsUnzip { zname to } {
        set zfile [file normalize ${zname}]
        if { ! [file readable ${zfile}] } {
            ErrorAppend "Cannot read Zip file $zname" "FATAL"
        }
        
        if { ![file isdirectory ${to}] } {
            file mkdir ${to}
        }
        
        set items [_VfsZipStat ${zname}]
        
        foreach item [dict keys ${items}] {
            set target [file join ${to} ${item}]
            set type [dict get ${items} ${item} type]
            if { ${type} eq "directory" } {
                file mkdir ${target}
            } else {
                _VfsZipCopy ${zname} ${item} ${to}
            }
        }
    }

    proc _VfsZipCopy { zname path to } {
        set zmount [file normalize ${zname}]
        set from [file join ${zmount} ${path}]
        set to [file normalize [file join ${to} ${path}]]
        file mkdir [file dirname ${to}]
        set zid [vfs::zip::Mount ${zmount} ${zmount}]
        file copy ${from} ${to}
        set sdict [::vfs::zip::stat ${zid} ${path}]
        set mode [dict get ${sdict} mode]
        set mtime [dict get ${sdict} mtime]
        set atime [dict get ${sdict} atime]
        if { $::tcl_platform(platform) eq "unix" } {
            file attributes ${to} -permissions ${mode}
        }
        file mtime ${to} ${mtime}
        file atime ${to} ${atime}
        ::vfs::zip::Unmount ${zid} ${zmount}
    }

    proc _VfsZipStat { zname } {
        set zfile [file normalize ${zname}]
        set fd [::zip::open ${zfile}]
        set items [dict create]
        foreach item [lsort [array names ::zip::$fd.toc]] {
            ::zip::stat ${fd} ${item} stat
            if { $stat(name) ne "" && $stat(ctime) > 0 } {
                set vdict [dict create {*}[array get stat]]
                set name $stat(name)
                dict unset vdict name
                if { [string index ${name} end] eq "/" } {
                    dict set vdict type directory
                    set name [string trimright ${name} "/"]
                }
                dict set items ${name} ${vdict}
            }
        }
        ::zip::_close ${fd}
        return ${items}
    }

    proc Get7ZipDistDir {} {
        return [file join [GetOutputDevDir] "opt" "7-Zip"]
    }

    proc GetZipDistDir {} {
        return [Get7ZipDistDir]
    }

    proc Get7ZipProg { { exitOnError true } } {
        set progName "7z[GetExeSuffix]"
        set zipCmd [auto_execok $progName]
        if { [IsWindows] } {
            if { $zipCmd eq "" } {
                set bawtZipProg [file join [Get7ZipDistDir] $progName]
                if { [file exists $bawtZipProg] } {
                    set zipCmd $bawtZipProg
                }
            }
        } else {
            if { $zipCmd eq "" } {
                foreach zipName [list "7za" "7zr" "7zz"] {
                    set zipCmd [auto_execok $zipName]
                    if { $zipCmd ne "" } {
                        break
                    }
                }
                if { $zipCmd eq "" } {
                    set bawtZipProg [file join [Get7ZipDistDir] "7z"]
                    if { [file exists $bawtZipProg] } {
                        set zipCmd $bawtZipProg
                    }
                }
            }
        }
        if { $zipCmd eq "" && $exitOnError } {
            ErrorAppend "Cannot find 7-Zip program" "FATAL"
        }
        return $zipCmd
    }

    proc GetZipProg { { exitOnError true } } {
        set progName "zip[GetExeSuffix]"
        set zipCmd ""
        if { ! [IsWindows] } {
            # Always use the BAWT supplied zip program on Windows, as there
            # are zip programs around which do not work correctly when 
            # building Tcl/Tk 9.0. 
            set zipCmd [auto_execok $progName]
        }
        if { $zipCmd eq "" } {
            set bawtZipProg [file join [GetZipDistDir] $progName]
            if { [file exists $bawtZipProg] } {
                set zipCmd $bawtZipProg
            }
        }
        if { $zipCmd eq "" && $exitOnError } {
            ErrorAppend "Cannot find zip program" "FATAL"
        }
        return $zipCmd
    }

    proc Unzip { zipFile dir { zipTool "7Zip" } } {
        if { $zipTool eq "7Zip" } {
            set zipProg [Get7ZipProg]
            exec -ignorestderr {*}$zipProg x $zipFile -y -bd -o[file nativename $dir]
        } else {
            package require vfs::zip
            _VfsUnzip $zipFile $dir
        }
    }

    proc TarGzip { tarFile dir } {
        Log "TarGzip" 2
        Log "Source directory: $dir"     4 false
        Log "Tar file        : $tarFile" 4 false
        set rootDir [file dirname $dir]
        set cwd [pwd]
        cd $rootDir
        exec tar czf $tarFile [file tail $dir]
        cd $cwd
    }

    # Bootstrapping needed for Windows and Darwin.
    # Windows needs 7-Zip and the MSys/MinGW package.
    # Darwin needs 7-Zip.
    proc Bootstrap {} {
        Log "Bootstrap"

        if { ! [file isdirectory [GetBootstrapDir]] } {
            ErrorAppend "Bootstrap directory [GetBootstrapDir] not existent." "FATAL"
        }

        # First check, if bootstrap tool 7-Zip is either available on the system
        # or is in the Bootstrap directory as compressed file.
        # Note:
        # The 7-Zip distribution must be compressed with standard ZIP, so that
        # it can be extracted with the vfs::zip package contained in the tclkit. 
        # All other tools and libraries are compressed in 7-Zip format because of
        # better compression rates (Example: MSys/MinGW is 2 times smaller with 7z).
        set zipProg [Get7ZipProg false]
        set haveZipProg [expr { $zipProg ne "" ? true : false }]
        if { $haveZipProg } {
            Log "7-Zip available: $zipProg" 2 false
        } else {
            if { [IsLinux] && [IsArm] } {
                ErrorAppend "No 7-zip for Linux ARM supported by BAWT. Please install 7-zip separately." "FATAL"
            }
            if { [IsRiscV] } {
                ErrorAppend "No 7-zip for RiscV supported by BAWT. Please install 7-zip separately." "FATAL"
            }
            if { [IsSunOS] } {
                ErrorAppend "No 7-zip for SunOS supported by BAWT. Please install 7-zip separately." "FATAL"
            }
            set zipFile [file join [GetBootstrapDir] "7-Zip.zip"]
            if { ! [file exists $zipFile] } {
                ErrorAppend "Cannot find 7-Zip program" "FATAL"
            }
            Log "Extract 7-Zip" 2 false
            Log "Source file     : $zipFile"         4 false
            Log "Target directory: [Get7ZipDistDir]" 4 false
            Unzip $zipFile [file dirname [Get7ZipDistDir]] "vfs"
            SetFilePermissions [file join [Get7ZipDistDir] "7z"] "u+rwx"
        }

        # Tcl 8.7 needs the zip command line utility. 
        # Check, if it is either available on the system or is in
        # the Bootstrap directory as compressed file.
        set zipProg [GetZipProg false]
        set haveZipProg [expr { $zipProg ne "" ? true : false }]
        if { $haveZipProg } {
            Log "zip available  : $zipProg" 2 false
        } else {
            if { [IsArm] } {
                ErrorAppend "No zip for ARM supported by BAWT. Please install zip separately." "FATAL"
            }
            if { [IsRiscV] } {
                ErrorAppend "No zip for RiscV supported by BAWT. Please install zip separately." "FATAL"
            }
            set zipFile [file join [GetBootstrapDir] "zip.zip"]
            if { [file exists $zipFile] } {
                Log "Extract zip" 2 false
                Log "Source file     : $zipFile" 4 false
                Log "Target directory: [GetZipDistDir]"  4 false
                Unzip $zipFile [GetZipDistDir] "vfs"
                if { [IsLinux] } {
                    SetFilePermissions [GetZipDistDir] "u+rwx" true
                    file copy [file join [GetZipDistDir] "zip[GetBits]"] [file join [GetZipDistDir] "zip"]
                }
                SetFilePermissions [file join [GetZipDistDir] "zip"] "u+rwx"
            }
        }

        # Now check, if bootstrap tool MSys/MinGW is already extracted in the correct
        # architecture and gcc version.
        # Note:
        # This step is only needed for Windows, as on Unix systems we assume a development
        # package to be installed, ex. XCode for Mac, C/C++ development package for Linux.
        if { [IsWindows] } {
            set msysDistDir [file join [GetOutputToolsDir] [GetMingwDir]]
            if { ! [file isdirectory $msysDistDir] } {
                set toolList [lsort [glob -nocomplain [GetBootstrapDir]/gcc*]]
                set haveMSys false
                foreach zipFile $toolList {
                    if { [file rootname [file tail $zipFile]] eq [GetMingwDir] } {
                        set fileExt [string tolower [file extension $zipFile]]
                        if { $fileExt eq ".7z" || $fileExt eq ".zip" } {
                            Log "Extract MSys/MinGW" 2 false
                            Log "Source file     : $zipFile"     4 false
                            Log "Target directory: $msysDistDir" 4 false
                            if { ! [file isdirectory [GetOutputToolsDir]] } {
                                file mkdir [GetOutputToolsDir]
                            }
                            Unzip $zipFile [GetOutputToolsDir]

                            # Adjust MinGw/MSys pathes in the /etc/fstab file.
                            if { [HaveMSys2] } {
                                set fstabFile [file join [GetMSysDir 2] "etc" "fstab"]
                                Log "Adjust MSYS2 fstab file $fstabFile" 4 false
                                set fp [open $fstabFile "a"]
                                fconfigure $fp -translation "lf"
                                puts $fp "[file join $msysDistDir [GetMingwSubDir]] /mingw"
                                close $fp
                            }
                            if { [HaveMSys1] } {
                                set fstabFile [file join [GetMSysDir 1] "etc" "fstab"]
                                Log "Adjust MSYS1 fstab file $fstabFile" 4 false
                                set fp [open $fstabFile "w"]
                                fconfigure $fp -translation "lf"
                                puts $fp "# Win32_Path    Mount_Point"
                                puts $fp "[file join $msysDistDir [GetMingwSubDir]] /mingw"
                                close $fp
                            }

                            if { [HaveMSys2] } {
                                # Add /mingw to PATH.
                                set profileFile [file join [GetMSysDir 2] "etc" "profile"]
                                Log "Adjust MSYS2 profile file $profileFile" 4 false
                                set fp [open $profileFile "a"]
                                fconfigure $fp -translation "lf"
                                puts $fp "export PATH=\"/mingw/bin:\$PATH\""
                                close $fp
                            }
                            if { [HaveMSys1] } {
                                # Comment out "cd $HOME" in the /etc/profile file.
                                set profileFile [file join [GetMSysDir 1] "etc" "profile"]
                                Log "Adjust MSYS1 profile file $profileFile" 4 false
                                ReplaceLine $profileFile "cd \"\$HOME\"" ""
                            }
                            set haveMSys true
                        }
                    }
                }
                if { ! $haveMSys } {
                    ErrorAppend "Cannot find MSys/MinGW distribution file at [GetMingwDir]" "FATAL"
                }
            }
            if { [file isdirectory $msysDistDir] } {
                Log "MinGW available: $msysDistDir"     2 false
            }
            if { [file exists [GetMSysConsole]] } {
                Log "MSys available : [GetMSysConsole]" 2 false
            } else {
                ErrorAppend "Cannot find MSys console at [GetMSysConsole]" "FATAL"
            }
        }
    }
}

namespace eval BawtFile {

    namespace export SetFilePermissions
    namespace export GetCurlProg GetMd5Prog GetMakeProg
    namespace export SetCurlCertificate GetCurlCertificate
    namespace export CreateBackupFile
    namespace export DownloadFile
    namespace export GetHomeDir
    namespace export GetMSysDir GetMSysBinDir
    namespace export SetMSysVersion
    namespace export GetMSysConsole
    namespace export MSysPath HaveMSys1 HaveMSys2 UseMSys2
    namespace export CheckMatchList
    namespace export GetDirList
    namespace export StripRecursive
    namespace export StripLibraries
    namespace export SignExecutables
    namespace export CopyRecursive
    namespace export DirCopy
    namespace export DirDelete
    namespace export DirCreate
    namespace export DirTouch
    namespace export SingleFileCopy
    namespace export MultiFileCopy
    namespace export LibFileCopy
    namespace export CopySysIncludeFiles
    namespace export FileRename
    namespace export FindFile
    namespace export AddToFile WriteToFile
    namespace export ReplaceKeywords ReplaceLine
    namespace export ReplaceKeywordsRecursive
    namespace export FixSunOSCpAndFind

    proc SetFilePermissions { fileName permissions { recursiveFlag false } } {
        if { [IsUnix] } {
            if { $recursiveFlag } {
                exec chmod -R $permissions $fileName
            } else {
                exec chmod $permissions $fileName
            }
        }
    }

    proc SetMSysVersion { msysVersion } {
        variable sMSysVersion

        set sMSysVersion $msysVersion
    }

    proc UseMSys2 {} {
        variable sMSysVersion

        set retVal false

        if { [info exists sMSysVersion] } {
            if { $sMSysVersion == 2 && ! [HaveMSys2] } {
                ErrorAppend "Requested MSYS version 2 is not available." "FATAL"
            }
            if { $sMSysVersion == 2 } {
                set retVal true
            }
        } else {
            if { [HaveMSys2] } {
                set retVal true
            }
        }
        return $retVal
    }

    proc HaveMSys1 {} {
        set dir [file join [GetOutputToolsDir] [GetMingwDir] "msys"]
        if { [file isdirectory $dir] } {
            return true
        } else {
            return false
        }
    }

    proc HaveMSys2 {} {
        set dir [file join [GetOutputToolsDir] [GetMingwDir] "msys32"]
        if { [file isdirectory $dir] } {
            return true
        } else {
            return false
        }
    }

    proc GetHomeDir {} {
        if { [package vcompare "9.0" [info tclversion]] <= 0 } {
            set homeDir [file tildeexpand "~"]
        } else {
            set homeDir "~"
        }
        return $homeDir
    }

    proc GetMSysDir { { msysVersion -1 } } {
        set msys1Dir [file join [GetOutputToolsDir] [GetMingwDir] "msys"]
        set msys2Dir [file join [GetOutputToolsDir] [GetMingwDir] "msys32"]
        if { $msysVersion == 1 } {
            return $msys1Dir
        } elseif { $msysVersion == 2 } {
            return $msys2Dir
        } else {
            if { [UseMSys2] } {
                return $msys2Dir
            } else {
                return $msys1Dir
            }
        }
    }
  
    proc GetMSysBinDir {} {
        if { [UseMSys2] } {
            return [file join [GetMSysDir] "usr" "bin"]
        } else {
            return [file join [GetMSysDir] "bin"]
        }
    }

    proc GetMSysConsole {} {
        if { [UseMSys2] } {
            set msys [file join [GetMSysDir] "mingw32.exe"]
            #set msys [file join [GetMSysDir] "mingw[GetBits].exe"]
        } else {
            set msys [file join [GetMSysDir] "msys.bat"]
        }
        return $msys
    }

    proc GetCurlProg { { exitOnError true } } {
        set progName "curl[GetExeSuffix]"
        if { [IsWindows] } {
            set curlPath [file join [GetMSysBinDir] $progName]
            if { [file exists $curlPath] } {
                return $curlPath
            }
        }
        set curlCmd [auto_execok $progName]
        if { $curlCmd eq "" && $exitOnError } {
            ErrorAppend "Cannot find curl program" "FATAL"
        }
        return $curlCmd
    }

    proc SetCurlCertificate { fileName } {
        variable sCurlCertificate

        set sCurlCertificate [file normalize $fileName]
    }

    proc GetCurlCertificate {} {
        variable sCurlCertificate

        if { [info exists sCurlCertificate] } {
            return $sCurlCertificate
        } else {
            return ""
        }
    }

    proc GetMd5Prog { { exitOnError true } } {
        set progName "md5sum[GetExeSuffix]"
        if { [IsDarwin] } {
            set progName "md5"
        }
        set md5Cmd [auto_execok $progName]
        if { $md5Cmd eq "" } {
            if { [IsWindows] } {
                set md5Path [file join [GetMSysBinDir] $progName]
                if { [file exists $md5Path] } {
                    return $md5Path
                }
            }
        }
        if { $md5Cmd eq "" && $exitOnError } {
            ErrorAppend "Cannot find md5sum/md5 program" "FATAL"
        }
        return $md5Cmd
    }

    proc GetMakeProg {} {
        if { [IsSunOS] } {
            return "gmake"
        } else {
            return "make"
        }
    }

    proc CreateBackupFile { fileName { useLog true } } {
        set backupFile [format "%s.bak" $fileName]
        if { [file exists $fileName] } {
            if { $useLog } {
                Log "Backup file: $backupFile" 4 false
            }
            file delete -force $backupFile
            file rename $fileName $backupFile
        }
    }

    proc _CheckMd5Hash { libName fileName { errorType "FATAL" } } {
        set md5Prog [GetMd5Prog]
        set hashKeyRepo [GetHashKey $libName [file tail $fileName]]
        if { [IsDarwin] } {
            set hashKeyLine [eval exec $md5Prog -q $fileName]
        } else {
            set hashKeyLine [eval exec $md5Prog $fileName]
        }
        set hashKeyFile [string trim [lindex [split $hashKeyLine] 0]]

        if { $hashKeyRepo eq "" } {
            ErrorAppend "No hash key for file $fileName available." $errorType
        }
        if { $hashKeyRepo ne $hashKeyFile } {
            ErrorAppend "Hash keys of file $fileName differ." $errorType
        }
        Log "Hash key   : $hashKeyFile"  4 false
    }

    proc DownloadFile { libName subDir fileName outFile { errorType "FATAL" } } {
        set curlProg [GetCurlProg]
        set sourceFileUrl [format "%s/%s/%s" [GetBawtUrl] $subDir $fileName]
        set targetFile    [MSysPath $outFile]
        if { ! [file isdirectory [file dirname $outFile]] } {
            file mkdir [file dirname $outFile]
        }

        Log "DownloadFile" 2
        Log "Source file: $sourceFileUrl" 4 false
        Log "Target file: $targetFile"    4 false

        CreateBackupFile $outFile

        set cacert ""
        if { [GetCurlCertificate] ne "" } {
            set cacert "-cacert [GetCurlCertificate] "
        }
        set cmd "$curlProg $cacert -I $sourceFileUrl"
        set result [MSysRun $libName "DownloadFile" "" "$cmd"]
        if { [string match -nocase "*404 Not Found*" $result] } {
            ErrorAppend "File $sourceFileUrl not existent." $errorType
            return
        }

        set cmd "$curlProg $cacert -s -o $targetFile $sourceFileUrl"
        MSysRun $libName "DownloadFile" "" "$cmd"

        if { $errorType eq "FATAL" } {
            _CheckMd5Hash $libName $outFile
        }
    }

    proc MSysPath { path } {
        if { [IsWindows] } {
            # Convert C:/Dev and C:\Dev to /C/Dev and /C/Dev
            if { [string first "/" $path] == 0 } {
                return $path
            } else {
                return [format "/%s" [string map { "\\" "/" ":" "" } $path]]
            }
        } else {
            return $path
        }
    }

    proc CheckMatchList { searchString matchList { ignCase false } } {
        # Compare a string against a list of glob-style patterns.
        #
        # searchString - String to be compared.
        # matchList    - List of glob-style patterns.
        # ignCase      - Ignore case when matching. 
        #
        # Return true, if the searchString matches a pattern in the matchList.
        # Otherwise return false.

        if { $ignCase } {
            set matchCmd "string match -nocase"
        } else {
            set matchCmd "string match"
        }
        foreach matchString $matchList {
            if { [eval $matchCmd {$matchString $searchString}] } {
                return true
            }
        }
        return false
    }

    proc GetDirList {dirName {showDirs 1} {showFiles 1} {showHiddenDirs 1} {showHiddenFiles 1} {dirPattern *} {filePattern *}} {
        set curDir [pwd]
        set catchVal [catch {cd $dirName}]
        if { $catchVal } {
            return [list]
        }

        set absDirList  [list]
        set relFileList [list]

        if { $showDirs } {
            set relDirList [glob -nocomplain -types d -- {*}$dirPattern]
            foreach dir $relDirList {
                if { [string index $dir 0] eq "~" } {
                    set dir [format "./%s" $dir]
                }
                set absName [file join $dirName $dir]
                lappend absDirList $absName
            }
            if { $showHiddenDirs } {
                set relHiddenDirList \
                    [glob -nocomplain -types {d hidden} -- {*}$dirPattern]
                foreach dir $relHiddenDirList {
                    if { $dir eq "." || $dir eq ".." } {
                        continue
                    }
                    set absName [file join $dirName $dir]
                    lappend absDirList $absName
                }
            }
        }
        if { $showFiles } {
            set relFileList [glob -nocomplain -types f -- {*}$filePattern]
            if { $showHiddenFiles } {
                set relHiddenFileList \
                    [glob -nocomplain -types {f hidden} -- {*}$filePattern]
                if { [llength $relHiddenFileList] != 0 } {
                    set relFileList [concat $relFileList $relHiddenFileList]
                }
            }
        }
        cd $curDir

        return [list $absDirList $relFileList]
    }

    proc StripRecursive { srcDir pattern } {
        set retVal [catch { cd $srcDir } ]
        if { $retVal } {
            ErrorAppend "Could not read directory \"$srcDir\"" "FATAL"
            return
        }
        set dirCont [GetDirList $srcDir 1 1  1 1]
        set dirList  [lindex $dirCont 0]
        set fileList [lindex $dirCont 1]
        foreach dir $dirList {
            set dirName [file tail $dir]
            set subSrcDir  [file join $srcDir  $dirName]
            StripRecursive $subSrcDir $pattern
        }
        foreach fileName $fileList {
            if { [string first "~" $fileName] == 0 } {
                # File starts with tilde.
                set fileName [format "./%s" $fileName]
            }
            set fileAbs [file join $srcDir $fileName]
            if { [CheckMatchList [file tail $fileAbs] $pattern false] } {
                set fileAbsMSys [MSysPath $fileAbs]
                set cmd "chmod u+rwx $fileAbsMSys ; "
                if { [IsDarwin] } {
                    append cmd "strip -x $fileAbsMSys"
                } else {
                    append cmd "strip $fileAbsMSys"
                }
                MSysRun "Strip" "Strip" "" "$cmd"
            }
        }
    }

    proc StripLibraries { { dir "" } } {
        if { ! [StripLibs] || [IsDebugBuild] } {
            return
        }
        set pattern [GetLibPattern]
        if { $dir eq "" } {
            set dir [GetOutputDistDir]
        }
        StripRecursive $dir $pattern
    }

    proc _SignExecutable { appName certFile timeStampUrl } {
        Log "Executable        : $appName" 4 false

        set timeStampUrlString ""
        if { $timeStampUrl ne "" } {
            set timeStampUrlString "/t $timeStampUrl"
        }
        set cmd ""
        append cmd "CALL signtool.exe sign "
        append cmd     "/fd SHA256 "
        append cmd     "/f [file nativename $certFile] "
        append cmd     "$timeStampUrlString "
        append cmd     "[file nativename $appName] "

        DosRun "Sign" "SignExecutables" [file dirname $appName] "$cmd" -nolog
    }

    proc _SignRecursive { srcDir pattern certFile timeStampUrl } {
        variable sSignCount

        set retVal [catch { cd $srcDir } ]
        if { $retVal } {
            ErrorAppend "Could not read directory \"$srcDir\"" "FATAL"
            return
        }
        set dirCont [GetDirList $srcDir 1 1  1 1]
        set dirList  [lindex $dirCont 0]
        set fileList [lindex $dirCont 1]
        foreach dir $dirList {
            set dirName [file tail $dir]
            set subSrcDir  [file join $srcDir  $dirName]
            _SignRecursive $subSrcDir $pattern $certFile $timeStampUrl
        }
        foreach fileName $fileList {
            if { [string first "~" $fileName] == 0 } {
                # File starts with tilde.
                set fileName [format "./%s" $fileName]
            }
            set fileAbs [file join $srcDir $fileName]
            if { [CheckMatchList [file tail $fileAbs] $pattern false] } {
                _SignExecutable $fileAbs $certFile $timeStampUrl
                incr sSignCount
            }
        }
    }

    proc SignExecutables { dir { pattern "*.exe" } { warnNoFilesSigned false } } {
        variable sSignCount

        if { ! [IsWindows] } {
            return
        }
        if { [GetTclkitCertFile All] eq "" } {
            return
        }
        # If Windows, but not using VisualStudio, print out a warning.
        if { ! [UseVisualStudio] } {
            ErrorAppend "SignExecutables: Code signing needs VisualStudio." "Warning"
            return
        }
        set certFile [GetTclkitCertFile All]
        if { ! [file exists $certFile] } {
            ErrorAppend "SignExecutables: Certification file $certFile not found." "FATAL"
        }
        set timeStampUrl [GetTclkitTimeStampUrl All]

        Log "SignExecutables" 2
        Log "Directory         : $dir"          4 false
        Log "Certification file: $certFile"     4 false
        Log "Timestamp server  : $timeStampUrl" 4 false

        set curDir [pwd]
        set sSignCount 0
        _SignRecursive $dir $pattern $certFile $timeStampUrl
        cd $curDir
        if { $warnNoFilesSigned && $sSignCount == 0 } {
            ErrorAppend "SignExecutables: No files signed in $dir." "Warning"
        } else {
            Log "Number of signed files: $sSignCount" 4 false
        }
    }

    proc CopyRecursive { srcDir destDir keepFolders pattern ignDirPattern } {
        variable sCopyCount

        set retVal [catch { cd $srcDir } ]
        if { $retVal } {
            ErrorAppend "Could not read directory \"$srcDir\"" "FATAL"
            return
        }
        set dirCont [GetDirList $srcDir 1 1  1 1]
        set dirList  [lindex $dirCont 0]
        set fileList [lindex $dirCont 1]
        foreach dir $dirList {
            set dirName [file tail $dir]
            if { [CheckMatchList $dirName $ignDirPattern false] } {
                continue
            }
            set subSrcDir [file join $srcDir $dirName]
            if { $keepFolders } {
                set subDestDir [file join $destDir $dirName]
            } else {
                set subDestDir $destDir
            }
            CopyRecursive $subSrcDir $subDestDir $keepFolders $pattern $ignDirPattern
        }
        foreach fileName $fileList {
            if { [string first "~" $fileName] == 0 } {
                # File starts with tilde.
                set fileName [format "./%s" $fileName]
            }
            set fileAbs [file join $srcDir $fileName]
            if { [CheckMatchList [file tail $fileAbs] $pattern false] } {
                Log "Copy $fileAbs" 6 false
                if { ! [file isdirectory $destDir] } {
                    file mkdir $destDir
                }
                file copy -force $fileAbs $destDir
                incr sCopyCount
            }
        }
    }

    proc DirDelete { dir } {
        Log "DirDelete" 2
        Log "Directory: $dir" 4 false

        set count  0
        set retVal 1
        set maxTime [GetTimeout]
        while { $retVal != 0 && $count <= $maxTime } {
            set retVal [catch {file delete -force $dir} errMsg]
            if { $retVal == 0 } {
                break
            }
            Log "$errMsg  ... retrying" 6 false
            incr count 1000
            after 1000
        }
        if { $retVal != 0 } {
            ErrorAppend $errMsg "FATAL"
        }
    }

    proc DirCreate { dir } {
        if { [file isdirectory $dir] } {
            return
        }

        Log "DirCreate" 2 false
        Log "Directory: $dir" 4 false

        set count  0
        set retVal 1
        set maxTime [GetTimeout]
        while { $retVal != 0 && $count <= $maxTime } {
            set retVal [catch {file mkdir $dir} errMsg]
            if { $retVal == 0 } {
                break
            }
            Log "$errMsg  ... retrying" 6 false
            incr count 1000
            after 1000
        }
        if { $retVal != 0 } {
            ErrorAppend $errMsg "FATAL"
        }
    }

    proc DirTouch { dir } {
        if { ! [file isdirectory $dir] } {
            return
        }

        Log "DirTouch" 2 false
        Log "Directory: $dir" 4 false

        set count  0
        set retVal 1
        set maxTime [GetTimeout]
        while { $retVal != 0 && $count <= $maxTime } {
            set touchTime [clock seconds]
            set retVal [catch {file mtime $dir $touchTime} errMsg]
            if { $retVal == 0 } {
                break
            }
            Log "$errMsg  ... retrying" 6 false
            incr count 1000
            after 1000
        }
        if { $retVal != 0 } {
            ErrorAppend $errMsg "FATAL"
        }
    }

    proc DirCopy { sourceDir targetDir } {
        Log "DirCopy" 2
        Log "Source directory: $sourceDir" 4 false
        Log "Target directory: $targetDir" 4 false

        if { ! [file isdirectory [file dirname $targetDir]] } {
            file mkdir [file dirname $targetDir]
        }
        if { ! [file isdirectory $sourceDir] } {
            ErrorAppend "Directory $sourceDir does not exist." "FATAL"
        } else {
            file copy -force $sourceDir $targetDir
        }
    }

    proc SingleFileCopy { sourceFile targetDir args } {
        set opts [dict create \
            -mustexist true   \
            -rename    ""     \
        ]
        if { [llength $args] == 1 } {
            # Old mode with optional string parameter newName
            dict set opts -rename [lindex $args 0]
        } else {
            foreach { key value } $args {
                if { [dict exists $opts $key] } {
                    if { $value eq "" } {
                        ErrorAppend "No value specified for key \"$key\"." "FATAL"
                    }
                    dict set opts $key $value
                } else {
                    ErrorAppend "Unknown option key \"$key\"." "FATAL"
                }
            }
        }
        set newName [dict get $opts "-rename"]
        Log "SingleFileCopy" 2
        Log "Source file     : $sourceFile" 4 false
        Log "Target directory: $targetDir"  4 false
        if { $newName ne "" } {
            Log "New name        : $newName"  4 false
        } else {
            set newName [file tail $sourceFile]
        }
        Log "Must exist      : [dict get $opts "-mustexist"]"  4 false

        if { ! [file exists $sourceFile] } {
            if { [dict get $opts "-mustexist"] } {
                ErrorAppend "File $sourceFile does not exist." "FATAL"
            }
        } else {
            if { ! [file isdirectory $targetDir] } {
                file mkdir $targetDir
            }
            file copy -force $sourceFile [file join $targetDir $newName]
        }
    }

    proc MultiFileCopy { sourceDir targetDir { pattern "*" } { keepFolders false } { warnNoFilesCopied true } } {
        variable sCopyCount

        Log "MultiFileCopy" 2
        Log "Source directory: $sourceDir" 4 false
        Log "Target directory: $targetDir" 4 false
        Log "File pattern    : $pattern"   4 false

        set curDir [pwd]
        set sCopyCount 0
        CopyRecursive $sourceDir $targetDir $keepFolders $pattern ".svn"
        cd $curDir
        if { $warnNoFilesCopied && $sCopyCount == 0 } {
            ErrorAppend "MultiFileCopy: No files copied from $sourceDir." "Warning"
        } else {
            Log "Number of copied files: $sCopyCount" 4 false
        }
        return $sCopyCount
    }

    proc LibFileCopy { sourceDir targetDir { pattern "*" } { keepFolders false } } {
        Log "LibFileCopy" 2

        set sourceLibDir1 [file join $sourceDir "lib"]
        set sourceLibDir2 [file join $sourceDir "lib64"]
        set targetLibDir  [file join $targetDir "lib"]

        if { ! [file isdirectory $sourceLibDir1] && ! [file isdirectory $sourceLibDir2] } {
            set errMsg    "No source library directory available.\n"
            append errMsg "Neither \"$sourceLibDir1\" nor \"$sourceLibDir2\""
            ErrorAppend $errMsg "FATAL"
        }
        set numFilesCopied1 0
        set numFilesCopied2 0
        if { [file isdirectory $sourceLibDir1] } {
            set numFilesCopied1 [MultiFileCopy $sourceLibDir1 $targetLibDir $pattern $keepFolders false]
        }
        if { [file isdirectory $sourceLibDir2] } {
            set numFilesCopied2 [MultiFileCopy $sourceLibDir2 $targetLibDir $pattern $keepFolders false]
        }
        if { $numFilesCopied1 == 0 && $numFilesCopied2 == 0 } {
            ErrorAppend "LibFileCopy: No files copied from \"$sourceLibDir1\" or \"$sourceLibDir2\"." "Warning"
        }
    }

    proc CopySysIncludeFiles { libName targetDir } {
        # Need system libraries in VisualStudio format because of dependencies on other libraries,
        # which were compiled with MSys.
        if { [UseWinCompiler $libName "vs"] } {
            foreach includeFile [list "stdbool.h" "stdint.h" "inttypes.h"] {
                SingleFileCopy [file join [GetInputResourceDir] "VisualStudio" $includeFile] "$targetDir"
            }
        }
    }

    proc FileRename { source target } {
        Log "FileRename" 2
        Log "Source: $source" 4 false
        Log "Target: $target" 4 false

        if { [file normalize $source] eq [file normalize $target] } {
            ErrorAppend "Source and target are identical. No action." "Warning"
            return
        }

        file delete -force $target

        set count  0
        set retVal 1
        set maxTime [GetTimeout]
        while { $retVal != 0 && $count <= $maxTime } {
            set retVal [catch {file rename $source $target} errMsg]
            if { $retVal == 0 } {
                break
            }
            Log "$errMsg  ... retrying" 6 false
            incr count 1000
            after 1000
        }
        if { $retVal != 0 } {
            ErrorAppend $errMsg "FATAL"
        }
    }

    proc FindFile { dir filePattern { exitOnError true } } {
        set fullPath [file join $dir $filePattern]
        set fileList [glob -nocomplain $fullPath]
        if { [llength $fileList] == 0 } {
            if { $exitOnError } {
                ErrorAppend "FindFile: No file matching $filePattern in directory $dir" "FATAL"
            } else {
                return ""
            }
        }
        if { [llength $fileList] > 1 } {
            ErrorAppend "More than 1 file matching $filePattern in directory $dir" "Warning"
        }
        return [file tail [lindex $fileList 0]]
    }

    proc AddToFile { fileName string { where "top" } } {
        Log "AddToFile" 2
        Log "File  : $fileName" 4 false
        Log "String: [string range $string 0 20] ..." 4 false

        set retVal [catch {open $fileName "r"} fp]
        if { $retVal != 0 } {
            ErrorAppend "AddToFile: Cannot read from file $fileName" "FATAL"
            return
        }
        set fileContent [read $fp]
        close $fp

        if { $where eq "top" } {
            set final $string
            append final $fileContent
        } else {
            set final $fileContent
            append final $string
        }

        set retVal [catch {open $fileName "w"} fp]
        if { $retVal != 0 } {
            ErrorAppend "AddToFile: Cannot write to file $fileName" "FATAL"
            return
        }
        puts $fp $final
        close $fp
    }

    proc WriteToFile { fileName string { mode "w" } } {
        set retVal [catch {open $fileName $mode} outFp]
        if { $retVal != 0 } {
            ErrorAppend "Cannot write to file $fileName" "FATAL"
        } else {
            puts -nonewline $outFp $string
            close $outFp
        }
    }

    proc _QuoteRegexpChars { str } {
        # Quote all regexp special chars: "^$*+.?()|[]\"

        regsub -all -- {\^|\$|\*|\+|\.|\?|\(|\)|\||\[|\]|\\} $str {\\&} tmpStr
        return $tmpStr
    }

    proc ReplaceKeywords { fileName replaceList } {
        Log "ReplaceKeywords" 2
        Log "File   : $fileName" 4 false

        set retVal [catch {open $fileName "r"} fp]
        if { $retVal != 0 } {
            ErrorAppend "ReplaceKeywords: Cannot read from file $fileName" "FATAL"
            return
        }
        set fileContent [read $fp]
        close $fp

        foreach { key val } $replaceList {
            Log "Replace: $key -> $val" 4 false
            set searchStr [_QuoteRegexpChars $key]
            set num [regsub -all -- $searchStr $fileContent $val fileContent]
            if { $num == 0 } {
                ErrorAppend "ReplaceKeywords \"$key\": No keywords replaced." "Warning"
            }
        }

        set retVal [catch {open $fileName "w"} fp]
        if { $retVal != 0 } {
            ErrorAppend "ReplaceKeywords: Cannot write to file $fileName" "FATAL"
            return
        }
        puts $fp $fileContent
        close $fp
    }

    proc ReplaceKeywordsRecursive { rootDir filePattern replaceList } {
        set retVal [catch { cd $rootDir } ]
        if { $retVal } {
            ErrorAppend "Could not read directory \"$rootDir\"" "FATAL"
            return
        }
        set dirCont [GetDirList $rootDir 1 1  1 1]
        set dirList  [lindex $dirCont 0]
        set fileList [lindex $dirCont 1]
        foreach dir $dirList {
            set dirName [file tail $dir]
            set subSrcDir  [file join $rootDir  $dirName]
            ReplaceKeywordsRecursive $subSrcDir $filePattern $replaceList
        }
        foreach fileName $fileList {
            if { [string first "~" $fileName] == 0 } {
                # File starts with tilde.
                set fileName [format "./%s" $fileName]
            }
            set fileAbs [file join $rootDir $fileName]
            if { [CheckMatchList [file tail $fileAbs] $filePattern false] } {
                ReplaceKeywords $fileAbs $replaceList
            }
        }
    }

    proc ReplaceLine { fileName searchLine replaceLine } {
        Log "ReplaceLine" 2
        Log "File   : $fileName"    4 false
        Log "Search : $searchLine"  4 false
        Log "Replace: $replaceLine" 4 false

        set retVal [catch {open $fileName "r"} fp]
        if { $retVal != 0 } {
            ErrorAppend "ReplaceLine: Cannot read from file $fileName" "FATAL"
            return
        }
        set fileContent [read $fp]
        close $fp

        set searchStr [_QuoteRegexpChars $searchLine]
        set num [regsub -all -- $searchStr $fileContent $replaceLine fileContent]
        Log "Number of replaced lines: $num" 4 false
        if { $num == 0 } {
            ErrorAppend "ReplaceLine \"$searchLine\": No lines replaced." "Warning"
        }

        set retVal [catch {open $fileName "w"} fp]
        if { $retVal != 0 } {
            ErrorAppend "ReplaceLine: Cannot write to file $fileName" "FATAL"
            return
        }
        puts $fp $fileContent
        close $fp
    }

    proc FixSunOSCpAndFind { args } {
        foreach fileName $args {
            if { ! [file writable $fileName] } {
                ErrorAppend "FixSunOSCpAndFind: Cannot access file $fileName" "FATAL"
                return
            }
            # SunOS find command does not have options "-empty" and "-delete".
            # So do not execute the find command at all.
            ReplaceLine $fileName "@find " "#@find "

            # SunOS cp command does not have option "-a". 
            # Replace with simple cp call.
            ReplaceKeywords $fileName [list "cp -a" "cp"]
        } 
    }
}

namespace eval BawtBuild {

    namespace export GetValidSortModes
    namespace export UseZipkit
    namespace export MakeStarpack MakeStarpackTcl MakeStarpackTk
    namespace export AddCheckOption GetCheckOptions IsSimulationMode
    namespace export AddUserConfig GetUserConfig GetUserConfigValue GetUserCFlags
    namespace export UpdateLib SetLibUpdated AnyLibsUpdated LibNeedsUpdate GetLibUpdateCause
    namespace export CheckRecursiveDependencies UseRecursiveDependencies
    namespace export EnableStage EnableStages EnableAllStages
    namespace export DisableStage DisableStages DisableAllStages
    namespace export UseStage IsBuildStage GetUsedStages
    namespace export UseTclPkgVersion ExitOnFatalError PrintSetupWarning
    namespace export UseSanitizer GetSanitizerCFlags GetSanitizerLdFlags
    namespace export UseUniversal GetUniversalCFlags GetDarwinCFlags
    namespace export SetDarwinMinVersion GetDarwinMinVersion
    namespace export SetBuildType GetBuildType GetValidBuildTypes
    namespace export SetCompilerVersion GetCompilerVersion
    namespace export SetCompilerVersions GetCompilerVersions
    namespace export GetValidCompilerVersions
    namespace export SetNumJobs GetNumJobs
    namespace export SetTimeout GetTimeout
    namespace export GetMingwVersion GetMingwGccVersion SetMingwGccVersion
    namespace export GetMingwDir GetMingwSubDir GetMingwIncludeDir
    namespace export GetMingwLib GetPthreadLib GetSehLib GetTclZlib
    namespace export HaveYasmProg HaveAsmProg
    namespace export GetGendefProg GetSWIGDistDir GetPandocDistDir
    namespace export GetCMakeDistDir GetCMakeProg GetCMakePolicyOption GetCMakeMSysOption
    namespace export GetMSysCppOption
    namespace export GetPermissiveCFlags
    namespace export GetPermissiveDarwinCFlags
    namespace export GetInnoDistDir GetInnoProg
    namespace export GetDpkgDebProg GetDpkgArchitecture GetDpkgDebOpts
    namespace export GetHdiUtilProg
    namespace export GetVcvarsProg SetVcvarsProg
    namespace export GetVSRuntimeLibDir SetVSRuntimeLibDir
    namespace export GetVSEditions
    namespace export UseVisualStudio GetVisualStudioVersion
    namespace export GetMscVer
    namespace export GetGccCompilerVersion IsGccCompilerNewer
    namespace export GetMSysShell GetCmdShell
    namespace export IsReleaseBuild IsDebugBuild 
    namespace export GetDebugSuffix GetWinDebugSuffix
    namespace export SetArchitecture GetArchitecture GetValidArchitectures
    namespace export Is32Bit Is64Bit GetBits
    namespace export IsIntel IsArm IsRiscV
    namespace export IsWindows IsLinux IsDarwin IsSunOS IsUnix
    namespace export GetMajor GetMinor GetPatch GetMajorMinor GetMajorMinorPatch
    namespace export VersionCompare VersionCheck UnifyVersionNumber
    namespace export GetPlatformName GetMsBuildPlatform
    namespace export GetExeSuffix GetBatchSuffix GetExePattern
    namespace export GetLibSuffix GetImportLibSuffix GetStaticLibSuffix GetLibPattern
    namespace export SetFinalizeFile GetFinalizeFile
    namespace export SetTclkitIconFile GetTclkitIconFile
    namespace export SetTclkitResourceFile GetTclkitResourceFile
    namespace export SetTclkitCertFile GetTclkitCertFile
    namespace export SetTclkitTimeStampUrl GetTclkitTimeStampUrl
    namespace export SetTclkitCompanyName GetTclkitCompanyName
    namespace export SetTclkitLegalCopyright GetTclkitLegalCopyright
    namespace export SetTclkitFileDescription GetTclkitFileDescription
    namespace export SetTclkitProductName GetTclkitProductName
    namespace export SetTclkitProductVersion GetTclkitProductVersion
    namespace export SetTclkitFileVersion GetTclkitFileVersion
    namespace export SetTclVersion GetTclVersion 
    namespace export SetTkVersion GetTkVersion 
    namespace export HaveTcl9 HaveTk9
    namespace export GetPythonVersion
    namespace export SetImgVersion GetImgVersion
    namespace export SetOsgVersion GetOsgVersion
    namespace export SetTlsVersion GetTlsVersion
    namespace export SetLibraryVersion GetLibraryVersion
    namespace export SetLibraryZipFile GetLibraryZipFile
    namespace export SetTclDir GetTclDir
    namespace export SetPythonDir GetPythonDir
    namespace export GetTclLibDir GetTclBinDir GetTclIncDir
    namespace export GetPythonLibDir GetPythonBinDir GetPythonIncDir
    namespace export GetDevTclDir GetDevTclLibDir GetDevTclBinDir GetDevTclIncDir
    namespace export GetDevPythonDir GetDevPythonLibDir GetDevPythonBinDir GetDevPythonIncDir
    namespace export GetTclLibName GetTkLibName
    namespace export GetTclStubLib GetTkStubLib
    namespace export GetTclshName GetTclshPath 
    namespace export GetWishName GetWishPath
    namespace export GetItclDir GetPngLibDir
    namespace export GetOutputArchDir GetOutputDevDir GetOutputTypeDir GetOutputBuildDir GetOutputInstDir 
    namespace export SetShortRootDir UseShortRootDir
    namespace export SetOutputDistDir GetOutputDistDir
    namespace export CreateDefaultDirs
    namespace export GetOtherArchDir
    namespace export CleanLib
    namespace export BuildLib
    namespace export CMakeConfig CMakeBuild
    namespace export NMakeBuild NMakeTest
    namespace export MsBuild
    namespace export MSysConfig MSysRun MSysBuild MSysTest
    namespace export TeaConfig TeaConfigTcl TeaConfigTk
    namespace export NeedDll2Lib Dll2Lib
    namespace export DosRun

    variable sBuildStages
    array set sBuildStages {
        Check,all        false
        Clean,all        false
        Extract,all      false
        Configure,all    false
        Compile,all      false
        Distribute,all   false
        Finalize,all     false
        Test,all         false
        Update,all       false
        Touch,all        false
    }
    variable sStageOrder
    set sStageOrder [list Clean Extract Configure Compile Distribute Finalize Touch Test]

    variable sBuildOpts
    array set sBuildOpts {
        Version,Tcl                "8.6.17"
        Version,Img                "2.1.0"
        Version,Osg                "3.6.5"
        Version,Tls                "2"
        TclDir                     "opt/Tcl"
        PythonDir                  "opt/Python"
        DistDir                    ""
        Tclkit,all,IconFile        ""
        Tclkit,all,ResourceFile    ""
        Tclkit,all,CertFile        ""
        Tclkit,All,TimeStampUrl    "http://timestamp.digicert.com"
        Tclkit,all,CompanyName     ""
        Tclkit,all,LegalCopyright  ""
        Tclkit,all,FileDescription ""
        Tclkit,all,ProductName     ""
        Tclkit,all,ProductVersion  ""
        Tclkit,all,FileVersion     ""
        FinalizeFile               ""
        BuildType                  "NA"
        GccVersion                 "14.2.0"
        all,NumJobs                1
        Timeout                    30000          ; # Default timeout 30 seconds.
        UseTclPkgVersion           true
        ExitOnFatalError           true
        PrintSetupWarning          true
        UseRecursiveDependencies   true
        UseShortRootDir            false
        UseSanitizer               false
        UseUniversal               false
        UseZipkit                  false
        DarwinMinVersion           "10.14"
    }

    proc GetValidSortModes {} {
        return [list "dependencies" "dictionary" "none"]
    }

    proc UseZipkit { { onOff "" } } {
        variable sBuildOpts

        if { $onOff eq "" } {
            return $sBuildOpts(UseZipkit)
        } else {
            set sBuildOpts(UseZipkit) $onOff
        }
    }

    proc _utf16le {s} {
        set s [encoding convertto unicode $s]
        if { $::tcl_platform(byteOrder) eq "bigEndian" } {
            binary scan $s S* t
            set s [binary format s* $t]
        }
        return $s
    }

    # This procedure is taken from sdx.kit to add resource strings to 
    # Windows executables.
    proc _WriteStringInfo { fileName array } {
        upvar $array val

        set fp [open $fileName r+]
        fconfigure $fp -translation binary
        set data [read $fp]

        set s [string first [_utf16le "StringFileInfo\0"] $data]
        if { $s < 0 } {
            close $fp
            ErrorAppend "No StringFileInfo resource found. No resource strings added." "Warning"
            return
        }
        if { ! [info exists val(CodePage)] } { set val(CodePage) 04b0 }
        if { ! [info exists val(Language)] } { set val(Language) 0409 }
     
        binary scan $data "x[incr s -6]s" len
        seek $fp [incr s 36] start
        incr len -36

        puts -nonewline $fp [binary format sss $len 0 1]
        puts -nonewline $fp [_utf16le "$val(Language)$val(CodePage)\0"]
        unset val(CodePage) val(Language)

        set len [expr {$len / 2 - 12}]
        foreach x [array names val] {
            set vlen [expr {[string length $val($x)] + 1}]
            set nlen [string length $x]
            set npad [expr {$nlen % 2}]
            set tlen [expr {$vlen + $nlen + $npad + 4}]
            set tpad [expr {$tlen % 2}]

            if {($tlen + $tpad) > $len} {
                ErrorAppend "Resource string \"$x\" too long. Will not be added." "Warning"
                break
            }
            # puts "  $x: set to '$val($x)'"
            puts -nonewline $fp [binary format sss [expr {$tlen * 2}] $vlen 1]
            set snpad [string repeat \0 $npad]
            set stpad [string repeat \0 $tpad]
            puts -nonewline $fp [_utf16le "$x\0$snpad$val($x)\0$stpad"]
            set len [expr {$len - $tlen - $tpad}]
        }
        puts -nonewline $fp [string repeat \0 [expr {$len * 2}]]
        close $fp
    }

    proc MakeStarpackTcl { appScript appName starpackName buildDir args } {
        _MakeStarpack "Tcl" $appScript $appName $starpackName $buildDir {*}$args
    }

    proc MakeStarpackTk { appScript appName starpackName buildDir args } {
        _MakeStarpack "Tk" $appScript $appName $starpackName $buildDir {*}$args
    }

    proc MakeStarpack { appScript appName starpackName buildDir args } {
        _MakeStarpack "Tk" $appScript $appName $starpackName $buildDir {*}$args
    }

    proc _MakeStarpack { type appScript appName starpackName buildDir args } {
        set msg "using tclkits"
        if { [UseZipkit] } {
            set msg "using zipkits"
        }
        Log "MakeStarpack $msg" 2
        if { ! [file isdirectory $buildDir] } {
            file mkdir $buildDir
        }

        set outPlatform "[GetPlatformName true][GetBits]"

        set tclBinDir [GetDevTclBinDir]
        set tclLibDir [GetDevTclLibDir]

        if { [UseZipkit] } {
            set runtimeTcl [file join $tclBinDir "zipkit-${outPlatform}-tcl[GetExeSuffix]"]
            set runtimeTk  [file join $tclBinDir "zipkit-${outPlatform}-tk[GetExeSuffix]"]
            set tclkit     [file join $buildDir  "zipkit-${outPlatform}-sh[GetExeSuffix]"]
            if { ! [file exists $runtimeTcl] } {
                ErrorAppend "MakeStarpack${type}: zipkit-tcl not found ($runtimeTcl)" "FATAL"
            }
            if { ! [file exists $runtimeTk] } {
                ErrorAppend "MakeStarpack${type}: zipkit-tk not found ($runtimeTk)" "FATAL"
            }
        } else {
            set runtimeTcl [file join $tclBinDir "tclkit-${outPlatform}-tcl[GetExeSuffix]"]
            set runtimeTk  [file join $tclBinDir "tclkit-${outPlatform}-tk[GetExeSuffix]"]
            set runtimeDyn [file join $tclBinDir "tclkit-${outPlatform}-dyn[GetExeSuffix]"]
            set tclkit     [file join $buildDir  "tclkit-${outPlatform}-sh[GetExeSuffix]"]

            if { ! [file exists $runtimeTcl] } {
                ErrorAppend "MakeStarpack${type}: tclkit-tcl not found ($runtimeTcl)" "FATAL"
            }
            if { ! [file exists $runtimeTk] } {
                ErrorAppend "MakeStarpack${type}: tclkit-tk not found ($runtimeTk)" "FATAL"
            }
            if { ! [file exists $runtimeDyn] } {
                ErrorAppend "MakeStarpack${type}: tclkit-dyn not found ($runtimeDyn)" "FATAL"
            }
        }
        if { ! [file exists $appScript] } {
            ErrorAppend "MakeStarpack${type}: Script not found ($appScript)" "FATAL"
        }

        set starpackExe       [file join $buildDir $starpackName]
        set starpackVfs       [format "%s.vfs" $appName]
        set starpackVfsDir    [file join $buildDir $starpackVfs]
        set starpackVfsLibDir [file join $starpackVfsDir "lib"]
        set starpackVfsRunDir [file join $starpackVfsDir "runtime"]

        Log "Output file: $starpackExe"  4 false

        file delete -force $starpackExe
        file copy -force $runtimeTcl $tclkit

        file mkdir $starpackVfsDir
        file mkdir $starpackVfsLibDir
        file mkdir $starpackVfsRunDir

        set cwd [pwd]
        cd $tclLibDir
        set itclDir ""
        foreach pkg $args {
            if { [string trim $pkg] eq "" } {
                continue
            } elseif { [string first "--" $pkg] == 0 } {
                Log "Add option : $pkg" 4 false
                if { $pkg eq "--runtime-vs" || $pkg eq "--runtime-gcc" } {
                    SingleFileCopy [GetTclZlib] $starpackVfsRunDir
                    if { $pkg eq "--runtime-vs" } {
                        if { [GetVSRuntimeLibDir] ne "" } {
                            MultiFileCopy [GetVSRuntimeLibDir] $starpackVfsRunDir "vcruntime*.dll"
                        }
                    } elseif { $pkg eq "--runtime-gcc" } {
                        SingleFileCopy [GetPthreadLib] $starpackVfsRunDir
                        SingleFileCopy [GetSehLib]     $starpackVfsRunDir
                    }
                }
            } elseif { [file isfile $pkg] } {
                Log "Add file   : $pkg" 4 false
                file copy -force $pkg $starpackVfsLibDir
            } else {
                Log "Add package: $pkg" 4 false
                if { ! [file isdirectory $pkg] } {
                    set tmpList [glob -nocomplain -types d -- "$pkg*"]
                    set dirList [list]
                    # Windows file names are not case sensitive, so we get double entries for
                    # ex. Img and imgtools. So check directory names with string match.
                    foreach dir $tmpList {
                        if { [string match "$pkg*" $dir] } {
                            lappend dirList $dir
                        }
                    }
                    if { [llength $dirList] == 0 } {
                        # Make this only a warning, so that packages needed / available only
                        # for a single platform (ex. Twapi) do not break the build.
                        ErrorAppend "MakeStarpack${type} $starpackName: Cannot find Tcl package $pkg" "Warning"
                        continue
                    }
                    if { [llength $dirList] > 1 } {
                        # More than 1 package was found. This is typically due to adding a new version
                        # of a library and not using the "--noversion" command line option.
                        # Example: cawt2.4.6 and cawt2.4.7
                        # In such a case you should remove the old library versions from the build directory.
                        #
                        # There is one case however, where this is not a possible error: tcllib.
                        # There are tcllibc and tcllib1.19 directories, which are both needed.
                        if { [file tail $pkg] ne "tcllib" } {
                            ErrorAppend "MakeStarpack${type} $starpackName: Found more than 1 package with prefix [file tail ${pkg}]*:\n \
                                        [join $dirList "\n  "]" "Warning"
                        }
                        foreach pkg $dirList {
                            file copy -force $pkg [file join $starpackVfsLibDir [file tail $pkg]]
                        }
                        continue
                    }
                    set pkg [lindex $dirList 0]
                }
                file copy -force $pkg [file join $starpackVfsLibDir [file tail $pkg]]
                if { [UseZipkit] } {
                    if { [string match "itcl*" [file tail $pkg]] } {
                        set itclDir [file tail $pkg]
                    }
                }
            }
        }
        file copy -force $appScript $starpackVfsLibDir

        set mainFile [file join $starpackVfsDir "main.tcl"]
        set startupCode ""

        if { [UseZipkit] } {
            if { $itclDir ne "" } {
                # itcl needs special treatment in zipkits, because of its
                # strange package initialization code (see generic/itclBase.c).
                # Set the global env(ITCL_LIBRARY) variable to the itcl directory
                # in the zipkit virtual filesystem. 
                append startupCode "set env(ITCL_LIBRARY) \"//zipfs:/app/lib/$itclDir\"\n"
            }
            append startupCode "set auto_path \{ //zipfs:/app/tcl_library //zipfs:/app //zipfs:/app/lib \}\n"
            append startupCode "source \"//zipfs:/app/lib/[file tail $appScript]\""
        } else {
            append startupCode "package require starkit\n"
            if { $type eq "Tk" } {
                append startupCode "package require Tk\n"
            }
            append startupCode "starkit::startup\n"
            append startupCode "source \[file join \$starkit::topdir lib [file tail $appScript]\]\n"
        }

        set retVal [catch {open $mainFile "w"} fp]
        if { $retVal != 0 } {
            ErrorAppend "MakeStarpack${type}: Cannot write to file $mainFile" "FATAL"
            return
        }
        puts $fp $startupCode
        close $fp

        if { [IsDarwin] } {
            set templateDir [file join [GetInputResourceDir] "Template.app"]
            file copy -force $templateDir $buildDir
        }

        set winInfo ""
        set macInfo [list]
        lappend macInfo "@EXECUTABLE@" "$starpackName"

        if { [GetTclkitCompanyName $appName] ne "" } {
            append  winInfo "CompanyName \"[GetTclkitCompanyName $appName]\"\n"
        }
        if { [GetTclkitLegalCopyright $appName] ne "" } {
            append  winInfo "LegalCopyright \"[GetTclkitLegalCopyright $appName]\"\n"
            lappend macInfo "@LEGALCOPYRIGHT@" "[GetTclkitLegalCopyright $appName]"
        }
        if { [GetTclkitFileDescription $appName] ne "" } {
            append  winInfo "FileDescription \"[GetTclkitFileDescription $appName]\"\n"
            lappend macInfo "@FILEDESCRIPTION@" "[GetTclkitFileDescription $appName]"
        }
        if { [GetTclkitProductName $appName] ne "" } {
            append  winInfo "ProductName \"[GetTclkitProductName $appName]\"\n"
            lappend macInfo "@PRODUCTNAME@" "[GetTclkitProductName $appName]"
        }
        if { [GetTclkitProductVersion $appName] ne "" } {
            append  winInfo "ProductVersion \"[GetTclkitProductVersion $appName]\"\n"
            lappend macInfo "@PRODUCTVERSION@" "[GetTclkitProductVersion $appName]"
        }
        if { [GetTclkitFileVersion $appName] ne "" } {
            append winInfo "FileVersion \"[GetTclkitFileVersion $appName]\"\n"
        }
        if { [GetTclkitIconFile $appName] ne "" } {
            lappend macInfo "@ICONFILE@" [file tail [GetTclkitIconFile $appName]]
        }

        if { [IsWindows] && $winInfo ne "" } {
            if { ! [UseZipkit] } {
                set infoFile [file join $starpackVfsDir "tclkit.inf"]
                set retVal [catch {open $infoFile "w"} fp]
                if { $retVal != 0 } {
                    ErrorAppend "MakeStarpack${type}: Cannot write to file $infoFile" "FATAL"
                    return
                }
                puts $fp $winInfo
                close $fp
            }
        }

        cd $buildDir
        if { $type eq "Tcl" } {
            set runtime $runtimeTcl
        } else {
            if { [IsDarwin] && ! [UseZipkit] } {
                set runtime $runtimeDyn
            } else {
                set runtime $runtimeTk
            }
        }
        if { [UseZipkit] } {
            set wrapFile1 [file join $buildDir "wrap1.tcl"]
            set wrapFile2 [file join $buildDir "wrap2.tcl"]
            if { $type eq "Tk" } {
                set retVal [catch {open $wrapFile1 "w"} fp]
                if { $retVal != 0 } {
                    ErrorAppend "MakeStarpack${type}: Cannot write to file $wrapFile1" "FATAL"
                    return
                }
                puts $fp "file copy \$tk_library [file join $starpackVfs tk_library]"
                puts $fp "exit"
                close $fp
                exec $runtimeTk $wrapFile1
            }
            set retVal [catch {open $wrapFile2 "w"} fp]
            if { $retVal != 0 } {
                ErrorAppend "MakeStarpack${type}: Cannot write to file $wrapFile2" "FATAL"
                return
            }
            puts $fp "set exePath \[zipfs mount //zipfs:/app\]"
            puts $fp "file copy \$tcl_library [file join $starpackVfs tcl_library]"
            puts $fp "zipfs mkimg $starpackName $starpackVfs $starpackVfs \"\" $runtime"
            close $fp
            exec $tclkit $wrapFile2
            if { [IsWindows] && $winInfo ne "" } {
                if { [UseZipkit] } {
                    array set strInfo $winInfo
                    _WriteStringInfo $starpackName strInfo
                }
            }
        } else {
            exec $tclkit [file join $tclBinDir sdx.kit] wrap $appName -runtime $runtime
            if { $appName ne [file tail $starpackExe] } {
                file rename $appName $starpackExe
            }
        }

        if { [IsDarwin] } {
            set appDir [file join $buildDir "${appName}.app"]
            Log "Output app : $appDir" 4
            if { [GetTclkitIconFile $appName] ne "" } {
                set iconFile [GetTclkitIconFile $appName]
                if { ! [file exists $iconFile] } {
                    set iconFile [file join [GetInputResourceDir] [file tail $iconFile]]
                    if { ! [file exists $iconFile] } {
                        ErrorAppend "MakeStarpack${type}: Cannot find icon file [GetTclkitIconFile $appName]" "FATAL"
                    }
                }
                file copy -force $iconFile [file join $buildDir "Template.app" "Contents" "Resources"] 
            }
            set infoFile [file join $buildDir "Template.app" "Contents" "Info.plist"]
            ReplaceKeywords $infoFile $macInfo
            file copy -force $starpackExe [file join $buildDir "Template.app" "Contents" "MacOS"]
            file rename [file join $buildDir "Template.app"] [file join $buildDir "${appName}.app"]
        }

        if { [IsWindows] && ! [UseZipkit] } {
            # TODO Create zipkit-dyn
            set starpackBatchName [format "%sBatch%s" [file rootname $starpackName] [file extension $starpackName]]
            set starpackBatchExe [file join $buildDir ${starpackBatchName}]

            Log "Batch starpack for Windows: $starpackBatchExe" 4
            cd $buildDir
            exec $tclkit [file join $tclBinDir sdx.kit] wrap $appName -runtime $runtimeDyn
            file delete -force $starpackBatchExe
            file rename $appName $starpackBatchExe
        }

        cd $cwd
    }

    proc AddCheckOption { checkOption } {
        variable sBuildCheckOption

        lappend sBuildCheckOption $checkOption
    }

    proc GetCheckOptions {} {
        variable sBuildCheckOption

        if { [info exists sBuildCheckOption] } {
            return $sBuildCheckOption
        } else {
            return [list]
        }
    }

    proc AddUserConfig { libName configOpt } {
        variable sBuildUserConfig

        set libName [string tolower $libName]
        lappend sBuildUserConfig($libName) $configOpt
    }

    proc GetUserConfig { libName } {
        variable sBuildUserConfig

        set libName [string tolower $libName]
        if { ! [info exists sBuildUserConfig] || ! [info exists sBuildUserConfig($libName)] } {
            return [list]
        }
        return $sBuildUserConfig($libName)
    }

    proc GetUserConfigValue { libName configKey } {
        set configValue ""
        foreach configOpt [GetUserConfig $libName] {
            if { [string match "${configKey}=*" $configOpt] } {
                set configValue [string trim [lindex [split $configOpt "="] 1]]
            }
        }
        return $configValue
    }

    proc GetUserCFlags { libName } {
        set cflags ""
        set userConfig [GetUserConfig $libName]
        if { [llength $userConfig] > 0 } {
            foreach arg $userConfig {
                if { [string match "-*" $arg] } {
                    append cflags "$arg "
                }
            }
        }
        return $cflags
    }

    proc IsSimulationMode {} {
        if { [UseStage "Update"] && [lsearch -exact [GetCheckOptions] "Stages"] >= 0 } {
            return true
        }
        return false
    }

    proc UpdateLib { libName why } {
        EnableStages $libName "Clean" "Extract" "Configure" "Compile" "Distribute"
        SetLibUpdated $libName $why
    }

    proc SetLibUpdated { libName why } {
        variable sUpdatedLibs

        Log "Update cause: $why" 2 false
        set sUpdatedLibs($libName) $why
    }

    proc AnyLibUpdated {} {
        variable sUpdatedLibs

        if { [info exists sUpdatedLibs] } {
            return true
        }
        return false
    }

    proc GetLibUpdateCause { libName } {
        variable sUpdatedLibs

        if { [info exists sUpdatedLibs($libName)] } {
            return $sUpdatedLibs($libName)
        }
        return ""
    }

    proc LibNeedsUpdate { libName } {
        variable sUpdatedLibs

        if { ! [CheckRecursiveDependencies] } {
            return ""
        }
        foreach dependency [GetLibDependencies $libName] {
            if { [string equal -nocase $dependency "All"] || \
                 [info exists sUpdatedLibs($dependency)] } {
                return $dependency
            }
        }
        return ""
    }

    proc CheckRecursiveDependencies {} {
        variable sBuildOpts

        return $sBuildOpts(UseRecursiveDependencies)
    }

    proc UseRecursiveDependencies { onOff } {
        variable sBuildOpts

        set sBuildOpts(UseRecursiveDependencies) $onOff
    }

    proc EnableStage { stage { libName "all" } } {
        variable sBuildStages

        set sBuildStages($stage,$libName) true
    }

    proc EnableStages { libName args } {
        foreach stage $args {
            EnableStage $stage $libName
        }
    }

    proc EnableAllStages { { libName "all" } } {
        EnableStages $libName "Clean" "Extract" "Configure" "Compile" "Distribute" "Finalize"
    }

    proc DisableStage { stage { libName "all" } } {
        variable sBuildStages

        set sBuildStages($stage,$libName) false
    }

    proc DisableStages { libName args } {
        foreach stage $args {
            DisableStage $stage $libName
        }
    }

    proc DisableAllStages { { libName "all" } } {
        DisableStage "Clean"      $libName
        DisableStage "Extract"    $libName
        DisableStage "Configure"  $libName
        DisableStage "Compile"    $libName
        DisableStage "Distribute" $libName
        DisableStage "Finalize"   $libName
        DisableStage "Touch"      $libName
        DisableStage "Test"       $libName
    }

    proc UseStage { stage { libName "all" } } {
        variable sBuildStages

        if { [HaveFatalError] } {
            return false
        }

        if { [info exists sBuildStages($stage,$libName)] } {
            return $sBuildStages($stage,$libName)
        } else {
            if { [info exists sBuildStages($stage,all)] } {
                return $sBuildStages($stage,all)
            } else {
                return false
            }
        }
    }

    proc IsBuildStage { { libName "all" } } {
        return [expr \
            [UseStage "Extract"    $libName] || \
            [UseStage "Configure"  $libName] || \
            [UseStage "Compile"    $libName] || \
            [UseStage "Distribute" $libName] || \
            [UseStage "Test"       $libName]]
    }

    proc GetUsedStages { { libName "all" } } {
        variable sBuildStages
        variable sStageOrder

        set usedStages [list]
        foreach stage $sStageOrder {
            if { [info exists sBuildStages($stage,$libName)] && $sBuildStages($stage,$libName) } {
                lappend usedStages $stage
            }
        }
        if { [llength $usedStages] == 0 } {
            return "None"
        } else {
            return $usedStages
        }
    }

    proc UseTclPkgVersion { { onOff "" } } {
        variable sBuildOpts

        if { $onOff eq "" } {
            return $sBuildOpts(UseTclPkgVersion)
        } else {
            set sBuildOpts(UseTclPkgVersion) $onOff
        }
    }

    proc ExitOnFatalError { { onOff "" } } {
        variable sBuildOpts

        if { $onOff eq "" } {
            return $sBuildOpts(ExitOnFatalError)
        } else {
            set sBuildOpts(ExitOnFatalError) $onOff
        }
    }

    proc PrintSetupWarning { { onOff "" } } {
        variable sBuildOpts

        if { $onOff eq "" } {
            return $sBuildOpts(PrintSetupWarning)
        } else {
            set sBuildOpts(PrintSetupWarning) $onOff
        }
    }

    proc UseSanitizer { { onOff "" } } {
        variable sBuildOpts

        if { $onOff eq "" } {
            return $sBuildOpts(UseSanitizer)
        } else {
            set sBuildOpts(UseSanitizer) $onOff
        }
    }

    proc GetSanitizerCFlags {} {
        if { [UseSanitizer] } {
            return "-fsanitize=address,undefined -Wp,-D_FORTIFY_SOURCE=2 -fstack-protector-strong -mshstk "
        } else {
            return ""
        }
    }

    proc GetSanitizerLdFlags {} {
        if { [UseSanitizer] } {
            return "-fsanitize=address,undefined "
        } else {
            return ""
        }
    }

    proc SetDarwinMinVersion { version } {
        variable sBuildOpts

        set sBuildOpts(DarwinMinVersion) $version
    }

    proc GetDarwinMinVersion {} {
        variable sBuildOpts

        return $sBuildOpts(DarwinMinVersion)
    }

    proc UseUniversal { { onOff "" } } {
        variable sBuildOpts

        if { $onOff eq "" } {
            return $sBuildOpts(UseUniversal)
        } else {
            set sBuildOpts(UseUniversal) $onOff
        }
    }

    proc GetDarwinCFlags {} {
        if { [UseUniversal] } {
            return "-arch arm64 -arch x86_64 -mmacosx-version-min=[GetDarwinMinVersion]"
        } elseif { [IsDarwin] } {
            return "-mmacosx-version-min=[GetDarwinMinVersion]"
        } else {
            return ""
        }
    }

    proc GetUniversalCFlags {} {
        ErrorAppend "GetUniversalCFlags is deprecated since BAWT 3.1.0. Use GetDarwinCFlags instead." "Warning"
        return [GetDarwinCFlags]
    }

    proc SetCompilerVersion { version } {
        variable sBuildOpts

        if { $version eq "gcc" } {
            set sBuildOpts(CompilerVersion) [GetPlatformName]
        } else {
            set sBuildOpts(CompilerVersion) $version
        }
    }

    proc SetCompilerVersions { args } {
        variable sBuildOpts

        set sBuildOpts(CompilerVersions) [list]
        foreach version $args {
            lappend sBuildOpts(CompilerVersions) $version
        }
    }

    proc GetCompilerVersions {} {
        variable sBuildOpts

        return $sBuildOpts(CompilerVersions)
    }

    proc GetCompilerVersion { args } {
        variable sBuildOpts

        set showPlatformName false
        if { [lsearch -exact $args "-platform"] >= 0 } {
            set showPlatformName true
        }
        set showNumericVersion false
        if { [lsearch -exact $args "-numeric"] >= 0 } {
            set showNumericVersion true
        }
        set showVisualStudioVersion false
        if { [lsearch -exact $args "-vs"] >= 0 } {
            set showVisualStudioVersion true
        }
        set libName ""
        set ind [lsearch -exact $args "-lib"]
        if { $ind >= 0 } {
            set libName [lindex $args [expr { $ind + 1 }]]
        }

        if { ! [IsWindows] } {
            if { $showPlatformName } {
                return [GetPlatformName]
            } else {
                return "gcc"
            }
        }

        if { $libName ne "" } {
            if { [GetWinCompiler $libName] eq "" } {
                return ""
            }
            foreach version [GetCompilerVersions] {
                if { [string match "vs*" $version] && [UseWinCompiler $libName "vs"] } {
                    if { $showNumericVersion } {
                        return [string range $version 2 end]
                    } else {
                        return $version
                    }
                } elseif { $version eq "gcc" && [UseWinCompiler $libName "gcc"] } {
                    if { $showPlatformName } {
                        return [GetPlatformName]
                    } else {
                        return $version
                    }
                }
            }
            ErrorAppend "GetCompilerVersion: No compiler found for library $libName." "FATAL"
        }

        if { $showVisualStudioVersion } {
            foreach version [GetCompilerVersions] {
                if { [string match "vs*" $version] } {
                    if { $showNumericVersion } {
                        return [string range $version 2 end]
                    } else {
                        return $version
                    }
                }
            }
            ErrorAppend "GetCompilerVersion: No VisualStudio compiler specified." "FATAL"
        }

        set version [lindex [GetCompilerVersions] 0]
        if { [string match "vs*" $version] } {
            if { $showNumericVersion } {
                return [string range $version 2 end]
            } else {
                return $version
            }
        } else {
            if { $showPlatformName } {
                return [GetPlatformName]
            } else {
                return $version
            }
        }
    }

    proc GetVisualStudioVersion {} {
        return [GetCompilerVersion -vs -numeric]
    }

    proc GetGccCompilerVersion {} {
        if { [IsWindows] } {
            return [GetMingwGccVersion]
        } else {
            # gcc has option "-dumpversion" (dumps only major version) and
            # "-dumpfullversion" (dumps full version, but is not available with
            # gcc version 4.X).
            # So we scan the output of option "-v", where the last line shows
            # the full version number using different output formats.
            try {
                set result [exec gcc -v]
            } trap NONE result {
            }

            set version ""
            foreach line [split $result "\n"] {
                set line [string trim $line]
                if { [string match "gcc version*" $line] } {
                    scan $line "gcc version %s" version
                    break
                } elseif { [string match "gcc-Version*" $line] } {
                    scan $line "gcc-Version %s" version
                    break
                } elseif { [string match "Apple clang version*" $line] } {
                    scan $line "Apple clang version %s" version
                    break
                }
            }
            return $version
        }
    }

    proc GetValidCompilerVersions {} {
        return [list "gcc" "vs2008" "vs2010" "vs2012" "vs2013" "vs2015" "vs2017" "vs2019" "vs2022" "vs2026"]
    }

    proc _Min { a b } {
        if { $a < $b } {
            return $a
        } else {
            return $b
        }
    }

    proc SetNumJobs { numJobs { libName "all" } { winCompiler "" } } {
        variable sBuildOpts

        Log "SetNumJobs $numJobs $libName $winCompiler"
        set libName [string tolower $libName]
        if { $winCompiler ne "" } {
            set sBuildOpts($libName,NumJobs,$winCompiler) $numJobs
        } else {
            set sBuildOpts($libName,NumJobs) $numJobs
        }
    }

    proc GetNumJobs { { libName "all" } { winCompiler "" } } {
        variable sBuildOpts

        set libName [string tolower $libName]
        if { $winCompiler ne "" && [info exists sBuildOpts($libName,NumJobs,$winCompiler)] } {
            return [_Min $sBuildOpts($libName,NumJobs,$winCompiler) $sBuildOpts(all,NumJobs)]
        } elseif { [info exists sBuildOpts($libName,NumJobs)] } {
            return [_Min $sBuildOpts($libName,NumJobs) $sBuildOpts(all,NumJobs)]
        } else {
            return $sBuildOpts(all,NumJobs)
        }
    }

    proc _SupportsParallelBuild {} {
        if { [UseVisualStudio] } {
            # VisualStudio 2008 Express does not support option /m.
            if { [GetCompilerVersion -vs] eq "vs2008" } {
                return false
            }
        }
        return true
    }

    proc SetTimeout { secs } {
        variable sBuildOpts

        if { $secs < 0.0 } {
            set secs 0.0 
        }
        set sBuildOpts(Timeout) [expr int ($secs * 1000.0)]
    }

    proc GetTimeout { { unit "ms" } } {
        variable sBuildOpts

        if { $unit eq "ms" } {
            return $sBuildOpts(Timeout)
        } else {
            return [expr $sBuildOpts(Timeout) / 1000.0]
        }
    }

    proc HaveAsmProg { asmProg } {
        if { [auto_execok $asmProg] ne "" } {
            return true
        }
        if { [auto_execok "[GetOutputDevDir]/bin/${asmProg}[GetExeSuffix]"] ne "" } {
            return true
        }
        return false
    }

    proc HaveYasmProg {} {
        return [HaveAsmProg "yasm"]
    }

    proc GetGendefProg {} {
        return [file join [GetOutputToolsDir] [GetMingwDir] [GetMingwSubDir] "bin" "gendef.exe"]
    }

    proc GetSWIGDistDir {} {
        return [file join [GetOutputDevDir] "opt" "SWIG" "bin"]
    }

    proc GetPandocDistDir {} {
        return [file join [GetOutputDevDir] "opt" "pandoc"]
    }

    proc GetCMakeDistDir {} {
        return [file join [GetOutputDevDir] "opt" "CMake" "bin"]
    }

    proc GetCMakeProg {} {
        set cmakeCmd [auto_execok "[GetCMakeDistDir]/cmake[GetExeSuffix]"]
        if { $cmakeCmd eq "" } {
            set cmakeCmd [auto_execok "cmake[GetExeSuffix]"]
            if { $cmakeCmd eq "" } {
                ErrorAppend "Cannot find CMake program" "FATAL"
            }
        }
        return $cmakeCmd
    }

    proc GetCMakePolicyOption {} {
        set cmakeOpt ""
        if { [GetMajor [GetLibVersion "CMake"]] >= 4 } {
            append cmakeOpt "-DCMAKE_POLICY_VERSION_MINIMUM=3.5"
        }
        return $cmakeOpt
    }

    proc GetCMakeMSysOption { libName } {
        set cmakeMSysOpt ""
        if { [UseWinCompiler $libName "gcc"] } {
            set cmakeMSysOpt "-GMSYS Makefiles"
        }
        return $cmakeMSysOpt
    }

    proc GetMSysCppOption { libName } {
        set opt ""
        if { [UseWinCompiler $libName "gcc"] } {
            set opt "CXX='g++ -static-libstdc++ -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive' "
        }
        return $opt
    }

    proc GetPermissiveCFlags { { force false } } {
        set cflags ""
        if { [GetMajor [GetGccCompilerVersion]] >= 14 || $force } {
            append cflags "-fpermissive [GetPermissiveDarwinCFlags $force] "
        }
        if { [GetMajor [GetGccCompilerVersion]] >= 15 || $force } {
            append cflags "-std=gnu99 "
        }
        return $cflags
    }

    proc GetPermissiveDarwinCFlags { { force } } {
        if { [IsDarwin] && ( [IsArm] || $force ) } {
            set noErr ""
            append noErr "-Wno-error=incompatible-function-pointer-types "
            append noErr "-Wno-error=incompatible-pointer-types "
            append noErr "-Wno-error=int-conversion "
            append noErr "-Wno-error=implicit-function-declaration " 
            return $noErr
        }
        return ""
    }

    proc GetInnoDistDir {} {
        return [file join [GetOutputDevDir] "opt" "InnoSetup"]
    }

    proc GetInnoProg {} {
        set progName "ISCC[GetExeSuffix]"
        set innoCmd [auto_execok $progName]
        if { [IsWindows] } {
            if { $innoCmd eq "" } {
                set innoCmd [auto_execok "[GetInnoDistDir]/$progName"]
            }
            if { $innoCmd eq "" } {
                ErrorAppend "Cannot find InnoSetup program ISCC.exe" "FATAL"
            }
        }
        return $innoCmd
    }

    proc GetDpkgDebProg {} {
        set progName "dpkg-deb"
        set debCmd [auto_execok $progName]
        if { [IsLinux] } {
            if { $debCmd eq "" } {
                ErrorAppend "Cannot find Debian package program dpkg-dep" "FATAL"
            }
        }
        return $debCmd
    }

    proc GetDpkgDebOpts {} {
        # Older dpkg-deb versions do not support option "--root-owner-group"
        # Check by executing the program with option "--help".
        set opts ""
        if { [GetDpkgDebProg] ne "" } {
            catch { eval ::exec [GetDpkgDebProg] --help } dpkgHelpString
            if { [string match "*root-owner-group*" $dpkgHelpString] } {
                set opts "--root-owner-group"
            }
        }
        return $opts
    }

    proc GetDpkgArchitecture {} {
        if { [IsIntel] } {
            return "amd64"
        } elseif { [IsArm] } {
            return "arm64"
        } elseif { [IsRiscV] } {
            return "riscv64"
        } else {
            return "unknown"
        }
    }

    proc GetHdiUtilProg {} {
        set progName "hdiutil"
        set dmgCmd [auto_execok $progName]
        if { [IsDarwin] } {
            if { $dmgCmd eq "" } {
                ErrorAppend "Cannot find MacOS package program hdiutil" "FATAL"
            }
        }
        return $dmgCmd
    }

    proc SetMingwGccVersion { gccVersion } {
        variable sBuildOpts

        set sBuildOpts(GccVersion) $gccVersion
    }

    proc GetMingwGccVersion {} {
        variable sBuildOpts

        return $sBuildOpts(GccVersion)
    }

    proc GetMingwVersion {} {
        set mingwVersion ""
        if { [Is64Bit] } {
            set mingwVersion "x86_64-w64-mingw32"
        } else {
            set mingwVersion "i686-w64-mingw32"
        }
        return $mingwVersion
    }

    proc GetMingwDir {} {
        set gccVersion   [GetMingwGccVersion]
        set mingwVersion [GetMingwVersion]
        set mingwDir     "gcc${gccVersion}_${mingwVersion}"
        return $mingwDir
    }

    proc GetMingwSubDir {} {
        if { [Is64Bit] } {
            set mingwSubDir "mingw64"
        } elseif { [Is32Bit] } {
            set mingwSubDir "mingw32"
        }
        return $mingwSubDir
    }

    proc GetMingwIncludeDir {} {
        return [file join [GetOutputToolsDir] [GetMingwDir] [GetMingwSubDir] \
                "lib" "gcc" [GetMingwVersion] [GetMingwGccVersion] "include"]
    }

    proc GetMingwLib { libName } {
        set path [file join [GetOutputToolsDir] [GetMingwDir] [GetMingwSubDir] "bin" $libName]
        if { [file exists $path] } {
            return $path
        }
        ErrorAppend "GetMingwLib: Library $libName not found." "FATAL"
    }

    proc GetPthreadLib {} {
        return [GetMingwLib "libwinpthread-1.dll"]
    }

    proc GetSehLib {} {
        if { [Is64Bit] } {
            return [GetMingwLib "libgcc_s_seh-1.dll"]
        } else {
            return [GetMingwLib "libgcc_s_dw2-1.dll"]
        }
    }

    proc GetTclZlib {} {
        return [file join [GetDevTclBinDir] "zlib1.dll"]
    }

    proc GetMSysShell {} {
        if { [IsWindows] } {
            return [file join [GetMSysBinDir] "sh.exe"]
        } else {
            return "/bin/bash"
        }
    }

    proc GetCmdShell {} {
        global env

        if { [IsWindows] } {
            if { [Is64Bit] } {
                return [file join $env(windir) "SysWOW64" "cmd.exe"]
            } else {
                return [file join $env(windir) "system32" "cmd.exe"]
            }
        } else {
            return "/bin/bash"
        }
    }

    proc SetBuildType { buildType } {
        variable sBuildOpts

        set sBuildOpts(BuildType) $buildType
    }

    proc GetBuildType {} {
        variable sBuildOpts

        return $sBuildOpts(BuildType)
    }

    proc GetValidBuildTypes {} {
        return [list "Release" "Debug"]
    }

    proc IsReleaseBuild {} {
        if { [GetBuildType] eq "Release" } {
            return true
        } else {
            return false
        }
    }

    proc IsDebugBuild {} {
        if { [GetBuildType] eq "Debug" } {
            return true
        } else {
            return false
        }
    }

    proc GetDebugSuffix { { suffix "g" } } {
        set debugSuffix ""
        if { [IsDebugBuild] } {
            set debugSuffix $suffix
        }
        return $debugSuffix
    }

    proc GetWinDebugSuffix { { suffix "g" } } {
        set debugSuffix ""
        if { [IsDebugBuild] && [IsWindows] && [GetMajor [GetTclVersion]] < 9 } {
            set debugSuffix $suffix
        }
        return $debugSuffix
    }

    proc SetArchitecture { architecture } {
        variable sBuildOpts

        set sBuildOpts(Architecture) $architecture
    }

    proc GetArchitecture {} {
        variable sBuildOpts

        return $sBuildOpts(Architecture)
    }

    proc GetValidArchitectures {} {
        return [list "x86" "x64"]
    }

    proc Is32Bit {} {
        if { [GetArchitecture] eq "x86" } {
            return true
        } else {
            return false
        }
    }

    proc Is64Bit {} {
        if { [GetArchitecture] eq "x64" } {
            return true
        } else {
            return false
        }
    }

    proc GetBits { { show64BitOnly false } } {
        if { [Is64Bit] } {
            return 64
        } else {
            if { $show64BitOnly } {
                return ""
            } else {
                return 32
            }
        }
    }

    proc UnifyVersionNumber { libVersion { combineChar "." } } {
        # Map version strings like 9.0a1 to 9.0.a1.
        set modLibVersion [string map {".a" ".a"  ".b" ".b"  "a" ".a"  "b" ".b"} $libVersion]

        set versionNumbers [split $modLibVersion "."]
        if { [llength $versionNumbers] >= 3 } {
            # Remove "a" and "b" from patch string.
            # A fourth version number after patch is ignored.
            set patchVersion [string map {"a" ""  "b" ""} [lindex $versionNumbers 2]]
            if { [string is integer -strict [lindex $versionNumbers 0]] && \
                 [string is integer -strict [lindex $versionNumbers 1]] && \
                 [string is integer -strict $patchVersion] } {
                return $modLibVersion
            }
        } elseif { [llength $versionNumbers] == 2 } {
            if { [string is integer -strict [lindex $versionNumbers 0]] && \
                 [string is integer -strict [lindex $versionNumbers 1]] } {
                return [format "%d.%d.%d" [lindex $versionNumbers 0] [lindex $versionNumbers 1] 0]
            }
        } elseif { [llength $versionNumbers] == 1 } {
            if { [string is integer -strict [lindex $versionNumbers 0]] } {
                return [format "%d.%d.%d" [lindex $versionNumbers 0] 0 0]
            }
        }
        return "$libVersion"
    }

    proc GetMajor { libVersion } {
        return [lindex [split [UnifyVersionNumber $libVersion] "."] 0]
    }

    proc GetMinor { libVersion } {
        return [lindex [split [UnifyVersionNumber $libVersion] "."] 1]
    }

    proc GetPatch { libVersion } {
        return [lindex [split [UnifyVersionNumber $libVersion] "."] 2]
    }

    proc GetMajorMinor { libVersion { combineChar "" } } {
        set combine ""
        if { $combineChar eq "" } {
            if { [IsUnix] } {
                set combine "."
            }
        } else {
            set combine $combineChar
        }
        return [format "%s%s%s" [GetMajor $libVersion] $combine [GetMinor $libVersion]]
    }

    proc GetMajorMinorPatch { libVersion { combineChar "" } } {
        set combine ""
        if { $combineChar eq "" } {
            if { [IsUnix] } {
                set combine "."
            }
        } else {
            set combine $combineChar
        }
        return [format "%s%s%s%s%s" [GetMajor $libVersion] $combine \
                                    [GetMinor $libVersion] $combine \
                                    [GetPatch $libVersion] ]
    }

    proc VersionCompare { version1 version2 } {
        # A wrapper around package vcompare to handle alpha or beta versions
        # containing "a" or "b", ex. 8.7.a4. 
        # Alpha and Beta versions are handled as having a patch version of zero,
        # so 8.6.a4 is equal to 8.6.b1.
        set version1 [UnifyVersionNumber $version1]
        set version2 [UnifyVersionNumber $version2]
        set patchVersion1 [GetPatch $version1]
        set patchVersion2 [GetPatch $version2]
        if { ! [string is integer -strict $patchVersion1] } {
            set version1 [format "%d.%d.%d" [GetMajor $version1] [GetMinor $version1] 0]
        }
        if { ! [string is integer -strict $patchVersion2] } {
            set version2 [format "%d.%d.%d" [GetMajor $version2] [GetMinor $version2] 0]
        }
        return [package vcompare $version1 $version2]
    }

    proc VersionCheck { version1 cmp version2 } {
        set retVal [VersionCompare $version1 $version2]
        switch -exact -nocase -- $cmp {
            "newer"      { if { $retVal > 0 }                 { return true } }
            "newerequal" { if { $retVal > 0 || $retVal == 0 } { return true } }
            "equal"      { if { $retVal == 0 }                { return true } }
            "older"      { if { $retVal < 0 }                 { return true } }
            "olderequal" { if { $retVal < 0 || $retVal == 0 } { return true } }
            "default"    { ErrorAppend "VersionCheck: Invalid compare operator \"$cmp\"." "FATAL" }
        }
        return false
    }

    proc GetPlatformName { { useShortName false } } {
        if { [IsWindows] } {
            if { $useShortName } {
                return "win"
            } else {
                return "Windows"
            }
        } elseif { [IsLinux] } {
            return "Linux"
        } elseif { [IsDarwin] } {
            return "Darwin"
        } elseif { [IsSunOS] } {
            return "SunOS"
        } else {
            return "Unknown"
        }
    }

    proc GetMsBuildPlatform {} {
        if { [GetArchitecture] eq "x86" } {
            return "Win32"
        } else {
            return "x64"
        }
    }

    proc GetBatchSuffix { { dot "." } } {
        set suf $dot
        if { [IsWindows] } {
            append suf "bat"
        } else {
            append suf "sh"
        }
        return $suf
    }

    proc GetExeSuffix { { dot "." } } {
        set suf $dot
        if { [IsWindows] } {
            append suf "exe"
        } else {
            return ""
        }
        return $suf
    }

    proc GetExePattern {} {
        return "*[GetExeSuffix]"
    }

    proc GetLibSuffix { { dot "." } } {
        set suf $dot
        if { [IsWindows] } {
            append suf "dll"
        } elseif { [IsLinux] } {
            append suf "so"
        } elseif { [IsSunOS] } {
            append suf "so"
        } else {
            append suf "dylib"
        }
        return $suf
    }

    proc GetImportLibSuffix { { dot "." } } {
        set suf $dot
        if { [IsWindows] } {
            append suf "lib"
        } elseif { [IsLinux] } {
            append suf "so"
        } elseif { [IsSunOS] } {
            append suf "so"
        } else {
            append suf "dylib"
        }
        return $suf
    }

    proc GetStaticLibSuffix { { dot "." } } {
        set suf $dot
        if { [IsWindows] } {
            append suf "lib"
        } elseif { [IsLinux] } {
            append suf "a"
        } elseif { [IsSunOS] } {
            append suf "a"
        } else {
            append suf "a"
        }
        return $suf
    }

    proc GetLibPattern {} {
        return "*[GetLibSuffix]"
    }

    proc IsIntel {} {
        set machine $::tcl_platform(machine)
        if { $machine eq "amd64"  || \
             $machine eq "intel"  || \
             $machine eq "x86_64" || \
             $machine eq "x86pc"  || \
             $machine eq "i686" } {
            return true
        } else {
            return false
        }
    }

    proc IsArm {} {
        set machine $::tcl_platform(machine)
        if { $machine eq "arm64" || \
             $machine eq "aarch64" || \
             $machine eq "armv7l" } {
            return true
        } else {
            return false
        }
    }

    proc IsRiscV {} {
        set machine $::tcl_platform(machine)
        if { $machine eq "riscv64" } {
            return true
        } else {
            return false
        }
    }

    proc IsWindows {} {
        if { $::tcl_platform(platform) eq "windows" } {
            return true
        } else {
            return false
        }
    }

    proc IsLinux {} {
        if { $::tcl_platform(os) eq "Linux" } {
            return true
        } else {
            return false
        }
    }

    proc IsDarwin {} {
        if { $::tcl_platform(os) eq "Darwin" } {
            return true
        } else {
            return false
        }
    }

    proc IsSunOS { { osType "" } } {
        if { $::tcl_platform(os) eq "SunOS" } {
            if { $osType eq "" } {
                return true
            } else {
                set res [string trim [eval exec uname -o]]
                if { $res eq "Solaris" && $osType eq "Solaris" } {
                    return true
                } elseif { $res eq "illumos" && $osType eq "Indiana" } {
                    return true
                } else {
                    return false
                }
            }
        } else {
            return false
        }
    }

    proc IsUnix {} {
        if { $::tcl_platform(platform) eq "unix" } {
            return true
        } else {
            return false
        }
    }

    proc CreateDefaultDirs {} {
        if { ! [file isdirectory [GetOutputArchDir]] } {
            file mkdir [GetOutputArchDir]
        }
        if { ! [file isdirectory [GetOutputDevDir]] } {
            file mkdir [GetOutputDevDir]
        }
        if { ! [file isdirectory [GetOutputBuildDir]] } {
            file mkdir [GetOutputBuildDir]
        }
        if { ! [file isdirectory [GetOutputInstDir]] } {
            file mkdir [GetOutputInstDir]
        }
        if { ! [file isdirectory [GetOutputDistDir]] } {
            file mkdir [GetOutputDistDir]
        }
    }

    proc SetShortRootDir { onOff } {
        variable sBuildOpts

        set sBuildOpts(UseShortRootDir) $onOff
    }

    proc UseShortRootDir {} {
        variable sBuildOpts

        return $sBuildOpts(UseShortRootDir)
    }

    proc GetOutputArchDir {} {
        if { [UseShortRootDir] } {
            return [GetOutputRootDir]
        } else {
            return [file join [GetOutputRootDir] [GetCompilerVersion -platform] [GetArchitecture]]
        }
    }

    proc GetOutputDevDir {} {
        return [file join [GetOutputArchDir] "Development"]
    }

    proc GetOutputTypeDir {} {
        return [file join [GetOutputArchDir] [GetBuildType]]
    }

    proc GetOutputBuildDir {} {
        return [file join [GetOutputTypeDir] "Build"]
    }

    proc GetOutputInstDir {} {
        return [file join [GetOutputTypeDir] "Install"]
    }

    proc SetOutputDistDir { dir } {
        variable sBuildOpts

        set sBuildOpts(DistDir) [file normalize $dir]
    }

    proc GetOutputDistDir {} {
        variable sBuildOpts

        if { $sBuildOpts(DistDir) ne "" } {
            return $sBuildOpts(DistDir)
        } else {
            return [file join [GetOutputTypeDir] "Distribution"]
        }
    }

    proc SetFinalizeFile { fileName } {
        variable sBuildOpts

        set found [file exists $fileName]
        if { $found } {
            set sBuildOpts(FinalizeFile) [file normalize $fileName]
        } else {
            ErrorAppend "SetFinalizeFile: Script file $fileName not found." "FATAL"
        }
    }

    proc GetFinalizeFile {} {
        variable sBuildOpts

        return $sBuildOpts(FinalizeFile)
    }

    proc _GetTclkitAttribute { libName attrName } {
        variable sBuildOpts

        if { [info exists sBuildOpts(Tclkit,$libName,$attrName)] } {
            return $sBuildOpts(Tclkit,$libName,$attrName)
        }
        if { [info exists sBuildOpts(Tclkit,All,$attrName)] } {
            return $sBuildOpts(Tclkit,All,$attrName)
        }
        return ""
    }

    proc SetTclkitIconFile { libName fileName } {
        variable sBuildOpts

        set found [file exists $fileName]
        if { ! $found } {
            if { [file pathtype $fileName] eq "relative" } {
                set testFile [file join [GetInputResourceDir] $fileName]
                if { [file exists $testFile] } {
                    set fileName $testFile
                    set found true
                }
            }
        }
        if { $found } {
            set sBuildOpts(Tclkit,$libName,IconFile) [file normalize $fileName]
        } else {
            ErrorAppend "SetTclkitIconFile: Icon file $fileName not found. Using default icon file." "Warning"
        }
    }

    proc GetTclkitIconFile { libName } {
        return [_GetTclkitAttribute $libName "IconFile"]
    }

    proc SetTclkitResourceFile { libName fileName } {
        variable sBuildOpts

        set found [file exists $fileName]
        if { ! $found } {
            if { [file pathtype $fileName] eq "relative" } {
                set testFile [file join [GetInputResourceDir] $fileName]
                if { [file exists $testFile] } {
                    set fileName $testFile
                    set found true
                }
            }
        }
        if { $found } {
            set sBuildOpts(Tclkit,$libName,ResourceFile) [file normalize $fileName]
        } else {
            ErrorAppend "SetTclkitResourceFile: Resource file $fileName not found. Using default icon file." "Warning"
        }
    }

    proc GetTclkitResourceFile { libName } {
        return [_GetTclkitAttribute $libName "ResourceFile"]
    }

    proc SetTclkitCertFile { libName fileName } {
        variable sBuildOpts

        set found [file exists $fileName]
        if { ! $found } {
            if { [file pathtype $fileName] eq "relative" } {
                set testFile [file join [GetInputResourceDir] $fileName]
                if { [file exists $testFile] } {
                    set fileName $testFile
                    set found true
                }
            }
        }
        if { $found } {
            set sBuildOpts(Tclkit,$libName,CertFile) [file normalize $fileName]
        } else {
            ErrorAppend "SetTclkitCertFile: Certification file $fileName not found. No code signing." "Warning"
        }
    }

    proc GetTclkitCertFile { libName } {
        return [_GetTclkitAttribute $libName "CertFile"]
    }

    proc SetTclkitTimeStampUrl { libName value } {
        variable sBuildOpts

        set sBuildOpts(Tclkit,$libName,TimeStampUrl) $value
    }

    proc GetTclkitTimeStampUrl { libName } {
        return [_GetTclkitAttribute $libName "TimeStampUrl"]
    }

    proc SetTclkitCompanyName { libName value } {
        variable sBuildOpts

        set sBuildOpts(Tclkit,$libName,CompanyName) $value
    }

    proc GetTclkitCompanyName { libName } {
        return [_GetTclkitAttribute $libName "CompanyName"]
    }

    proc SetTclkitLegalCopyright { libName value } {
        variable sBuildOpts

        set sBuildOpts(Tclkit,$libName,LegalCopyright) $value
    }

    proc GetTclkitLegalCopyright { libName } {
        return [_GetTclkitAttribute $libName "LegalCopyright"]
    }

    proc SetTclkitFileDescription { libName value } {
        variable sBuildOpts

        set sBuildOpts(Tclkit,$libName,FileDescription) $value
    }

    proc GetTclkitFileDescription { libName } {
        return [_GetTclkitAttribute $libName "FileDescription"]
    }

    proc SetTclkitProductName { libName value } {
        variable sBuildOpts

        set sBuildOpts(Tclkit,$libName,ProductName) $value
    }

    proc GetTclkitProductName { libName } {
        return [_GetTclkitAttribute $libName "ProductName"]
    }

    proc SetTclkitProductVersion { libName value } {
        variable sBuildOpts

        set sBuildOpts(Tclkit,$libName,ProductVersion) $value
    }

    proc GetTclkitProductVersion { libName } {
        return [_GetTclkitAttribute $libName "ProductVersion"]
    }

    proc SetTclkitFileVersion { libName value } {
        variable sBuildOpts

        set sBuildOpts(Tclkit,$libName,FileVersion) $value
    }

    proc GetTclkitFileVersion { libName } {
        return [_GetTclkitAttribute $libName "FileVersion"]
    }

    proc SetTclVersion { version } {
        variable sBuildOpts

        set sBuildOpts(Version,Tcl) $version
    }

    proc GetTclVersion {} {
        variable sBuildOpts

        return $sBuildOpts(Version,Tcl)
    }

    proc SetTkVersion { version } {
        variable sBuildOpts

        set sBuildOpts(Version,Tk) $version
    }

    proc GetTkVersion {} {
        variable sBuildOpts

        if { [info exists sBuildOpts(Version,Tk)] } {
            return $sBuildOpts(Version,Tk)
        } else {
            return [GetTclVersion]
        }
    }

    proc HaveTcl9 {} {
        if { [GetMajor [GetTclVersion]] >= 9 } {
            return true
        } else {
            return false
        }
    }

    proc HaveTk9 {} {
        if { [GetMajor [GetTkVersion]] >= 9 } {
            return true
        } else {
            return false
        }
    }

    proc GetPythonVersion {} {
        set pythonVersion "0.0.0"
        # First try the BAWT supplied Python executable.
        set pythonExe [file join [GetDevPythonBinDir] "python.exe"]
        if { ! [file exists $pythonExe] } {
            # Look for an installed Python version.
            # Note, that on Win10 there might be files Python3.exe
            # and Python.exe with zero size in directory
            # %LOCALAPPDATA%\Microsoft\WindowsApps.
            # To get rid of these files, disable Python3 and Python in the
            # system settings: "Apps & features"->"App execution aliases"
            set pythonExe [auto_execok "python3"]
            if { $pythonExe eq "" || [file size $pythonExe] == 0 } {
                set pythonExe [auto_execok "python"]
                if { $pythonExe eq "" || [file size $pythonExe] == 0 } {
                    set pythonExe ""
                }
            }
        }
        if { $pythonExe ne "" } {
            catch { eval ::exec $pythonExe --version } pythonVersionString
            set pythonVersion [lindex [split $pythonVersionString] 1]
        }
        return $pythonVersion
    }

    proc SetOsgVersion { version } {
        variable sBuildOpts

        set sBuildOpts(Version,Osg) $version
    }

    proc GetOsgVersion {} {
        variable sBuildOpts

        return $sBuildOpts(Version,Osg)
    }

    proc SetTlsVersion { version } {
        variable sBuildOpts

        set sBuildOpts(Version,Tls) $version
    }

    proc GetTlsVersion {} {
        variable sBuildOpts

        return $sBuildOpts(Version,Tls)
    }

    proc SetImgVersion { version } {
        variable sBuildOpts

        set sBuildOpts(Version,Img) $version
    }

    proc GetImgVersion {} {
        variable sBuildOpts

        return $sBuildOpts(Version,Img)
    }

    proc SetLibraryVersion { libName version } {
        variable sBuildOpts

        set sBuildOpts(Version,$libName) $version
    }

    proc GetLibraryVersion { libName } {
        variable sBuildOpts

        if { [info exists sBuildOpts(Version,$libName)] } {
            return $sBuildOpts(Version,$libName)
        }
        return ""
    }

    proc SetLibraryZipFile { libName zipFile } {
        variable sBuildOpts

        set sBuildOpts(ZipFile,$libName) $zipFile
    }

    proc GetLibraryZipFile { libName } {
        variable sBuildOpts

        if { [info exists sBuildOpts(ZipFile,$libName)] } {
            return $sBuildOpts(ZipFile,$libName)
        }
        return ""
    }

    proc SetTclDir { dir } {
        variable sBuildOpts

        set sBuildOpts(TclDir) $dir
    }

    proc SetPythonDir { dir } {
        variable sBuildOpts

        set sBuildOpts(PythonDir) $dir
    }

    proc GetTclDir {} {
        variable sBuildOpts

        return $sBuildOpts(TclDir)
    }

    proc GetPythonDir {} {
        variable sBuildOpts

        return $sBuildOpts(PythonDir)
    }

    proc GetTclLibDir {} {
        return [file join [GetTclDir] "lib"]
    }

    proc GetPythonLibDir {} {
        return [file join [GetPythonDir] "lib"]
    }

    proc GetDevTclLibDir {} {
        return [file join [GetOutputDevDir] [GetTclLibDir]]
    }

    proc GetDevPythonLibDir {} {
        return [file join [GetOutputDevDir] [GetPythonLibDir]]
    }

    proc GetTclBinDir {} {
        return [file join [GetTclDir] "bin"]
    }

    proc GetPythonBinDir {} {
        return [GetPythonDir]
    }

    proc GetDevTclBinDir {} {
        return [file join [GetOutputDevDir] [GetTclBinDir]]
    }

    proc GetDevPythonBinDir {} {
        return [file join [GetOutputDevDir] [GetPythonBinDir]]
    }

    proc GetTclIncDir {} {
        return [file join [GetTclDir] "include"]
    }

    proc GetPythonIncDir {} {
        return [file join [GetPythonDir] "include"]
    }

    proc GetDevTclDir {} {
        return [file join [GetOutputDevDir] [GetTclDir]]
    }

    proc GetDevPythonDir {} {
        return [file join [GetOutputDevDir] [GetPythonDir]]
    }

    proc GetDevTclIncDir {} {
        return [file join [GetOutputDevDir] [GetTclIncDir]]
    }

    proc GetDevPythonIncDir {} {
        return [file join [GetOutputDevDir] [GetPythonIncDir]]
    }

    proc _GetTclTkLibName { tclOrTk libVersion } {
        set debugSuffix ""
        if { [IsDebugBuild] && [IsWindows] } {
            if { [string match "*tk*" $tclOrTk] } {
                set tclOrTkVersion [GetMajor [GetTkVersion]]
            } else {
                set tclOrTkVersion [GetMajor [GetTclVersion]]
            }
            if { $tclOrTkVersion < 9 } {
                set debugSuffix "g"
            }
        }
        set libName [format "%s%s%s" $tclOrTk [GetMajorMinor $libVersion] $debugSuffix]
        return $libName
    }

    proc GetTclLibName { libVersion { stub "" } } {
        return [_GetTclTkLibName "tcl$stub" $libVersion]
    }

    proc GetTkLibName { libVersion { stub "" } } {
        set name "tk$stub"
        if { ( [GetMajor [GetTclVersion]] >= 9 ) && ( [GetMajor [GetTkVersion]] >= 9 ) } {
            set name [format "tcl%dtk%s" [GetMajor [GetTclVersion]] $stub]
        }
        return [_GetTclTkLibName $name $libVersion]
    }

    proc GetTclStubLib { libVersion { compilerType "gcc" } } {
        set tclLibDir [GetDevTclLibDir]
        set debugSuf [list ""]
        if { [IsDebugBuild] && [IsWindows] && [GetMajor [GetTclVersion]] < 9 } {
            set debugSuf [list "g" ""]
        }
        # Tcl9 does not add the major and minor version to the file name.
        set versList [list [GetMajorMinor $libVersion] ""]
        foreach suf $debugSuf {
            foreach vers $versList {
                set libName [format "tclstub%s%s" $vers $suf]
                if { $compilerType eq "vs" && [IsWindows] } {
                    set stubName [format "%s.lib"  $libName]
                } else {
                    set stubName [format "lib%s.a" $libName]
                }
                set stubFile [file join $tclLibDir $stubName]
                if { [file exists $stubFile] } {
                    return $stubFile
                }
            }
        }
        ErrorAppend "GetTclStubLib: No Tcl stub file in $tclLibDir found." "FATAL"
    }

    proc GetTkStubLib { libVersion { compilerType "gcc" } } {
        set tclLibDir [GetDevTclLibDir]
        set debugSuf [list ""]
        if { [IsDebugBuild] && [IsWindows] && [GetMajor [GetTkVersion]] < 9 } {
            set debugSuf [list "g" ""]
        }
        # Tcl9 does not add the major and minor version to the file name.
        set versList [list [GetMajorMinor $libVersion] ""]
        foreach suf $debugSuf {
            foreach vers $versList {
                set libName [format "tkstub%s%s" $vers $suf]
                if { $compilerType eq "vs" && [IsWindows] } {
                    set stubName [format "%s.lib"  $libName]
                } else {
                    set stubName [format "lib%s.a" $libName]
                }
                Log "Looking for $stubName" 6 false
                set stubFile [file join $tclLibDir $stubName]
                if { [file exists $stubFile] } {
                    return $stubFile
                }
            }
        }
        ErrorAppend "GetTkStubLib: No Tk stub file in $tclLibDir found." "FATAL"
    }

    proc _GetTclshWishName { tclshOrWish libName { libVersion "" } } {
        set debugSuffix ""
        if { [IsDebugBuild] && [IsWindows] } {
            if { [GetMajor [GetTclVersion]] == 8 && [GetMinor [GetTclVersion]] <= 6 } {
                set debugSuffix "g"
            }
        }
        set threadSuffix ""
        if { [IsWindows] && [UseWinCompiler $libName "vs"] && \
             [GetMajor [GetTclVersion]] == 8 && [GetMinor [GetTclVersion]] < 7 } {
            set threadSuffix "t"
        }
        set staticSuffix ""
        if { [IsWindows] && [string tolower [GetUserConfigValue $libName "Static"]] eq "on" } {
            set staticSuffix "s"
        }
        if { $libVersion eq "" } {
            set versionStr ""
        } else {
            set versionStr [GetMajorMinor $libVersion]
        }
        set name [format "%s%s%s%s%s%s" $tclshOrWish $versionStr $threadSuffix $staticSuffix $debugSuffix [GetExeSuffix]]
        return $name
    }

    proc GetTclshName { { libVersion "" } } {
        return [_GetTclshWishName "tclsh" "Tcl" $libVersion]
    }

    proc GetTclshPath { { libVersion "" } } {
        return [file join [GetOutputDevDir] [GetTclBinDir] [GetTclshName $libVersion]]
    }

    proc GetWishName { { libVersion "" } } {
        return [_GetTclshWishName "wish" "Tk" $libVersion]
    }

    proc GetWishPath { { libVersion "" } } {
        return [file join [GetOutputDevDir] [GetTclBinDir] [GetWishName $libVersion]]
    }

    proc GetItclDir {} {
        foreach libDir { lib lib64 } {
            set dir [glob -nocomplain -type d [GetOutputInstDir]/Tcl/$libDir/itcl*]
            if { [llength $dir] == 1 && [file isdirectory $dir] } {
                return $dir
            }
        }
        ErrorAppend "GetItclDir: No Itcl directory found." "FATAL"
    }

    proc GetPngLibDir {} {
        set libDirs [list \
            "/usr/lib" \
            "/usr/lib64" \
            "/usr/lib/x86_64-linux-gnu" \
            "/usr/lib/aarch64-linux-gnu" \
            "/usr/lib/arm-linux-gnueabihf" \
            "/usr/lib/riscv64-linux-gnu" \
        ]
        foreach libDir $libDirs {
            set fileList [glob -nocomplain -type f $libDir/libpng*.so]
            if { [llength $fileList] != 0 } {
                return $libDir
            }
        }
        ErrorAppend "GetPngLibDir: No PNG library found." "FATAL"
    }

    proc GetOtherArchDir { dir } {
        return "${dir}_OtherArch"
    }

    proc CleanLib { libName } {
        set buildDir [file join [GetOutputBuildDir] $libName]
        set instDir  [file join [GetOutputInstDir] $libName]

        DirDelete $buildDir
        DirDelete $instDir
        if { [file isdirectory [GetOtherArchDir $buildDir]] } {
            DirDelete [GetOtherArchDir $buildDir]

        }
        if { [file isdirectory [GetOtherArchDir $instDir]] } {
            DirDelete [GetOtherArchDir $instDir]
        }
    }

    proc _GetBawtProgressFile { libName } {
        return [file join [GetOutputLogDir] "_${libName}_[GetBuildType].progress"]
    }

    proc _BawtProgressFile { libName { onOff -1 } } {
        set runFile [_GetBawtProgressFile $libName]
        if { $onOff == -1 } {
            return [file exists $runFile]
        } elseif { $onOff == 1 } {
            set retVal [catch {open $runFile "w"} fp]
            if { $retVal != 0 } {
                ErrorAppend "Cannot create progress file $runFile" "FATAL"
                return
            }
            puts $fp ""
            close $fp
        } else {
            file delete -force $runFile
        }
    }

    proc BuildLib { libName libVersion buildTypeList } {
        HaveFatalError false
        if { [info commands Build_$libName] eq "" } {
            ErrorAppend "Library $libName: No Build_$libName command defined." "FATAL"
            return
        }
        if { ! [IsSupportedPlatform $libName] } {
            SetBuildError $libName "[GetPlatforms $libName] only"
            Log [format "End %s: Excluded from build (%s)" $libName [GetBuildError $libName]]
            return -1.0
        }
        if { [GetExcludeOption $libName] ne "" } {
            SetBuildError $libName "Option [GetExcludeOption $libName]"
            Log [format "End %s: Excluded from build (%s)" $libName [GetBuildError $libName]]
            return -1.0
        }
        if { [GetExcludeCompiler $libName] ne "" } {
            SetBuildError $libName "Compiler [GetExcludeCompiler $libName]"
            Log [format "End %s: Excluded from build (%s)" $libName [GetBuildError $libName]]
            return -1.0
        }

        set t1 [clock clicks -milliseconds]

        set retVal 0
        foreach buildType $buildTypeList {
            SetBuildType $buildType
            set buildDir [file join [GetOutputBuildDir] $libName]
            set instDir  [file join [GetOutputInstDir] $libName]
            set devDir   [GetOutputDevDir]
            set distDir  [GetOutputDistDir]

            if { [info commands Env_$libName] ne "" } {
                Env_$libName $libName $libVersion $buildDir $instDir $devDir $distDir
            }
            if { [UseStage "Update" $libName] } {
                set libNeedsUpdate [LibNeedsUpdate $libName]
                set zipFileOrDir [file join [GetInputRootDir] [GetLibZipDir $libName] [GetLibZipFile $libName]]
                if { ! [file isdirectory $buildDir] } {
                    UpdateLib $libName "Build directory not existent"
                } elseif { [file mtime $buildDir] < [file mtime [GetLibBuildFile $libName]] } {
                    UpdateLib $libName "Build file newer than build dir"
                } elseif { [file mtime $buildDir] < [file mtime $zipFileOrDir] } {
                    UpdateLib $libName "Source file newer than build dir"
                } elseif { [_BawtProgressFile $libName] } {
                    UpdateLib $libName "Progress file existent"
                } elseif { $libNeedsUpdate ne "" } {
                    if { ! [string equal -nocase $libNeedsUpdate "All"] || \
                         ( [string equal -nocase $libNeedsUpdate "All"] && [AnyLibUpdated] ) } {
                        UpdateLib $libName "Recursive dependency on $libNeedsUpdate"
                    }
                }
            }
            set retVal 1
            if { ! [IsSimulationMode] } {
                if { [UseStage "Clean" $libName] } {
                    Log "Clean $libName ($buildType)"
                    CleanLib $libName
                }
                if { [UseStage "Test"] } {
                    Log "Test $libName [GetLibVersion $libName]"
                } else {
                    Log "Build $libName [GetLibVersion $libName] ($buildType)"
                }
                if { ! [UseStage "Check"] } {
                    CreateDefaultDirs
                }
                if { [UseStage "Touch"] } {
                    DirTouch $buildDir
                }
                if { [IsBuildStage $libName] } {
                    if { ! [UseStage "Check"] } {
                        DirCreate $buildDir
                        DirCreate $instDir
                    }
                    set logType "Test"
                    if { [UseStage "Test"] } {
                        if { [info commands Test_$libName] eq "" } {
                            SetBuildError $libName "No Test proc"
                            Log [format "End %s: Excluded from test (%s)" $libName [GetBuildError $libName]]
                            return -1.0
                        }
                    } else {
                        set logType "Build"
                        _BawtProgressFile $libName 1
                    }
                    WriteBuildLog $libName false "> Start ${logType}_$libName\n"
                    if { [UseStage "Test"] } {
                        set retVal [eval [list Test_$libName $libName $libVersion $buildDir $instDir $devDir $distDir]]
                    } else {
                        set retVal [eval [list Build_$libName $libName $libVersion $buildDir $instDir $devDir $distDir]]
                    }
                    WriteBuildLog $libName true "\n> End ${logType}_$libName"
                    _WriteMSysStartConsoleFile $libName $buildDir
                    if { ! [UseStage "Test"] } {
                        _BawtProgressFile $libName 0
                    }
                }
            } else {
                Log [format "End %s %s: Simulation mode" $libName [GetLibVersion $libName]]
                return -2.0
            }
        }

        if { $retVal } {
            set t2 [clock clicks -milliseconds]
            set buildTime [expr ($t2-$t1) / 1000.0 / 60.0]
            if { [UseLogTiming] } {
                Log [format "End %s %s: %.2f minutes" $libName [GetLibVersion $libName] $buildTime]
            } else {
                Log [format "End %s %s" $libName [GetLibVersion $libName]]
            }
        } else {
            SetBuildError $libName [GetLastLogMsg]
            Log [format "End %s %s: Excluded from build (%s)" $libName [GetLibVersion $libName] [GetBuildError $libName]]
            set buildTime -1.0
        }
        HaveFatalError false
        return $buildTime
    }

    proc _IsMSysGenerator { generator } {
        return [string match "MSYS*" $generator]
    }

    proc _GetCallCmd { generator } {
        if { [_IsMSysGenerator $generator] || [IsUnix] } {
            return ""
        } else {
            return "CALL "
        }
    }

    proc _GetBuildCount { libName buildInfra } {
        variable sBuildCount

        set key "$libName,[GetBuildType],$buildInfra"
        if { ! [info exists sBuildCount($key)] } {
            set sBuildCount($key) 1
            return ""
        } else {
            incr sBuildCount($key)
            return $sBuildCount($key)
        }
    }

    proc CMakeConfig { libName sourceDir buildDir installDir args } {
        set version [GetCompilerVersion -platform -lib $libName]
        switch -exact -nocase $version {
            "vs2008"  { set generator "Visual Studio 9 2008"  }
            "vs2010"  { set generator "Visual Studio 10 2010" }
            "vs2012"  { set generator "Visual Studio 11 2012" }
            "vs2013"  { set generator "Visual Studio 12 2013" }
            "vs2015"  { set generator "Visual Studio 14 2015" }
            "vs2017"  { set generator "Visual Studio 15 2017" }
            "vs2019"  { set generator "Visual Studio 16 2019" }
            "vs2022"  { set generator "Visual Studio 17 2022" }
            "vs2026"  { set generator "Visual Studio 18 2026" }
            "Windows" { set generator "MSYS Makefiles" }
            "Linux"   { set generator "Unix Makefiles" }
            "SunOS"   { set generator "Unix Makefiles" }
            "Darwin"  { set generator "Unix Makefiles" }
            default   { ErrorAppend "CMakeConfig: Unsupported compiler version \"$version\"" "FATAL" }
        }

        set argStr ""
        foreach arg $args {
            if { [string match "-G*" $arg] } {
                if { [IsWindows] } {
                    set generator [string range $arg 2 end]
                }
            } else {
                append argStr "$arg "
            }
        }

        # Determine CMake version. Starting with version 3.14, the architecture is not part of the
        # generator string anymore, but must be specified via the "-A" command line option.
        set cmakeVersion [GetLibVersion "CMake"]
        if { $cmakeVersion eq "" } {
            ErrorAppend "CMakeConfig: CMake version not specified." "FATAL"
        }
        set isVsGen false
        if { [string match "Visual*" $generator] } {
            set isVsGen true
        }

        set archOpt ""
        if { [VersionCheck $cmakeVersion "newer" "3.14.0"] } {
            if { $isVsGen } {
                if { [Is64Bit] } {
                    set archOpt "-Ax64"
                } else {
                    set archOpt "-AWin32"
                }
            }
        } else {
            if { [Is64Bit] && $isVsGen } {
                append generator " Win64"
            }
        }

        foreach arg [GetUserConfig $libName] {
            if { [string match "-D*" $arg] } {
                append argStr "$arg "
            }
        }

        set CMakeProg [GetCMakeProg]

        Log "CMakeConfig" 2
        Log "CMake program    : $CMakeProg"   4 false
        Log "Source directory : $sourceDir"   4 false
        Log "Build directory  : $buildDir"    4 false
        Log "Install directory: $installDir"  4 false
        Log "Generator        : $generator"   4 false
        Log "Architecture     : $archOpt"     4 false
        if { $argStr ne "" } {
            Log "Configuration    : $argStr"  4 false
        }

        file mkdir "$buildDir" "$installDir"

        set    cmd ""
        append cmd "[_GetCallCmd $generator] $CMakeProg "
        append cmd     "-G\"$generator\" "
        append cmd     "$archOpt "
        if { [GetSdkVersion $libName] ne "" } {
            append cmd "-DCMAKE_SYSTEM_VERSION=\"[GetSdkVersion $libName]\" "
        }
        append cmd     "-DCMAKE_BUILD_TYPE=[GetBuildType] "
        append cmd     "-DCMAKE_VERBOSE_MAKEFILE=1 "
        append cmd     "-DCMAKE_INSTALL_PREFIX:STRING=\"$installDir\" "
        append cmd     "$argStr "
        append cmd     "\"$sourceDir\" "

        set originator "CMakeConfig[_GetBuildCount $libName CMakeConfig]"
        # Call CMake configure twice, as some libraries do not have a 
        # valid configuration after the first call.
        if { [IsUnix] || [_IsMSysGenerator $generator] } {
            MSysRun $libName $originator $buildDir "$cmd"
            MSysRun $libName $originator $buildDir "$cmd"
        } else {
            DosRun $libName $originator $buildDir "$cmd"
            DosRun $libName $originator $buildDir "$cmd"
        }
    }

    proc CMakeBuild { libName buildDir cmakeTarget buildConfig args } {
        Log "CMakeBuild" 2
        Log "Build directory : $buildDir"    4 false
        Log "Build target    : $cmakeTarget" 4 false
        Log "Configuration   : $buildConfig" 4 false

        set generator ""
        foreach arg $args {
            if { [IsWindows] && [string match "-G*" $arg] } {
                set generator [string range $arg 2 end]
            }
        }
        set CMakeProg [GetCMakeProg]
        set    cmd ""
        append cmd "[_GetCallCmd $generator] $CMakeProg "
        append cmd     "--build . "
        append cmd     "--target \"$cmakeTarget\" "
        append cmd     "--config \"$buildConfig\" "
        set numJobs [GetNumJobs $libName [GetWinCompiler $libName]]
        if { [IsUnix] || [_IsMSysGenerator $generator] } {
            if { $numJobs > 1 } {
                append cmd "-- -j $numJobs "
            }
        } else {
            if { $numJobs > 1 && [_SupportsParallelBuild] } {
                append cmd "-- /m:$numJobs /p:BuildInParallel=true "
            }
        }

        set originator "CMakeBuild[_GetBuildCount $libName CMakeBuild]"
        if { [IsUnix] || [_IsMSysGenerator $generator] } {
            MSysRun $libName $originator $buildDir "$cmd"
        } else {
            DosRun $libName $originator $buildDir "$cmd"
        }
    }

    proc NMakeTest { libName buildDir makeFile buildTarget { makeFlags "" } } {
        if { $makeFlags eq "" } {
            set makeFlags "TCLLIBPATH=[GetDevTclLibDir]"
        }
        Log "NMakeTest" 2
        Log "Build directory: $buildDir"     4 false
        Log "Build target   : $buildTarget"  4 false
        if { $makeFlags ne "" } {
            Log "Make flags     : $makeFlags"  4 false
        }

        set    cmd ""
        append cmd "CALL nmake.exe "
        append cmd     "/nologo "
        append cmd     "/f \"$makeFile\" "
        append cmd     "$makeFlags $buildTarget "

        set originator "NMakeTest[_GetBuildCount $libName NMakeTest]"
        DosRun $libName $originator $buildDir "$cmd"
    }

    proc NMakeBuild { libName sourceDir makeFile args } {
        Log "NMakeBuild" 2
        Log "Source directory: $sourceDir"  4 false
        Log "Makefile        : $makeFile"   4 false
        if { [llength $args] > 0 } {
            Log "Options         : $args"   4 false
        }

        set    cmd ""
        append cmd "CALL nmake.exe "
        append cmd     "/nologo "
        append cmd     "/f \"$makeFile\" "
        append cmd     "$args "

        set originator "NMakeBuild[_GetBuildCount $libName NMakeBuild]"
        DosRun $libName $originator $sourceDir "$cmd"
    }

    proc MsBuild { libName sourceDir slnFile configuration { target "" } { buildPlatform "" } } {
        Log "MsBuild" 2
        Log "Source directory: $sourceDir"     4 false
        Log "Solution file   : $slnFile"       4 false
        Log "Configuration   : $configuration" 4 false
        if { $target ne "" } {
            Log "Build target    : $target"    4 false
        }

        set cmd ""
        append cmd "CALL MsBuild.exe "
        append cmd    "\"$slnFile\" "
        append cmd    "/v:detailed "
        append cmd    "/p:Configuration=\"$configuration\" "
        if { $buildPlatform eq "" } {
            set buildPlatform [GetMsBuildPlatform]
        }
        append cmd    "/p:Platform=\"$buildPlatform\" "
        set numJobs [GetNumJobs $libName [GetWinCompiler $libName]]
        if { $numJobs > 1 && [_SupportsParallelBuild] } {
            append cmd "/m:$numJobs /p:BuildInParallel=true "
        }
        if { $target ne "" } {
            append cmd "/t:\"$target\" "
        }

        set originator "MsBuild[_GetBuildCount $libName MsBuild]"
        DosRun $libName $originator $sourceDir "$cmd"
    }

    proc _LogCmd { cmd { sep " -" } } {
        foreach line [split $cmd ";"] {
            if { [string first $sep $line] >= 0 } {
                set sepInd1 [string first $sep $line]
                set sepInd2 $sepInd1
                Log "> [string trim [string range $line 0 $sepInd1]]" 4 false
                while { [string first $sep $line [expr $sepInd1 + 2]] >= 0 } {
                    set sepInd2 [string first $sep $line [expr $sepInd1 +2]]
                    Log "  [string trim [string range $line $sepInd1 $sepInd2]]" 4 false
                    set sepInd1 $sepInd2
                }
                Log "  [string trim [string range $line $sepInd2 end]]" 4 false
            } else {
                Log "> [string trim $line]" 4 false
            }
        }
    }
    
    proc _WriteMSysStartConsoleFile { libName buildDir } {
        if { [IsWindows] } {
            set batchFile [file join $buildDir "_Bawt_StartMSysConsole.bat"]
            Log "WriteConsoleFile" 2 false
            Log "File: $batchFile" 4 false
            set retVal [catch {open $batchFile "w"} fp]
            if { $retVal != 0 } {
                ErrorAppend "Cannot write to file $batchFile" "FATAL"
            }
            puts $fp "SET HOME=[file nativename $buildDir]"
            puts $fp "[GetMSysConsole]"
            close $fp
        }
    }

    proc _WriteMSysBatchFile { libName buildDir batchPrefix cmd } {
        set buildDirMSys [MSysPath $buildDir]
        if { [IsUnix] } {
            set batchFile "$batchPrefix.sh"
        } else {
            set batchFile "$batchPrefix.bat"
        }
        set batchPath [file join $buildDir $batchFile]
        Log "Batch file     : $batchPath"  4 false
        set retVal [catch {open $batchPath "w"} fp]
        if { $retVal != 0 } {
            ErrorAppend "Cannot write to file $batchPath" "FATAL"
            return ""
        }
        puts $fp "#!/bin/bash"
        if { [IsWindows] && [UseMSys2] } {
            puts $fp "export MSYSTEM=MINGW32"
            puts $fp "export HOME=[MSysPath $buildDir]"
        }
        puts $fp "export PATH=\"[GetPathes unix]\$PATH\""
        if { [IsUnix] } {
            puts $fp "export LD_LIBRARY_PATH=\"[GetOutputDevDir]/lib:\$LD_LIBRARY_PATH\""
        }
        puts $fp "export TCLLIBPATH=\"\$TCLLIBPATH [MSysPath [GetDevTclLibDir]]\""
        puts $fp "export PKG_CONFIG_PATH=\"[MSysPath [GetOutputDevDir]/bin]\""
        puts $fp "export BAWT_DEV_DIR=\"[MSysPath [GetOutputDevDir]]\""
        foreach { envVar envVal } [GetUseEnvVars] {
            puts $fp "export $envVar=\"$envVal\""
        }
        puts $fp "pushd $buildDirMSys"
        puts $fp "$cmd"
        puts $fp "retVal=\$?"
        puts $fp "popd"
        if { [GetIgnoreBuildError $libName] } {
            puts $fp "echo \$retVal"
        } else {
            puts $fp "exit \$retVal"
        }
        close $fp
        return $batchPath
    }

    proc _WriteDosBatchFile { libName buildDir batchPrefix cmd vcvarsProg vcvarsParam } {
        if { [IsUnix] } {
            return [_WriteMSysBatchFile $libName $buildDir $batchPrefix $cmd]
        }

        set batchFile "$batchPrefix.bat"
        set batchPath [file join $buildDir $batchFile]
        Log "Batch file     : $batchPath"  4 false
        set retVal [catch {open $batchPath "w"} fp]
        if { $retVal != 0 } {
            ErrorAppend "Cannot write to file $batchPath" "FATAL"
            return ""
        }
        puts $fp "SETLOCAL"
        if { $vcvarsProg ne "" } {
            puts $fp "CALL \"$vcvarsProg\" $vcvarsParam"
        }
        puts $fp "SET Path=[GetPathes win]%Path%"
        foreach { envVar envVal } [GetUseEnvVars] {
            puts $fp "SET $envVar=$envVal"
        }
        puts $fp "PUSHD \"$buildDir\""
        puts $fp "$cmd"
        puts $fp "SET retVal=%errorlevel%"
        puts $fp "POPD"
        if { [GetIgnoreBuildError $libName] } {
            puts $fp "ECHO %retVal%"
        } else {
            puts $fp "EXIT %retVal%"
        }
        close $fp
        return $batchPath
    }

    proc MSysRun { libName originator buildDir cmd } {
        variable sOpts
        set buildDirMSys [MSysPath $buildDir]

        Log "MSysRun (Caller: $originator)" 2
        Log "Shell          : [GetMSysShell]"  4 false
        if { $buildDir ne "" } {
            Log "Build directory: $buildDirMSys"   4 false
        }

        set status "OK"
        set result "FATAL"

        if { $buildDir ne "" } {
            set batchFile [_WriteMSysBatchFile $libName $buildDir "_Bawt_MSysRun_$originator" $cmd]
        }
        _LogCmd $cmd " --"

        try {
            if { $buildDir ne "" } {
                set curDir [pwd]
                cd $buildDir
                if { [IsWindows] } {
                    set result [eval exec [GetCmdShell] /d /c [GetMSysShell] --login [list $batchFile]]
                } else {
                    set result [eval exec [GetMSysShell] [list $batchFile]]
                }
                cd $curDir
            } else {
                if { [IsWindows] } {
                    set result [eval exec [GetCmdShell] /d /c [GetMSysShell] --login -i -c [list $cmd]]
                } else {
                    set result [eval exec [GetMSysShell] -c [list $cmd]]
                }
            }
        } trap NONE result {
            # $result now holds the message that was written to stderr and everything written to stdout
            append status " (Messages have been written to stderr)"
        } trap CHILDSTATUS { - opts } {
            # process $pid exited with non-zero exit code $status
            lassign [dict get $opts -errorcode] -> pid status
            set errorMsg [dict get $opts -errorinfo]
            ErrorAppend "MSysRun: Error running command:\n$cmd\nError message:\n$errorMsg" $result
        }

        Log "Status: $status" 4 false
        WriteBuildLog $libName true $result
        return $result
    }

    proc MSysTest { libName buildDir buildTarget { makeFlags "" } } {
        set buildDirMSys [MSysPath $buildDir]

        if { $makeFlags eq "" } {
            set makeFlags "TCLLIBPATH=[MSysPath [GetDevTclLibDir]]"
        }
        Log "MSysTest" 2
        Log "Build directory: $buildDirMSys"  4 false
        Log "Build target   : $buildTarget"   4 false
        if { $makeFlags ne "" } {
            Log "Make flags     : $makeFlags"  4 false
        }

        set cmd ""
        append cmd "cd $buildDirMSys ; "
        append cmd "[GetMakeProg] $makeFlags $buildTarget "

        set originator "MSysTest[_GetBuildCount $libName MSysTest]"
        MSysRun $libName $originator $buildDir "$cmd"
    }

    proc MSysBuild { libName buildDir buildTarget { buildFlags "" } } {
        set buildDirMSys [MSysPath $buildDir]

        Log "MSysBuild" 2
        Log "Build directory: $buildDirMSys"  4 false
        Log "Build target   : $buildTarget"   4 false
        if { $buildFlags ne "" } {
            Log "Build flags   : $buildFlags"  4 false
        }

        set cmd ""
        append cmd "cd $buildDirMSys ; "
        if { $buildFlags ne "" } {
            append cmd "$buildFlags "
        }
        set numJobs [GetNumJobs $libName [GetWinCompiler $libName]]
        if { $numJobs > 1 } {
            append cmd "[GetMakeProg] -j $numJobs ; "
        } else {
            append cmd "[GetMakeProg] ; "
        }
        append cmd "[GetMakeProg] $buildTarget "

        set originator "MSysBuild[_GetBuildCount $libName MSysBuild]"
        MSysRun $libName $originator $buildDir "$cmd"
    }

    proc MSysConfig { libName buildDir installDir { cflags "" } args } {
        set buildDirMSys     [MSysPath $buildDir]
        set instDirMSys      [MSysPath $installDir]
        set rootBuildDirMSys [MSysPath [GetOutputBuildDir]]

        Log "MSysConfig" 2
        Log "Build directory  : $buildDir"   4 false
        Log "Install directory: $installDir" 4 false

        set cmd ""
        append cmd "$cflags "
        append cmd "$buildDirMSys/configure "
        append cmd     "--prefix=$instDirMSys --exec-prefix=$instDirMSys "
        if { [IsWindows] } {
            append cmd "--build=[GetMingwVersion] "
        }
        if { [IsDebugBuild] } {
            append cmd "--enable-symbols "
        } else {
            append cmd "--disable-symbols "
        }
        foreach arg $args {
            append cmd " $arg "
        }

        set originator "MSysConfig[_GetBuildCount $libName MSysConfig]"
        MSysRun $libName $originator $buildDir "$cmd"
    }

    proc TeaConfigTcl { libName buildDir installDir { cflags "" } args } {
        _TeaConfig "Tcl" $libName $buildDir $installDir $cflags {*}$args
    }

    proc TeaConfigTk { libName buildDir installDir { cflags "" } args } {
        _TeaConfig "Tk" $libName $buildDir $installDir $cflags {*}$args
    }

    proc TeaConfig { libName buildDir installDir { cflags "" } args } {
        _TeaConfig "Tk" $libName $buildDir $installDir $cflags {*}$args
    }

    proc _TeaConfig { type libName buildDir installDir { cflags "" } args } {
        set buildDirMSys     [MSysPath $buildDir]
        set instDirMSys      [MSysPath $installDir]
        set rootBuildDirMSys [MSysPath [GetOutputBuildDir]]

        Log "TeaConfig" 2
        Log "Build directory  : $buildDir"   4 false
        Log "Install directory: $installDir" 4 false

        if { [string trim $cflags] eq "" } {
            if { [GetDarwinCFlags] ne "" } {
                set cflags "CFLAGS='[GetDarwinCFlags]' "
            }
        }
        set cmd ""
        append cmd "$cflags "
        append cmd "$buildDirMSys/configure "
        append cmd     "--enable-shared "
        append cmd     "--enable-threads "
        append cmd     "--prefix=$instDirMSys --exec-prefix=$instDirMSys "
        append cmd     "--with-tcl=$rootBuildDirMSys/Tcl "
        if { $type eq "Tk" } {
            append cmd "--with-tk=$rootBuildDirMSys/Tk "
        }
        if { [IsWindows] } {
            append cmd "--build=[GetMingwVersion] "
        }
        if { [Is64Bit] } {
            append cmd "--enable-64bit "
        }
        if { [IsDebugBuild] } {
            append cmd "--enable-symbols "
        } else {
            append cmd "--disable-symbols "
        }
        append cmd "$args "

        set originator "TeaConfig[_GetBuildCount $libName TeaConfig]"
        MSysRun $libName $originator $buildDir "$cmd"
    }

    proc NeedDll2Lib { libName } {
        if { [UseWinCompiler $libName "gcc"] && [UseVisualStudio] } {
            return true
        }
        return false
    }

    proc Dll2Lib { libName dllDir dllFile defFile libFile } {
        # On Unix or if creating import libs is disabled, do nothing.
        if { ! [IsWindows] || ! [CreateImportLibs] } {
            return
        }
        # If Windows, but not using VisualStudio, print out a warning.
        if { ! [UseVisualStudio] } {
            ErrorAppend "Dll2Lib $libFile: Creating import libraries needs VisualStudio." "Warning"
            return
        }

        Log "Dll2Lib" 2
        Log "Directory: $dllDir"  4 false
        Log "DLL file : $dllFile" 4 false
        Log "Def file : $defFile" 4 false
        Log "Lib file : $libFile" 4 false

        set GendefProg [GetGendefProg]

        if { ! [file executable $GendefProg] } {
            ErrorAppend "Dll2Lib: gendef program not found." "FATAL"
        }

        set exitOnError [expr [IsWindows]]
        set dllFile [FindFile $dllDir $dllFile $exitOnError]

        set dllFullPath [file join $dllDir $dllFile]
        if { ! [file exists $dllFullPath] } {
            ErrorAppend "Dll2Lib: DLL $dllFullPath not found." "FATAL"
        }

        set cmd ""
        append cmd "CALL $GendefProg - \"$dllFile\" > \"$defFile\"\n"
        append cmd "CALL lib.exe "
        append cmd     "/def:[file nativename $defFile] "
        append cmd     "/out:[file nativename $libFile] "
        append cmd     "/machine:[GetArchitecture] "

        DosRun $libName "Dll2Lib" $dllDir "$cmd"
    }

    proc GetVSEditions {} {
        return { "Ultimate" "Professional" "Community" "Preview" "Insiders" }
    }

    proc SetVSRuntimeLibDir { dir } {
        variable sOpts

        if { ! [file isdirectory $dir] } {
            ErrorAppend "SetVSRuntimeLibDir: Specified directory $dir not found." "FATAL"
        }
        set sOpts(VSRuntimeLibDir) [file normalize $dir]
    }

    proc _FindVSRuntimeDir { version versionNum } {
        set pathTemplate "C:/%s/Microsoft Visual Studio/%s/%s/VC/Redist/MSVC"
        foreach edition [GetVSEditions] {
            foreach progFiles [list "Program Files (x86)" "Program Files"] {
                set path1 [format $pathTemplate $progFiles $version $edition]
                if { [file isdirectory $path1] } {
                    set dirList [lindex [GetDirList $path1 1 0  0 0] 0]
                    foreach dir $dirList {
                        set path2 [file join $dir [GetArchitecture]]
                        if { [file isdirectory $path2] } {
                            set path3 [file join $path2 "Microsoft.VC$versionNum.CRT"]
                            if { [file isdirectory $path3] } {
                                return $path3
                            }
                        }
                    }
                }
            }
        }
        return ""
    }

    proc GetVSRuntimeLibDir {} {
        variable sOpts

        if { [info exists sOpts(VSRuntimeLibDir)] } {
            set libPath $sOpts(VSRuntimeLibDir)
        } else {
            # If path to VS runtime libraries has not been specified with SetVSRuntimeLibDir,
            # use some heuristics to find the corresponding directory.
            set path "C:/Program Files (x86)/Microsoft Visual Studio %s/VC/redist/%s/Microsoft.VC%s.CRT"
            set version [GetCompilerVersion -vs]
            switch -exact -nocase $version {
                "vs2008"  { set version  "9.0" ; set versionNum  "90" }
                "vs2010"  { set version "10.0" ; set versionNum "100" }
                "vs2012"  { set version "11.0" ; set versionNum "110" }
                "vs2013"  { set version "12.0" ; set versionNum "120" }
                "vs2015"  { set version "14.0" ; set versionNum "140" }
                "vs2017"  { return [_FindVSRuntimeDir "2017" "141"] }
                "vs2019"  { return [_FindVSRuntimeDir "2019" "142"] }
                "vs2022"  { return [_FindVSRuntimeDir "2022" "143"] }
                "vs2026"  { return [_FindVSRuntimeDir   "18" "145"] }
                "Windows" { return "" }
                default   { ErrorAppend "GetVSRuntimeLibDir: Unsupported compiler version \"$version\"" "FATAL" }
            }
            set libPath [format $path $version [GetArchitecture] $versionNum]
        }
        if { ! [file isdirectory $libPath] } {
            ErrorAppend "GetVSRuntimeLibDir: Runtime directory $libPath not found." "Warning"
            return ""
        }
        return $libPath
    }

    proc UseVisualStudio { { type "" } } {
        set count 1
        foreach version [GetCompilerVersions] {
            if { [string match "vs*" $version] } {
                if { $type eq "" || ( $count == 1 && $type eq "primary" ) } {
                    return true
                }
                if { $type eq "" || ( $count == 2 && $type eq "secondary" ) } {
                    return true
                }
            }
            incr count
        }
        return false
    }

    proc IsGccCompilerNewer { gccVersion } {
        if { [VersionCheck [GetGccCompilerVersion] "newer" $gccVersion] } {
            return true
        } else {
            return false
        }
    }

    proc GetMscVer {} {
        set version [GetCompilerVersion -vs]
        switch -exact -nocase $version {
            "vs2008" { set version "1500" }
            "vs2010" { set version "1600" }
            "vs2012" { set version "1700" }
            "vs2013" { set version "1800" }
            "vs2015" { set version "1900" }
            "vs2017" { set version "1910" }
            "vs2019" { set version "1920" }
            "vs2022" { set version "1930" }
            "vs2026" { set version "1940" }
            default  { ErrorAppend "GetMscVer: Unsupported compiler version \"$version\"" "FATAL" }
        }
        return $version
    }

    proc SetVcvarsProg { vcvarsProg } {
        variable sOpts

        if { ! [file isfile $vcvarsProg] || ! [file executable $vcvarsProg] } {
            ErrorAppend "SetVcvarsProg: Batch file vcvarsall.bat not found ($vcvarsProg)." "FATAL"
        }
        set sOpts(VcvarsProg) [file normalize $vcvarsProg]
    }

    proc GetVcvarsProg {} {
        variable sOpts

        if { [info exists sOpts(VcvarsProg)] } {
            return $sOpts(VcvarsProg)
        }

        # If path to vsvarsall.bat has not been specified with SetVcvarsProg,
        # use some heuristics to find the batch script.
        set vcvarsProg ""
        set pathTemplate(1) "C:/Program Files (x86)/Microsoft Visual Studio %s/VC/vcvarsall.bat"
        set pathTemplate(2) "C:/%s/Microsoft Visual Studio/%s/%s/VC/Auxiliary/Build/vcvarsall.bat"
        set version [GetCompilerVersion -vs]
        switch -exact -nocase $version {
            "vs2008" { set version  "9.0" ; set template 1 }
            "vs2010" { set version "10.0" ; set template 1 }
            "vs2012" { set version "11.0" ; set template 1 }
            "vs2013" { set version "12.0" ; set template 1 }
            "vs2015" { set version "14.0" ; set template 1 }
            "vs2017" { set version "2017" ; set template 2 }
            "vs2019" { set version "2019" ; set template 2 }
            "vs2022" { set version "2022" ; set template 2 }
            "vs2026" { set version   "18" ; set template 2 }
            default  { ErrorAppend "GetVcvarsProg: Unsupported compiler version \"$version\"" "FATAL" }
        }
        if { $template == 1 } {
            set vcvarsProg [format $pathTemplate(1) $version]
            if { ! [file isfile $vcvarsProg] || ! [file executable $vcvarsProg] } {
                ErrorAppend "GetVcvarsProg: Batch file vcvarsall.bat not found ($vcvarsProg)." "FATAL"
            }
        } elseif { $template == 2 } {
            set found false
            foreach edition [GetVSEditions] {
                foreach progFiles [list "Program Files (x86)" "Program Files"] {
                    set vcvarsProg [format $pathTemplate(2) $progFiles $version $edition]
                    if { [file isfile $vcvarsProg] && [file executable $vcvarsProg] } {
                        return $vcvarsProg
                    }
                }
            }
            if { ! $found } {
                ErrorAppend "GetVcvarsProg: Batch file vcvarsall.bat not found ($vcvarsProg)." "FATAL"
            }
        } else {
            ErrorAppend "GetVcvarsProg: Unsupported template $template." "FATAL"
        }
        return $vcvarsProg
    }

    proc DosRun { libName originator buildDir cmd args } {
        set removeBatch false
        if { [lsearch -exact $args "-removebatch"] >= 0 } {
            set removeBatch true
        }
        set noLog false
        if { [lsearch -exact $args "-nolog"] >= 0 } {
            set noLog true
        }
        set useVS true
        if { [lsearch -exact $args "-novs"] >= 0 } {
            set useVS false
        }
        if { $noLog } {
            set logLevel [GetLogLevel]
            SetLogLevel 0 false
        }
        Log "DosRun (Caller: $originator)"  2
        Log "Shell          : [GetCmdShell]"  4 false
        if { [llength $args] > 0 } {
            Log "Options        : $args"  4 false
        }

        if { [IsWindows] } {
            set vcvarsProg ""
            set vcvarsParam ""
            if { $useVS } {
                set vcvarsProg [GetVcvarsProg]
                if { [Is32Bit] } {
                    set vcvarsParam "x86 [GetSdkVersion $libName]"
                } else {
                    set vcvarsParam "x86_amd64 [GetSdkVersion $libName]"
                }
                Log "Environment    : $vcvarsProg $vcvarsParam" 4 false
            }
            set batchFile [_WriteDosBatchFile $libName $buildDir "_Bawt_DosRun_$originator" $cmd $vcvarsProg $vcvarsParam]
 
        } else {
            set batchFile [_WriteMSysBatchFile $libName $buildDir "_Bawt_DosRun_$originator" $cmd]
        }

        Log "Build directory: $buildDir"  4 false
        _LogCmd $cmd

        set status "OK"
        set result "FATAL"
        try {
            set curDir [pwd]
            cd $buildDir
            if { [IsWindows] } {
                set result [eval exec [GetCmdShell] /d /c [list $batchFile]]
            } else {
                set result [eval exec [GetCmdShell] [list $batchFile]]
            }
            cd $curDir
        } trap NONE result {
            # $result now holds the message that was written to stderr and everything written to stdout
            append status " (Messages have been written to stderr)"
        } trap CHILDSTATUS { - opts } {
            # process $pid exited with non-zero exit code $status
            lassign [dict get $opts -errorcode] -> pid status
            set errorMsg [dict get $opts -errorinfo]
            ErrorAppend "DosRun: Error running command:\n$cmd\nError message:\n$errorMsg" $result
        }
        Log "Status: $status" 4 false
        WriteBuildLog $libName true $result
        if { $noLog } {
            SetLogLevel $logLevel false
        }
        if { $removeBatch } {
            file delete -force $batchFile
        }
    }
}

namespace eval BawtMain {

    namespace export SetBawtUrl GetBawtUrl
    namespace export SetStartTime GetTotalTime
    namespace export SetBuildTime GetBuildTime
    namespace export SetBuildError GetBuildError
    namespace export SetIgnoreBuildError GetIgnoreBuildError
    namespace export SetWorkingSet AppendToWorkingSet GetWorkingSet
    namespace export SortLibsByDependencies SortLibsByDictionary
    namespace export GetLibs GetNumLibs GetLibName GetLibNumber GetLibIndex
    namespace export AppendLib PrintLibNames
    namespace export GenerateHtml
    namespace export GetUsageMsg PrintUsage GetVersion PrintVersion SetRemoteVersion
    namespace export HaveLibZipFile GetLibZipFile SetLibZipFile
    namespace export HaveLibBuildFile GetLibBuildFile SetLibBuildFile
    namespace export GetSetupFile SetSetupFile
    namespace export GetInputLibsDirs GetBawtLibInputDir AddInputLibsDir
    namespace export GetLibZipDir SetLibZipDir
    namespace export GetLibVersion SetLibVersion
    namespace export GetLibHomepage SetLibHomepage
    namespace export GetLibDependencies SetLibDependencies
    namespace export PrintLibDependency
    namespace export GetPlatforms SetPlatforms GetValidPlatforms IsSupportedPlatform
    namespace export GetWinCompilers SetWinCompilers
    namespace export GetSdkVersion SetSdkVersion
    namespace export GetScriptAuthorName GetScriptAuthorMail SetScriptAuthor
    namespace export CreateImportLibs CopyRuntimeLibs StripLibs 
    namespace export UseUserBuildFiles SetUserBuildFile GetUserBuildFile
    namespace export UseOnlineRepository
    namespace export GetEnvVars SetEnvVar AddEnvVar AddToPathEnv
    namespace export GetUseEnvVars UseEnvVar
    namespace export AppendBuildType GetBuildTypes
    namespace export SetExcludeOption GetExcludeOption
    namespace export SetBuildOptions GetBuildOptions
    namespace export SetExcludeCompiler GetExcludeCompiler
    namespace export SetWinCompiler GetWinCompiler UseWinCompiler
    namespace export SetInputRootDir GetInputRootDir
    namespace export SetInputResourceDir GetInputResourceDir
    namespace export SetOutputRootDir GetOutputRootDir
    namespace export SetOutputToolsDir GetOutputToolsDir
    namespace export GetBootstrapDir
    namespace export ExtractLibrary
    namespace export AddPath GetPathes SetPathes
    namespace export ModificationTime GetModificationTime
    namespace export HashKey GetHashKey
    namespace export GetVersionFromFileName
    namespace export AddIncludePath Include Setup
    namespace export PrintSummary

    proc _Init {} {
        variable sOpts

        SetInputRootDir    [file dirname [info script]]
        SetOutputRootDir   [file join [pwd] "BawtBuild"]

        CreateImportLibs    true
        CopyRuntimeLibs     true
        StripLibs           true
        UseOnlineRepository true
        UseUserBuildFiles   true
        GenerateHtml        false

        if { $::tcl_platform(pointerSize) == 8 } {
            SetArchitecture "x64"
        } else {
            SetArchitecture "x86"
        }
        SetCompilerVersions "gcc"
        SetStartTime
        SetSetupFile ""
        AddIncludePath      [file join [GetInputRootDir] "Setup"] false
        AddInputLibsDir     [file join [GetInputRootDir] "InputLibs"] false
        AddInputLibsDir     [file join [pwd] "InputLibs"] false
        SetInputResourceDir [file join [GetInputRootDir] "Resources"]
        SetBawtUrl          "https://www.tcl3d.org/bawt/download"

        set sOpts(EnvVarPath) [list]
        set sOpts(Path)       [list]
        set sOpts(WorkingSet) [list]
        set sOpts(LibNames)   [list]
        set sOpts(BuildTypes,ForceBuildType) [list]
    }

    proc GetBawtUrl {} {
        variable sOpts

        return $sOpts(BawtUrl)
    }

    proc SetBawtUrl { url } {
        variable sOpts

        set sOpts(BawtUrl) $url
    }

    proc SetStartTime {} {
        variable sOpts

        set sOpts(StartTime) [clock clicks -milliseconds]
    }

    proc GetTotalTime {} {
        variable sOpts

        return [expr [clock clicks -milliseconds] - $sOpts(StartTime)]
    }

    proc SetBuildTime { libName buildTime } {
        variable sOpts

        set sOpts(BuildTime,$libName) $buildTime
    }

    proc GetBuildTime { libName } {
        variable sOpts

        if { [info exists sOpts(BuildTime,$libName)] } {
            return $sOpts(BuildTime,$libName)
        } else {
            return -1.0
        }
    }

    proc SetBuildError { libName buildError } {
        variable sOpts

        set sOpts(BuildError,$libName) $buildError
    }

    proc GetBuildError { libName } {
        variable sOpts

        if { [info exists sOpts(BuildError,$libName)] } {
            return $sOpts(BuildError,$libName)
        } else {
            return ""
        }
    }

    proc SetIgnoreBuildError { libName onOff } {
        variable sOpts

        set sOpts(IgnoreBuildError,$libName) $onOff
    }

    proc GetIgnoreBuildError { libName } {
        variable sOpts

        if { [info exists sOpts(IgnoreBuildError,$libName)] } {
            return $sOpts(IgnoreBuildError,$libName)
        } else {
            return false
        }
    }

    proc SetWorkingSet { libNameList } {
        variable sOpts

        set sOpts(WorkingSet) $libNameList
    }

    proc AppendToWorkingSet { libName } {
        variable sOpts

        lappend sOpts(WorkingSet) $libName
    }

    proc GetWorkingSet {} {
        variable sOpts

        return $sOpts(WorkingSet)
    }

    proc _lremove { list item } {
        return [lsearch -all -inline -not -exact -nocase $list $item]
    }

    proc SortLibsByDictionary {} {
        variable sOpts

        Log "SortLibsByDictionary"

        set sOpts(LibNames) [lsort -dictionary [GetLibs]]
    }

    proc _GetLibDependency { libName { level 0 } } {
        variable sDirectDependencies
        variable sIndirectDependencies

        set depList [list]
        foreach lib [GetLibs] {
            set dependencies [GetLibDependencies $lib]
            if { [lsearch -exact -nocase $dependencies $libName] >= 0 } {
                lappend depList $lib
            }
        }
        if { $level == 0 } {
            set sDirectDependencies $depList
            set sIndirectDependencies [list]
        }
        foreach lib $depList {
            if { $level > 0 && \
                [lsearch -exact -nocase $sDirectDependencies $lib] < 0 && \
                [lsearch -exact -nocase $sIndirectDependencies $lib] < 0 } {
                lappend sIndirectDependencies $lib
            }
        }
        if { [llength $lib] > 0 } {
            foreach lib $depList {
                _GetLibDependency $lib [expr $level + 1]
            }
        }
    }

    proc PrintLibDependency { libName } {
        variable sDirectDependencies
        variable sIndirectDependencies

        if { [GetLibIndex $libName] < 0 } {
            puts "Invalid library \"$libName\" specified."
            return
        }
        _GetLibDependency $libName

        puts "Libraries directly depending on $libName:"
        foreach lib [lsort -dictionary $sDirectDependencies] {
            puts "  $lib"
        }
        puts "Libraries recursively depending on $libName:"
        foreach lib [lsort -dictionary $sIndirectDependencies] {
            puts "  $lib"
        }
    }

    proc SortLibsByDependencies {} {
        variable sOpts

        Log "SortLibsByDependencies"

        set sortedList [list]
        set lastList   [list]

        foreach libName [GetLibs] {
            set dependencies [GetLibDependencies $libName]
            if { [lsearch -exact -nocase $dependencies $libName] >= 0 } {
                ErrorAppend "$libName depends on itself" "FATAL"
            }
            if { [lsearch -exact -nocase $dependencies "All"] >= 0 } {
                lappend lastList $libName
            } else {
                set sLibs($libName) $dependencies
            }
        }

        set iteration 1
        set oldSize [array size sLibs]
        while { [array size sLibs] > 0 } {
            # puts "Iteration $iteration: [array size sLibs]"
            # parray sLibs
            # First loop: Copy all libraries with no (more) dependencies into sorted list.
            foreach libName [lsort -dictionary [array names sLibs]] {
                if { [llength $sLibs($libName)] == 0 } {
                    lappend sortedList $libName
                    unset sLibs($libName)
                    # Second loop: Remove all occurrences of copied library in dependencies list.
                    foreach lib [array names sLibs] {
                        set sLibs($lib) [_lremove $sLibs($lib) $libName]
                    }
                }
            }
            if { $oldSize == [array size sLibs] } {
                foreach lib [lsort -dictionary [array names sLibs]] {
                    Log "Library $lib depends on $sLibs($lib), which is not included in build file." 2 false
                }
                ErrorAppend "Error in dependencies" "FATAL"
                break
            }
            set oldSize [array size sLibs]
            incr iteration
        }
        set sOpts(LibNames) [concat $sortedList $lastList]
    }

    proc GetLibIndex { libName } {
        return [lsearch -nocase -exact [GetLibs] $libName]
    }

    proc GetLibNumber { libName } {
        return [expr [GetLibIndex $libName] + 1]
    }

    proc GetLibName { libNumber } {
        return [lindex [GetLibs] [expr $libNumber - 1]]
    }

    proc AppendLib { libName } {
        variable sOpts

        lappend sOpts(LibNames) $libName
    }

    proc GetLibs {} {
        variable sOpts

        return $sOpts(LibNames)
    }

    proc GetNumLibs {} {
        return [llength [GetLibs]]
    }

    proc GenerateHtml { { onOff "" } } {
        variable sOpts

        if { $onOff eq "" } {
            return $sOpts(GenerateHtml)
        } else {
            set sOpts(GenerateHtml) $onOff
        }
    }

    proc HaveTestProc { libName } {
        if { [info commands Test_$libName] eq "" } {
            return "No"
        } else {
            return "Yes"
        }
    }

    proc PrintLibNames {} {
        # First determine maximum string length of each column for pretty output.
        set typeList [list # Name Version Platforms Compilers Dependencies ScriptAuthor Homepage Excludes Options Stages HaveTest]
        foreach type $typeList {
            set max($type) [string length $type]
        }
        set count 0
        foreach libName [GetLibs] {
            set opt($count,#)       [GetLibNumber  $libName]
            set opt($count,Name)    $libName
            set opt($count,Version) [GetLibVersion $libName]

            set opt($count,Platforms)    [join [GetPlatforms $libName]]
            set opt($count,Compilers)    [join [GetWinCompilers $libName]]
            set opt($count,Dependencies) [join [GetLibDependencies $libName]]
            set opt($count,ScriptAuthor) [GetScriptAuthorName $libName]
            set opt($count,Homepage)     [GetLibHomepage $libName]
            set opt($count,Excludes)     [GetExcludeOption $libName]
            set opt($count,Options)      [GetBuildOptions $libName]
            set opt($count,Stages)       [GetUsedStages $libName]
            set opt($count,HaveTest)     [HaveTestProc $libName]
            
            foreach type $typeList {
                if { [string length $opt($count,$type)] > $max($type) } {
                    set max($type) [string length $opt($count,$type)]
                }
            }
            incr count
        }

        # Output header line.
        puts -nonewline [format "%$max(#)s: %-$max(Name)s %-$max(Version)s" "#" "Name" "Version"]
        foreach option [GetCheckOptions] {
            if { [info exists opt(0,$option)] } {
                puts -nonewline [format " %-$max($option)s" $option]
            }
        }
        puts ""
        puts -nonewline [format "%s--%s-%s" \
             [string repeat "-" $max(#)] [string repeat "-" $max(Name)] [string repeat "-" $max(Version)]]
        foreach option [GetCheckOptions] {
            if { [info exists opt(0,$option)] } {
                puts -nonewline [format "%s" [string repeat "-" [expr { $max($option) + 1}]]]
            }
        }
        puts ""

        # Output information line for each library.
        set count 0
        foreach libName [GetLibs] {
            puts -nonewline [format "%$max(#)d: %-$max(Name)s %-$max(Version)s" \
                 $opt($count,#) $opt($count,Name) $opt($count,Version)]
            foreach option [GetCheckOptions] {
                if { [info exists opt($count,$option)] } {
                    if { $option eq "Homepage" && [GenerateHtml] } {
                        puts -nonewline [format " <a href=\"%s\" target=\"_%s\">%s</a>" \
                             $opt($count,$option) $libName $opt($count,$option)]
                    } else {
                        puts -nonewline [format " %-$max($option)s" $opt($count,$option)]
                    }
                }
            }
            puts ""
            incr count
        }
        _PrintErrorsAndWarnings
    }

    proc GetUsageMsg {} {
        set msg ""
        append msg "\n"
        append msg "Usage: Bawt.tcl \[Options\] SetupFile LibraryName \[LibraryNameN\]\n"
        append msg "\n"
        append msg "Start the BAWT automatic library build process.\n"
        append msg "When using \"all\" as target library name, all libraries specified\n"
        append msg "in the setup file are built.\n"
        append msg "It is also possible to specify the numbers of the libraries as printed\n"
        append msg "by option \"--list\" or specify a range of numbers (e.g: 2-5).\n"
        append msg "Note, that at least either a list or build action option must be specified.\n"
        append msg "\n"
        append msg "General options:\n"
        append msg "--help          : Print this help message and exit.\n"
        append msg "--version       : Print version number and exit.\n"
        append msg "--procs         : Print all available procedures and exit.\n"
        append msg "--proc <str>    : Print documentation of specified procedure and exit.\n"
        append msg "--loglevel <int>: Specify log message verbosity.\n"
        append msg "                  Choices: 0 - 4. Default: [GetLogLevel].\n"
        append msg "--nologtime     : Do not write time strings with log messages.\n"
        append msg "                  Default: Write time strings.\n"
        append msg "--logviewer     : Start graphical log viewer program BawtLogViewer.\n"
        append msg "                  Only working, if log level is greater than 1. Default: No.\n"
        append msg "\n"
        append msg "List action options:\n"
        append msg "--list          : Print all available library names and versions and exit.\n"
        append msg "--platforms     : Additionally print supported platforms.\n"
        append msg "--wincompilers  : Additionally print supported Windows compilers.\n"
        append msg "--authors       : Additionally print script authors.\n"
        append msg "--homepages     : Additionally print library homepages.\n"
        append msg "--excludes      : Additionally print platform specific excluded libraries.\n"
        append msg "--options       : Additionally print library build options.\n"
        append msg "--havetest      : Additionally print test procedure availability.\n"
        append msg "--dependencies  : Additionally print library dependencies.\n"
        append msg "--dependency    : Print dependencies of specified target libraries.\n"
        append msg "\n"
        append msg "Build action options:\n"
        append msg "--clean     : Clean library specific build and install directories.\n"
        append msg "--extract   : Extract library source from a ZIP file or a directory.\n"
        append msg "\n"
        append msg "--configure : Perform the configure stage of the build process.\n"
        append msg "--compile   : Perform the compile stage of the build process.\n"
        append msg "--distribute: Perform the distribution stage of the build process.\n"
        append msg "\n"
        append msg "--finalize  : Generate environment file and call user supplied Finalize procedure.\n"
        append msg "--complete  : Perform the following stages in order:\n"
        append msg "              clean, extract, configure, compile, distribute, finalize.\n"
        append msg "\n"
        append msg "--update    : Perform necessary stages depending on modification times.\n"
        append msg "              Note: Global stage finalize is always executed.\n"
        append msg "--simulate  : Simulate update action without actually building libraries.\n"
        append msg "--touch     : Set modification times of library build directories to current time.\n"
        append msg "\n"
        append msg "--test      : Perform library specific test scripts.\n"
        append msg "\n"
        append msg "Build configuration options:\n"
        append msg "--architecture <str>    : Build for specified processor architecture.\n"
        append msg "                          Choices: [GetValidArchitectures].\n"
        append msg "                          Default: [GetArchitecture].\n"
        append msg "                          x64 means 64-bit builds. x86 means 32-bit builds.\n"
        append msg "--compiler <str>        : Build with specified compiler version.\n"
        append msg "                          Choices: gcc vs2013 vs2015 vs2017 vs2019 vs2022 vs2026.\n"
        append msg "                          Specify primary and secondary compiler by adding a plus sign\n"
        append msg "                          inbetween. Example: gcc+vs2022.\n"
        append msg "                          Default: [GetCompilerVersion].\n"
        append msg "--gccversion <str>      : Build with specified MinGW gcc version. Windows only.\n"
        append msg "                          Choices: 7.2.0 8.1.0 11.2.0 12.2.0 13.2.0 14.2.0 15.2.0.\n"
        append msg "                          Default: [GetMingwGccVersion].\n"
        append msg "--msysversion <str>     : Build with specified MSYS version. Windows only.\n"
        append msg "                          Choices: 1 2.\n"
        append msg "                          Default: Version 2 if available, otherwise version 1.\n"
        append msg "--tclversion <str>      : Build Tcl, Tk and Tclkit for specified version.\n"
        append msg "                          Choices: 8.6.7 - 8.6.17 9.0.1 - 9.0.3.\n"
        append msg "                          Default: [GetTclVersion].\n"
        append msg "--tkversion <str>       : Build Tk and Tclkit for specified version.\n"
        append msg "                          Choices: 8.6.7 - 8.6.17 9.0.1 - 9.0.3.\n"
        append msg "                          Default: [GetTkVersion].\n"
        append msg "--imgversion <str>      : Build Img for specified version.\n"
        append msg "                          Choices: 1.4.13 - 1.4.17 2.0.1 - 2.1.0.\n"
        append msg "                          Default: [GetImgVersion].\n"
        append msg "--tlsversion <str>      : Build tcltls for specified version.\n"
        append msg "                          Choices: 1 2.\n"
        append msg "                          Default: Version 2.\n"
        append msg "--osgversion <str>      : Build OpenSceneGraph for specified version.\n"
        append msg "                          Choices: 3.4.1 3.6.5.\n"
        append msg "                          Default: [GetOsgVersion].\n"
        append msg "--libversion <lib> <str>: Build library for specified version.\n"
        append msg "                          Overwrites values specified in Setup file.\n"
        append msg "                          Default: As specified in Setup file.\n"
        append msg "--zipfile <lib> <str>   : Build library from specified file or directory.\n"
        append msg "                          Overwrites values specified in Setup file.\n"
        append msg "                          Default: As specified in Setup file.\n"
        append msg "--buildtype <str>       : Use specified build type.\n"
        append msg "                          Choices: [GetValidBuildTypes].\n"
        append msg "                          Default: Specified in setup file.\n"
        append msg "--zipkit                : Use zipkits instead of tclkits for Starpack generation.\n"
        append msg "                          Available for Tcl9 only.\n"
        append msg "                          Default: Use tclkits.\n"
        append msg "--universal             : Enable universal binary builds.\n"
        append msg "                          Available for Darwin only.\n"
        append msg "                          Default: Not enabled.\n"
        append msg "--mindarwin <str>       : Specify Darwin minimum version to build for.\n"
        append msg "                          Default: 11.0 (Big Sur).\n"
        append msg "--sanitizer             : Enable Address Sanitizer libasan.\n"
        append msg "                          Only valid with build type Debug.\n"
        append msg "                          Default: Not enabled.\n"
        append msg "--exclude <lib>         : Force exclusion of build for specified library name.\n"
        append msg "\n"
        append msg "--wincc <lib> <str>     : Use specified Windows compiler, if supported by build script.\n"
        append msg "                          Choices: \"gcc\" \"vs\".\n"
        append msg "--sdk <lib> <str>       : Use specified Microsoft SDK version.\n"
        append msg "                          To use the SDK version for all libraries,\n"
        append msg "                          specify \"all\" as library name.\n"
        append msg "--copt <lib> <str>      : Specify library specific configuration option.\n"
        append msg "--user <lib> <str>      : Specify library specific user build file.\n"
        append msg "\n"
        append msg "--url <str>             : Specify BAWT download server.\n"
        append msg "                          Default: [GetBawtUrl].\n"
        append msg "--cacert <str>          : Use specified certificate file as parameter to curl calls.\n"
        append msg "                          Default: None.\n"
        append msg "--toolsdir <str>        : Specify directory containing MSys/MinGW.\n"
        append msg "                          Default: [GetOutputToolsDir].\n"
        append msg "--rootdir <str>         : Specify build output root directory.\n"
        append msg "                          Default: [GetOutputRootDir].\n"
        append msg "--libdir <str>          : Add a directory containing library source and build files.\n"
        append msg "                          Default: [GetInputLibsDirs].\n"
        append msg "--distdir <str>         : Specify distribution root directory.\n"
        append msg "                          Default: [GetOutputDistDir].\n"
        append msg "--finalizefile <str>    : Specify file with user supplied Finalize procedure.\n"
        append msg "                          Default: None.\n"
        append msg "\n"
        append msg "--sort <str>            : Sort libraries according to specified sorting mode.\n"
        append msg "                          Choices: [GetValidSortModes].\n"
        append msg "                          Default: [lindex [GetValidSortModes] 0].\n"
        append msg "--noversion             : Do not use version number for Tcl package directories.\n"
        append msg "                          Default: Library name and version number.\n"
        append msg "--noexit                : Do not exit build process after fatal error, but try to continue.\n"
        append msg "                          Default: Exit build process after a fatal error.\n"
        append msg "--nosetupwarning        : Do not print warnings regarding multiple versions in Setup files.\n"
        append msg "                          Default: Print warnings.\n"
        append msg "--noimportlibs          : Do not create import libraries on Windows.\n"
        append msg "                          Default: Create import libraries. Needs Visual Studio.\n"
        append msg "--noruntimelibs         : Do not copy VisualStudio runtime libraries.\n"
        append msg "                          Default: Copy runtime libraries. Needs Visual Studio.\n"
        append msg "--nostrip               : Do not strip libraries in distribution directory.\n"
        append msg "                          Default: Strip libraries.\n"
        append msg "--noonline              : Do not check or download from online repository.\n"
        append msg "                          Default: Use [GetBawtUrl].\n"
        append msg "--norecursive           : Do not check recursive dependencies.\n"
        append msg "                          Default: Use recursive dependencies.\n"
        append msg "--nosubdirs             : Do not create compiler and architecture sub directories.\n"
        append msg "                          Default: Create compiler and architecture sub directories.\n"
        append msg "--nouserbuilds          : Do not consider user build files.\n"
        append msg "                          Default: User build files named \"LibraryName_User.bawt\".\n"
        append msg "\n"
        append msg "--iconfile <str>        : Use specified icon file for tclkits and starpacks.\n"
        append msg "                          Default: Standard tclkit icon. Windows only.\n"
        append msg "--resourcefile <str>    : Use specified resource file for tclkits and starpacks.\n"
        append msg "                          Default: Standard tclkit resource file. Windows only.\n"
        append msg "--certfile <str>        : Use specified certification file for code signing starpacks.\n"
        append msg "                          Default: No code signing. Windows only.\n"
        append msg "--timestampurl <str>    : Use specified timestamp server for code signing starpacks.\n"
        append msg "                          Default: [GetTclkitTimeStampUrl All]. Windows only.\n"
        append msg "                          Alternative: http://timestamp.sectigo.com\n"
        append msg "\n"
        append msg "--numjobs <int>         : Number of parallel compile jobs for all libraries.\n"
        append msg "                          Default: [GetNumJobs]\n"
        append msg "--libjobs <lib> <int>   : Number of parallel compile jobs for specified library.\n"
        append msg "                          Default: [GetNumJobs]\n"
        append msg "--timeout <float>       : Number of seconds to try renaming or deleting directories.\n"
        append msg "                          Default: [GetTimeout s]\n"
        return $msg
    }

    proc PrintUsage { { msg "" } } {
        if { $msg ne "" } {
            puts "ERROR: $msg"
        }
        puts [GetUsageMsg]
    }

    proc GetVersion {} {
        return "3.2.0"
    }

    proc PrintVersion { { versionNumOnly false } } {
        set versionNum [GetVersion]
        if { $versionNumOnly } {
            puts "$versionNum"
        } else {
            puts "BAWT $versionNum"
            puts "Copyright 2016-2025 Paul Obermeier"
        }
    }

    proc SetRemoteVersion { versionNum } {
        if { [GetMajor $versionNum] !=  [GetMajor [GetVersion]] } {
            ErrorAppend "Remote major version $versionNum different to major local version [GetVersion]" "FATAL"
        } elseif { [GetMinor $versionNum] !=  [GetMinor [GetVersion]] } {
            ErrorAppend "Remote minor version $versionNum different to minor local version [GetVersion]" "FATAL"
        } else {
            if { [VersionCheck $versionNum "newer" [GetVersion]] } {
                ErrorAppend "Remote version $versionNum newer than local version [GetVersion]" "Warning"
            }
        }
    }

    proc GetSetupFile {} {
        variable sOpts

        return $sOpts(SetupFile)
    }

    proc SetSetupFile { fileName } {
        variable sOpts

        set sOpts(SetupFile) [file normalize $fileName]
    }

    proc GetInputLibsDirs {} {
        variable sOpts

        return $sOpts(InputLibs)
    }

    proc GetBawtLibInputDir {} {
        return [lindex [GetInputLibsDirs] end]
    }

    proc AddInputLibsDir { dir { useLog true } } {
        variable sOpts

        set normDir [file normalize $dir]
        if { ! [info exists sOpts(InputLibs)] } {
            set sOpts(InputLibs) [list]
        }
        if { [lsearch -exact $sOpts(InputLibs) $normDir] < 0 } {
            set sOpts(InputLibs) [linsert $sOpts(InputLibs) 0 $normDir]
            if { $useLog } {
                Log "AddInputLibsDir $normDir"
            }
        }
    }

    proc HaveLibZipFile { libName } {
        variable sOpts

        return [info exists sOpts($libName,ZipFile)]
    }

    proc GetLibZipFile { libName } {
        variable sOpts

        return $sOpts($libName,ZipFile)
    }

    proc SetLibZipFile { libName zipFile } {
        variable sOpts

        set sOpts($libName,ZipFile) $zipFile
    }

    proc HaveLibBuildFile { libName } {
        variable sOpts

        return [info exists sOpts($libName,BuildFile)]
    }

    proc GetLibBuildFile { libName } {
        variable sOpts

        return $sOpts($libName,BuildFile)
    }

    proc SetLibBuildFile { libName buildFile } {
        variable sOpts

        set sOpts($libName,BuildFile) $buildFile
    }

    proc GetLibZipDir { libName } {
        variable sOpts

        return $sOpts($libName,ZipDir)
    }

    proc SetLibZipDir { libName zipDir } {
        variable sOpts

        set sOpts($libName,ZipDir) $zipDir
    }

    proc GetLibVersion { libName } {
        variable sOpts

        if { [info exists sOpts($libName,Version)] } {
            return $sOpts($libName,Version)
        } else {
            return ""
        }
    }

    proc SetLibVersion { libName version } {
        variable sOpts

        set sOpts($libName,Version) $version
    }

    proc GetLibHomepage { libName } {
        variable sOpts

        if { [info exists sOpts($libName,Homepage)] } {
            return $sOpts($libName,Homepage)
        } else {
            return ""
        }
    }

    proc SetLibHomepage { libName url } {
        variable sOpts

        set sOpts($libName,Homepage) $url
    }
    
    proc GetLibDependencies { libName } {
        variable sOpts

        if { [info exists sOpts($libName,Dependencies)] } {
            if { $sOpts($libName,Dependencies) eq "None" } {
                return [list]
            } else {
                return $sOpts($libName,Dependencies)
            }
        } else {
            return [list]
        }
    }

    proc SetLibDependencies { libName args } {
        variable sOpts

        set sOpts($libName,Dependencies) [list]
        foreach arg $args {
            if { $arg ne "" } {
                if { $arg eq "Tclkit" && [UseZipkit] } {
                    lappend sOpts($libName,Dependencies) "Zipkit"
                } else {
                    lappend sOpts($libName,Dependencies) $arg
                }
            }
        }
    }

    proc GetPlatforms { libName } {
        variable sOpts

        if { [info exists sOpts($libName,Platforms)] } {
            return $sOpts($libName,Platforms)
        } else {
            return [list]
        }
    }

    proc GetValidPlatforms {} {
        return [list "Windows" "Linux" "Darwin" "SunOS"]
    }

    proc SetPlatforms { libName args } {
        variable sOpts

        foreach arg $args {
            if { [string equal -nocase "All" $arg] } {
                set sOpts($libName,Platforms) [GetValidPlatforms]
                break
            }
            switch -exact -nocase -- $arg {
                "Windows" { set platform "Windows" }
                "Linux"   { set platform "Linux" }
                "Darwin"  { set platform "Darwin" }
                "SunOS"   { set platform "SunOS" }
                default   { ErrorAppend "Unknown platform $arg" "FATAL" }
            }
            lappend sOpts($libName,Platforms) $arg
        }
    }

    proc IsSupportedPlatform { libName } {
        if { [lsearch -exact [GetPlatforms $libName] [GetPlatformName]] >= 0 } {
            return true
        }
        return false
    }

    proc GetWinCompilers { libName } {
        variable sOpts

        set libName [string tolower $libName]
        if { [info exists sOpts($libName,WinCompilers)] } {
            return $sOpts($libName,WinCompilers)
        } else {
            return [list]
        }
    }

    proc SetWinCompilers { libName args } {
        variable sOpts

        set libName [string tolower $libName]
        if { ! [IsWindows] } {
            set sOpts($libName,WinCompilers) [list "gcc"]
            SetWinCompiler $libName "gcc"
            return
        }
        foreach arg $args {
            switch -exact -nocase -- $arg {
                "gcc"   { set compiler "gcc" }
                "vs"    { set compiler "vs" }
                default { ErrorAppend "Unknown Windows compiler $arg" "FATAL" }
            }
            lappend sOpts($libName,WinCompilers) $arg
        }
        set numWinCompilersSupported [llength $sOpts($libName,WinCompilers)]
        set numWinCompilersGlobal    [llength [GetCompilerVersions]]
        if { $numWinCompilersSupported > 0 && \
             $numWinCompilersGlobal > 0 && \
             [GetWinCompiler $libName] eq "" } {
            # Build script has specified supported Windows compiler(s) in the Init procedure.
            # Currently supported Windows compiler are: "vs" or "gcc".
            # Check the Windows compilers to be used globally (as specified with option "--compiler")
            # and determine the compiler to be used for this library:
            #   - Build script supports only gcc.
            #   - Build script supports only vs.
            #   - Build script supports both gcc and vs.
            #
            # Command line option   |        SetWinCompilers               |
            #                       | gcc         | vs          | gcc vs   |
            # --------------------------------------------------------------
            # --compiler gcc        | (1) gcc     |  (2) Exclude | (5) gcc |
            # --compiler vs20XX     | (3) Exclude |  (4) vs      | (6) vs  |
            # --compiler gcc+vs20XX | (9) gcc     | (10) vs      | (7) gcc |
            # --compiler vs20XX+gcc | (9) gcc     | (10) vs      | (8) vs  |

            if { $numWinCompilersGlobal == 1 } {
                set globalCompiler [lindex [GetCompilerVersions] 0]
                if { $numWinCompilersSupported == 1 } {
                    set scriptCompiler [lindex $sOpts($libName,WinCompilers) 0]
                    if { $globalCompiler eq "gcc" && $scriptCompiler eq "gcc" } {
                        SetWinCompiler $libName "gcc"                    ; # (1)
                    }
                    if { $globalCompiler eq "gcc" && [string match "vs*" $scriptCompiler] } {
                        SetExcludeCompiler $libName "gcc not supported"  ; # (2)
                    }
                    if { [string match "vs*" $globalCompiler] && $scriptCompiler eq "gcc" } {
                        SetExcludeCompiler $libName "vs not supported"   ; # (3)
                    }
                    if { [string match "vs*" $globalCompiler] && [string match "vs*" $scriptCompiler] } {
                        SetWinCompiler $libName "vs"                     ; # (4)
                    }
                } else {
                    if { $globalCompiler eq "gcc" } {
                        SetWinCompiler $libName "gcc"                    ; # (5)
                    } else {
                        SetWinCompiler $libName "vs"                     ; # (6)
                    }
                }
            } else {
                if { $numWinCompilersSupported == 1 } {
                    set scriptCompiler [lindex $sOpts($libName,WinCompilers) 0]
                    if { $scriptCompiler eq "gcc" } {
                        SetWinCompiler $libName "gcc"                    ; # (7)
                    } else {
                        SetWinCompiler $libName "vs"                     ; # (8)
                    }
                } else {
                    set globalCompiler [lindex [GetCompilerVersions] 0]
                    if { $globalCompiler eq "gcc" } {
                        SetWinCompiler $libName "gcc"                    ; # (9)
                    } else {
                        SetWinCompiler $libName "vs"                     ; # (10)
                    }
                }
            }
        }
    }

    proc GetSdkVersion { libName } {
        variable sOpts

        set libName [string tolower $libName]
        if { [info exists sOpts($libName,SdkVersion)] } {
            return $sOpts($libName,SdkVersion)
        } elseif { [info exists sOpts(all,SdkVersion)] } {
            return $sOpts(all,SdkVersion)
        } else {
            return ""
        }
    }

    proc SetSdkVersion { libName version } {
        variable sOpts

        set libName [string tolower $libName]
        set sOpts($libName,SdkVersion) $version
    }

    proc GetScriptAuthorName { libName } {
        variable sOpts

        if { [info exists sOpts($libName,Author,Name)] } {
            return $sOpts($libName,Author,Name)
        } else {
            return "Unknown"
        }
    }

    proc GetScriptAuthorMail { libName } {
        variable sOpts

        if { [info exists sOpts($libName,Author,Mail)] } {
            return $sOpts($libName,Author,Mail)
        } else {
            return "Unknown"
        }
    }

    proc SetScriptAuthor { libName name mail } {
        variable sOpts

        set sOpts($libName,Author,Name) $name
        set sOpts($libName,Author,Mail) $mail
    }

    proc CreateImportLibs { { onOff "" } } {
        variable sOpts

        if { $onOff eq "" } {
            return $sOpts(CreateImportLibs)
        } else {
            set sOpts(CreateImportLibs) $onOff
        }
    }

    proc CopyRuntimeLibs { { onOff "" } } {
        variable sOpts

        if { $onOff eq "" } {
            return $sOpts(CopyRuntimeLibs)
        } else {
            set sOpts(CopyRuntimeLibs) $onOff
        }
    }

    proc StripLibs { { onOff "" } } {
        variable sOpts

        if { $onOff eq "" } {
            return $sOpts(StripLibs)
        } else {
            set sOpts(StripLibs) $onOff
        }
    }

    proc UseOnlineRepository { { onOff "" } } {
        variable sOpts

        if { $onOff eq "" } {
            return $sOpts(UseOnlineRepository)
        } else {
            set sOpts(UseOnlineRepository) $onOff
        }
    }

    proc UseUserBuildFiles { { onOff "" } } {
        variable sOpts

        if { $onOff eq "" } {
            return $sOpts(UseUserBuildFiles)
        } else {
            set sOpts(UseUserBuildFiles) $onOff
        }
    }

    proc SetUserBuildFile { libName userBuildFile } {
        variable sUserBuildFiles

        set fileFound false
        set fileName $userBuildFile
        if { ! [file exists $fileName] } {
            if { [file pathtype $fileName] ne "absolute" } {
                foreach dir [GetInputLibsDirs] {
                    set fileName [file join $dir $userBuildFile]
                    if { [file exists $fileName] } {
                        set fileFound true
                        break
                    }
                }
            }
        } else {
            set fileName [file normalize $userBuildFile]
            set fileFound true
        }
        if { ! $fileFound } {
            ErrorAppend "SetUserBuildFile: User build file $userBuildFile does not exist" "FATAL"
        }

        set libName [string tolower $libName]
        set sUserBuildFiles($libName) $fileName
    }

    proc GetUserBuildFile { libName } {
        variable sUserBuildFiles

        set libName [string tolower $libName]
        if { ! [info exists sUserBuildFiles] || ! [info exists sUserBuildFiles($libName)] } {
            return ""
        }
        return $sUserBuildFiles($libName)
    }

    proc SetEnvVar { varName varValue } {
        variable sOpts

        Log "Environment : $varName=$varValue" 2 false
        set sOpts(EnvVars,$varName) $varValue
    }

    proc AddEnvVar { varName varValue } {
        variable sOpts

        lappend sOpts(EnvVars,$varName) $varValue
    }

    proc AddToPathEnv { varValue } {
        variable sOpts

        if { [lsearch -exact $sOpts(EnvVarPath) $varValue] < 0 } {
            Log "AddToPathEnv: $varValue" 2 false
            lappend sOpts(EnvVarPath) $varValue
        }
    }

    proc GetEnvVars {} {
        variable sOpts

        set msg ""
        foreach key [array names sOpts "EnvVars,*"] {
            set envVar [lindex [split $key ","] 1]
            if { [IsWindows] } {
                append msg "SETX $envVar \"$sOpts($key)\"\n"
            } else {
                append msg "export $envVar=\"$sOpts($key)\"\n"
            }
        }
        append msg "\n"
        if { [IsWindows] } {
            append msg "SETX BAWT_PATH \""
        } else {
            append msg "export BAWT_PATH=\""
        }
        foreach val $sOpts(EnvVarPath) {
            if { [IsWindows] } {
                append msg "$val;"
            } else {
                append msg "$val:"
            }
        }
        append msg "\"\n"
        if { [IsUnix] } {
            append msg "\n"
            append msg "export LD_LIBRARY_PATH=\"\$LD_LIBRARY_PATH:[GetOutputDevDir]/lib\""
            append msg "\n"
            append msg "export PATH=\"\$PATH:\$BAWT_PATH\""
        }
        return $msg
    }

    proc UseEnvVar { varName varValue } {
        variable sOpts

        Log "UseEnvVar   : $varName=$varValue" 2 false
        set sOpts(UseEnvVars,$varName) $varValue
        set ::env($varName) $varValue
    }

    proc GetUseEnvVars {} {
        variable sOpts

        set envList [list]
        foreach key [array names sOpts "UseEnvVars,*"] {
            set envVar [lindex [split $key ","] 1]
            lappend envList $envVar $sOpts($key)
        }
        return $envList
    }

    proc ClearBuildType { libName } {
        variable sOpts

        set sOpts(BuildTypes,$libName) [list]
    }

    proc AppendBuildType { libName buildType } {
        variable sOpts

        if { $buildType eq "Release" || $buildType eq "Debug" } {
            if { ! [info exists sOpts(BuildTypes,$libName)] || \
                 [lsearch -exact $sOpts(BuildTypes,$libName) $buildType] < 0 } {
                lappend sOpts(BuildTypes,$libName) $buildType
            }
        } else {
            ErrorAppend "Unknown build type $buildType" "FATAL"
        }
    }

    proc GetBuildTypes { libName } {
        variable sOpts

        return $sOpts(BuildTypes,$libName)
    }

    proc SetBuildOptions { libName options } {
        variable sOpts

        set libName [string tolower $libName]
        set sOpts(BuildOptions,$libName) $options
    }

    proc GetBuildOptions { libName } {
        variable sOpts

        set libName [string tolower $libName]
        if { [info exists sOpts(BuildOptions,$libName)] } {
            return $sOpts(BuildOptions,$libName)
        } else {
            return ""
        }
    }

    proc SetExcludeOption { libName option } {
        variable sOpts

        set libName [string tolower $libName]
        set sOpts(ExcludeOption,$libName) $option
    }

    proc GetExcludeOption { libName } {
        variable sOpts

        set libName [string tolower $libName]
        if { [info exists sOpts(ExcludeOption,$libName)] } {
            return $sOpts(ExcludeOption,$libName)
        } else {
            return ""
        }
    }

    proc SetExcludeCompiler { libName compiler } {
        variable sOpts

        set libName [string tolower $libName]
        set sOpts(ExcludeCompiler,$libName) $compiler
    }

    proc GetExcludeCompiler { libName } {
        variable sOpts

        set libName [string tolower $libName]
        if { [info exists sOpts(ExcludeCompiler,$libName)] } {
            return $sOpts(ExcludeCompiler,$libName)
        } else {
            return ""
        }
    }

    proc SetWinCompiler { libName winCompiler } {
        variable sOpts

        set libName [string tolower $libName]
        set sOpts(WinCompiler,$libName) $winCompiler
    }

    proc GetWinCompiler { libName } {
        variable sOpts

        set libName [string tolower $libName]
        set retVal ""
        if { [info exists sOpts(WinCompiler,$libName)] } {
            set winCompiler $sOpts(WinCompiler,$libName)
            if { [lsearch $sOpts($libName,WinCompilers) $winCompiler] < 0 } {
                set retVal "Unsupported"
            }
            set retVal $winCompiler
        }
        return $retVal
    }

    proc UseWinCompiler { libName winCompiler } {
        if { $winCompiler ne "gcc" && $winCompiler ne "vs" } {
            ErrorAppend "Unknown Windows compiler $winCompiler" "FATAL"
            return
        }
        set libName [string tolower $libName]
        if { [IsWindows] && ( [GetWinCompiler $libName] eq $winCompiler ) } {
            return true
        } else {
            return false
        }
    }

    proc SetInputRootDir { dir } {
        variable sOpts

        set sOpts(BawtRootDir) [file normalize $dir]
    }

    proc GetInputRootDir {} {
        variable sOpts

        return $sOpts(BawtRootDir)
    }

    proc SetInputResourceDir { dir } {
        variable sOpts

        set sOpts(BawtResourceDir) [file normalize $dir]
    }

    proc GetInputResourceDir {} {
        variable sOpts

        return $sOpts(BawtResourceDir)
    }

    proc SetOutputRootDir { dir } {
        variable sOpts

        set sOpts(BawtBuildDir) [file normalize $dir]
    }

    proc GetOutputRootDir {} {
        variable sOpts

        return $sOpts(BawtBuildDir)
    }

    proc SetOutputToolsDir { dir } {
        variable sOpts

        set sOpts(BawtToolsDir) [file normalize $dir]
    }

    proc GetOutputToolsDir {} {
        variable sOpts

        if { ! [info exists sOpts(BawtToolsDir)] } {
            return [file join [GetOutputRootDir] "Tools"]
        } else {
            return $sOpts(BawtToolsDir)
        }
    }

    proc GetBootstrapDir {} {
        return [file join [GetInputRootDir] "Bootstrap-[GetPlatformName]"]
    }

    proc ExtractLibrary { libName targetDir } {
        if { ! [HaveLibZipFile $libName] } {
            ErrorAppend "ExtractLibrary: No directory or ZIP file specified for library $libName" "FATAL"
            return
        }
        set zipFileOrDir [file join [GetInputRootDir] [GetLibZipDir $libName] [GetLibZipFile $libName]]
        set haveDir false
        if { [file isdirectory $zipFileOrDir] } {
            set haveDir true
        } elseif { ! [file exists $zipFileOrDir] } {
            ErrorAppend "ExtractLibrary: Directory or ZIP file $zipFileOrDir does not exist" "FATAL"
            return
        }

        Log "ExtractLibrary" 2
        if { [file isdirectory $targetDir] } {
            file delete -force $targetDir
        }
        if { $haveDir } {
            Log "Directory       : $zipFileOrDir"   4 false
        } else {
            Log "ZIP file        : $zipFileOrDir"   4 false
        }
        Log "Target directory: $targetDir" 4 false

        # Target directory has library name and version as last path component.
        # The ZIP file may have either just the library name or the library name
        # and version as root directory.
        # So we extract the ZIP file in the directory above targetDir and rename
        # the extracted directory to the library name, if it contains the version.
        if { $haveDir } {
            MultiFileCopy $zipFileOrDir $targetDir "*" true
        } else {
            set rootDir [file dirname $targetDir]
            Unzip $zipFileOrDir $rootDir
            if { ! [file isdirectory $targetDir] } {
                set dirName [file rootname [file tail $zipFileOrDir]]
                FileRename [file join $rootDir $dirName] $targetDir
            }
        }
        SetFilePermissions $targetDir "u+rwx" true
        file mtime $targetDir [clock seconds]
    }

    proc AddPath { path } {
        variable sOpts

        Log "AddPath $path" 2 false
        lappend sOpts(Path) $path
    }

    proc GetPathes { type } {
        variable sOpts

        if { $type eq "unix" } {
            set sep ":"
        } else {
            set sep ";"
        }
        set pathString ""
        foreach path $sOpts(Path) {
            if { $type eq "unix" } {
                set path [MSysPath $path]
            }
            set pathString [format "%s%s%s" $path $sep $pathString]
        }
        return $pathString
    }

    proc SetPathes {} {
        Log "SetPathes"
        AddPath [Get7ZipDistDir]
        AddPath [GetCMakeDistDir]
        AddPath [GetSWIGDistDir]
        AddPath [GetPandocDistDir]
        AddPath [file join [GetOutputDevDir] "bin"]
        AddPath [file join [GetOutputDevDir] "lib"]
        AddPath [file join [GetOutputDevDir] [GetTclBinDir]]
    }

    proc ModificationTime { libName fileName modTime } {
        variable sRepository

        set sRepository($fileName,modTime) [clock scan $modTime -format "%Y-%m-%d_%H:%M:%S"]
    }

    proc GetModificationTime { libName fileName } {
        variable sRepository

        set modTime 0
        if { [info exists sRepository($fileName,modTime)] } {
            set modTime $sRepository($fileName,modTime)
        }
        return $modTime
    }

    proc HashKey { libName fileName hashKey } {
        variable sRepository

        set sRepository($fileName,hashKey) $hashKey
    }

    proc GetHashKey { libName fileName } {
        variable sRepository

        set hashKey ""
        if { [info exists sRepository($fileName,hashKey)] } {
            set hashKey $sRepository($fileName,hashKey)
        }
        return $hashKey
    }

    proc GetVersionFromFileName { fileName } {
        set fileIsDir false
        if { [file isdirectory $fileName] } {
            set fileIsDir true
        }
        set pureName [file tail $fileName]
        set versionStart [string first "-" $pureName]
        if { $fileIsDir } {
            # Directories don't have an extension
            set versionEnd [string length $pureName]
        } else {
            # Files have .7z or .zip as extension
            set versionEnd [string last  "." $pureName]
        }
        set version ""
        if { $versionStart >= 0 && $versionEnd >= 0 && $versionEnd > $versionStart } {
            set version [string range $pureName [expr $versionStart + 1] [expr $versionEnd - 1]]
        }
        return $version
    }

    proc AddIncludePath { path { useLog true } } {
        variable sIncludePathes

        if { [file isdirectory $path] } {
            if { ! [info exists sIncludePathes] || \
                [lsearch -exact $sIncludePathes $path] < 0 } {
                lappend sIncludePathes $path
                if { $useLog } {
                    Log "AddIncludePath $path"
                }
            }
        } else {
            ErrorAppend "AddIncludePath: Path $path does not exist" "Warning"
        }
    }

    proc Include { setupFile } {
        variable sIncludeFiles
        variable sIncludePathes

        set fileFound false
        set fileName $setupFile
        if { ! [file exists $fileName] } {
            if { [file pathtype $fileName] ne "absolute" } {
                foreach dir $sIncludePathes {
                    set fileName [file join $dir $setupFile]
                    if { [file exists $fileName] } {
                        set fileFound true
                        break
                    }
                }
            }
        }
        if { ! $fileFound } {
            ErrorAppend "Include: Setup file $setupFile does not exist" "FATAL"
        }
        if { ! [info exists sIncludeFiles] || [lsearch -exact $sIncludeFiles $setupFile] < 0 } {
            lappend sIncludeFiles $setupFile
            uplevel #0 source $fileName
        }
    }

    proc _ExcludeLibByVersion { excludeVersion libVersion } {
        set versionNumbers [split $excludeVersion "."]
        if { [llength $versionNumbers] == 1 } {
            # Only major version specified.
            set libVersion [GetMajor $libVersion]
        } elseif { [llength $versionNumbers] == 2 } {
            # Only major and minor version specified.
            set libVersion [GetMajorMinor $libVersion "."]
        }
        set excludeVersion [UnifyVersionNumber $excludeVersion]
        set libVersion     [UnifyVersionNumber $libVersion]
        if { [VersionCheck $libVersion "equal" $excludeVersion] } {
            return true
        }
        return false
    }

    proc Setup { libName zipFile buildFile args } {
        # Check for existence of the library source code (either as a 7z file or directory)
        # as well as the according build file. If these do not exist in the library directory
        # (--libdir) and online update is enabled (--noonline not specified), they are downloaded
        # from the BAWT website.
        # If this fails, a fatal error is thrown and the build process is stopped.
        # The version number of the library is extracted from the file name of the library.
        # If build action is set to "Update", the necessary build stages are determined according
        # to the existence of the library source and build files as well as to the modification
        # times of the corresponding build and install directories.

        Log "Setup $libName" 2

        if { [GetLibraryZipFile $libName] ne "" } {
            # Override library source code with value specified on command line.
            set zipFile [GetLibraryZipFile $libName]
        }
        foreach libZipDir [GetInputLibsDirs] {
            Log "Looking for $zipFile in $libZipDir" 6 false
            # Note, that if zipFile is an absolute path,
            # zipFullPath gets the same value as zipFile.
            set zipFullPath [file join $libZipDir $zipFile]
            if { [file exists $zipFullPath] } {
                break
            }
        }
        Log "ZIP file   : $zipFullPath" 4 false
        if { ! [file exists $zipFullPath] } {
            if { [UseOnlineRepository] } {
                DownloadFile $libName "InputLibs" $zipFile $zipFullPath
                if { [UseStage "Update"] } {
                    UpdateLib $libName "Download/Build (Source distribution not available)"
                }
            } else {
                ErrorAppend "Setup: Directory or ZIP file $zipFullPath does not exist" "FATAL"
            }
        } else {
            set modTime 0
            if { [UseOnlineRepository] } {
                set modTime [GetModificationTime $libName [file tail $zipFile]]
            }
            if { $modTime > [file mtime $zipFullPath] } {
                if { ! [IsSimulationMode] } {
                    DownloadFile $libName "InputLibs" $zipFile $zipFullPath
                }
                if { [UseStage "Update"] } {
                    UpdateLib $libName "Remote source file newer than local"
                }
            }
        }
        foreach libDir [GetInputLibsDirs] {
            Log "Looking for $buildFile in $libDir" 6 false
            set buildFullPath [file join $libDir $buildFile]
            if { [file exists $buildFullPath] } {
                break
            }
        }
        foreach libDir [GetInputLibsDirs] {
            set userBuildExt [file extension $buildFile]
            set userBuildFile [format "%s_User%s" [file rootname [file tail $buildFile]] $userBuildExt]
            Log "Looking for user supplied $userBuildFile in $libDir" 6 false
            set userBuildFullPath [file join $libDir $userBuildFile]
            if { [file exists $userBuildFullPath] } {
                break
            }
        }
        Log "Build file : $buildFullPath" 4 false
        if { ! [file exists $buildFullPath] } {
            if { [UseOnlineRepository] } {
                DownloadFile $libName "InputLibs" $buildFile $buildFullPath
                if { [UseStage "Update"] } {
                    UpdateLib $libName "Download/Build (Build file not available)"
                }
            } else {
                ErrorAppend "Setup: Build file $buildFullPath does not exist" "FATAL"
            }
        } else {
            set modTime 0
            if { [UseOnlineRepository] } {
                set modTime [GetModificationTime $libName [file tail $buildFile]]
            }
            if { $modTime > [file mtime $buildFullPath] } {
                if { ! [IsSimulationMode] } {
                    DownloadFile $libName "InputLibs" $buildFile $buildFullPath
                }
                if { [UseStage "Update"] } {
                    UpdateLib $libName "Remote build file newer than local"
                }
            }
        }
        if { [file exists $buildFullPath] } {
            uplevel #0 source $buildFullPath
        }
        
        set libSpecificBuildFile [GetUserBuildFile $libName]
        if { ( [UseUserBuildFiles] && [file exists $userBuildFullPath] ) || \
               $libSpecificBuildFile ne "" } {
            if { $libSpecificBuildFile ne "" } {
                set userBuildFullPath $libSpecificBuildFile
            }
            Log "User file  : $userBuildFullPath" 4 false
            uplevel #0 source $userBuildFullPath
        }

        set version [GetVersionFromFileName $zipFullPath]

        if { [GetLibIndex $libName] >= 0 } {
            # Library was specified already. Print out a warning and
            # overwrite older values.
            set oldZipFile   [GetLibZipFile $libName]
            set oldBuildFile [file tail [GetLibBuildFile $libName]]
        }

        SetLibZipDir    $libName $libZipDir
        SetLibZipFile   $libName $zipFile
        SetLibBuildFile $libName $buildFullPath
        SetLibVersion   $libName $version
        set buildMsg ""
        AppendBuildType  $libName "Release"
        append buildMsg "Release "
        # Parse optional arguments.
        SetBuildOptions $libName $args
        foreach arg $args {
            switch -glob -- $arg {
                "Release" -
                "Debug" {
                    # Additional build types
                    AppendBuildType $libName $arg
                    append buildMsg "$arg "
                }
                "NoGcc*" {
                    # Exclude library from building with specified gcc version.
                    set excludeVersion [string range $arg 5 end]
                    if { [_ExcludeLibByVersion $excludeVersion [GetGccCompilerVersion]] } {
                        SetExcludeOption $libName $arg
                    }
                }
                "NoVs*" {
                    # Exclude library from building with specified vs version.
                    if { [UseVisualStudio] } {
                        set excludeVersion [string range $arg 4 end]
                        if { [_ExcludeLibByVersion $excludeVersion [GetVisualStudioVersion]] } {
                            SetExcludeOption $libName $arg
                        }
                    }
                }
                "NoTcl*" {
                    # Exclude library from building with specified Tcl version.
                    set excludeVersion [string range $arg 5 end]
                    if { [_ExcludeLibByVersion $excludeVersion [GetTclVersion]] } {
                        SetExcludeOption $libName $arg
                    }
                }
                "NoTk*" {
                    # Exclude library from building with specified Tk version.
                    set excludeVersion [string range $arg 4 end]
                    if { [_ExcludeLibByVersion $excludeVersion [GetTkVersion]] } {
                        SetExcludeOption $libName $arg
                    }
                }
                "NoUniversal" {
                    # Exclude library from building as universal binary.
                    if { [UseUniversal] } {
                        SetExcludeOption $libName $arg
                    }
                }
                "NoWindows" -
                "NoLinux" -
                "NoDarwin" -
                "NoSunOS" {
                    # Exclude library from building on specific OS using any machine type.
                    if { [GetPlatformName] eq [string range $arg 2 end] } {
                        SetExcludeOption $libName $arg
                    }
                }
                "NoSunOS-solaris" -
                "NoSunOS-indiana" {
                    # Exclude library from building on specific SunOS derivate.
                    set endIndex [expr [string first "-" $arg] + 1]
                    if { [IsSunOS [string totitle [string range $arg $endIndex end]]] } {
                        SetExcludeOption $libName $arg
                    }
                }
                "NoWindows-arm" -
                "NoLinux-arm" -
                "NoDarwin-arm" {
                    # Exclude library from building on specific OS using ARM machine type.
                    set endIndex [expr [string first "-" $arg] - 1]
                    if { [IsArm] && ( [GetPlatformName] eq [string range $arg 2 $endIndex] ) } {
                        SetExcludeOption $libName $arg
                    }
                }
                "NoLinux32-arm" {
                    # Exclude library from building on Linux ARM in 32-bit.
                    set endIndex [expr [string first "-" $arg] - 3]
                    if { [Is32Bit] && [IsArm] && ( [GetPlatformName] eq [string range $arg 2 $endIndex] ) } {
                        SetExcludeOption $libName $arg
                    }
                }
                "NoWindows-riscv" -
                "NoLinux-riscv" -
                "NoDarwin-riscv" {
                    # Exclude library from building on specific OS using RiscV machine type.
                    set endIndex [expr [string first "-" $arg] - 1]
                    if { [IsRiscV] && ( [GetPlatformName] eq [string range $arg 2 $endIndex] ) } {
                        SetExcludeOption $libName $arg
                    }
                }
                "NoWindows-intel" -
                "NoLinux-intel" -
                "NoDarwin-intel" {
                    # Exclude library from building on specific OS using Intel machine type.
                    set endIndex [expr [string first "-" $arg] - 1]
                    if { [IsIntel] && ( [GetPlatformName] eq [string range $arg 2 $endIndex] ) } {
                        SetExcludeOption $libName $arg
                    }
                }
                default {
                    if { [string match "Version=*" $arg] } {
                        # Override library version number.
                        set version [lindex [split $arg "="] 1]
                        SetLibVersion $libName $version
                    } elseif { [string match "WinCompiler=*" $arg] } {
                        # Override default Windows compiler.
                        set winCompiler [lindex [split $arg "="] 1]
                        SetWinCompiler $libName $winCompiler
                    } elseif { [string match "MaxParallel=*" $arg] } {
                        # Use parallel build for specified platforms separated by commas.
                        set platforms [lindex [split $arg "="] 1]
                        foreach platformStr [split $platforms ","] {
                            set compiler ""
                            lassign [split $platformStr ":"] platform numJobs
                            if { [string first "-" $platform] > 0 } {
                                lassign [split $platform "-"] platform compiler 
                            }
                            if { [string tolower [GetPlatformName]] eq [string tolower $platform] } {
                                SetNumJobs $numJobs $libName $compiler
                            } elseif { [lsearch -nocase -exact [GetValidPlatforms] $platform] < 0 } {
                                ErrorAppend "Setup: Unknown platform name \"$platform\" specified for MaxParallel option" "FATAL"
                            }
                        }
                    } elseif { [string match "NoParallel=*" $arg] } {
                        # Do not use parallel build for specified platforms separated by commas.
                        ErrorAppend "Option NoParallel is obsolete. Use MaxParallel= instead." "Warning"
                        set platforms [lindex [split $arg "="] 1]
                        foreach platform [split $platforms ","] {
                            if { [string tolower [GetPlatformName]] eq [string tolower $platform] } {
                                SetNumJobs 1 $libName
                            } elseif { [lsearch -nocase -exact [GetValidPlatforms] $platform] < 0 } {
                                ErrorAppend "Setup: Unknown platform name \"$platform\" specified for NoParallel option" "FATAL"
                            }
                        }
                    } else {
                        # All other strings are interpeted as user configurations.
                        AddUserConfig $libName $arg
                    }
                }
            }
        }
        if { [GetLibraryVersion $libName] ne "" } {
            # Override library version number with value specified on command line.
            set version [GetLibraryVersion $libName]
            SetLibVersion $libName $version
        }
        Log "Version    : $version"  4 false
        Log "Build types: $buildMsg" 4 false
        if { [GetLibIndex $libName] >= 0 } {
            if { $oldZipFile ne $zipFile || $oldBuildFile ne $buildFile } {
                if { [PrintSetupWarning] } {
                    ErrorAppend "Setup: Library $libName already specified:\n \
                                 Old setup: $oldZipFile $oldBuildFile\n \
                                 New setup: $zipFile $buildFile" "Warning"
                }
            }
        } else {
            AppendLib $libName
        }
    }

    proc _PrintErrorsAndWarnings {} {
        set msg ""
        if { [llength [GetWarningList]] > 0 } {
            append msg "\nWarning list:\n"
            foreach warnMsg [GetWarningList] {
                append msg "  $warnMsg\n"
            }
        }
        if { [llength [GetErrorList]] > 0 } {
            append msg "\nError list:\n"
            foreach errorMsg [GetErrorList] {
                append msg "  $errorMsg\n"
            }
        }
        if { [GetLogLevel] > 0 } {
            Log $msg 0 false
        } else {
            puts $msg
        }
    }

    proc PrintSummary {} {
        Log ""
        Log "Summary"
        Log "Setup file     : [GetSetupFile]"        0 false
        Log "Build directory: [GetOutputBuildDir]"   0 false
        Log "Architecture   : [GetArchitecture]"     0 false
        Log "Compilers      : [GetCompilerVersions]" 0 false
        Log "Global stages  : [GetUsedStages]"       0 false
        if { [llength [GetWorkingSet]] > 0 } {
            set stageTitle ""
            if { [UseStage "Update"] } {
                set stageTitle "Stages"
            }
            if { ! [IsSimulationMode] } {
                if { [UseStage "Test"] } {
                    Log [format "#  : %-20s %-10s %-15s %s" "Library Name" "Version" "Test time" $stageTitle] 0 false
                } else {
                    Log [format "#  : %-20s %-10s %-15s %s" "Library Name" "Version" "Build time" $stageTitle] 0 false
                }
            } else {
                Log [format "#  : %-20s %-10s %-15s %s" "Library Name" "Version" "Build action" "Build cause"] 0 false
            }
            Log [string repeat "-" 70] 0 false
            foreach libName [GetWorkingSet] {
                set libVersion [GetLibVersion $libName]
                set libNumber  [GetLibNumber  $libName]
                set buildTime  [GetBuildTime  $libName]
                set buildError [GetBuildError $libName]
                if { ! [IsSimulationMode] } {
                    if { $buildTime >= 0.0 || $buildTime == -2.0 } {
                        set stages ""
                        if { [UseStage "Update"] } {
                            set stages [GetUsedStages $libName]
                        }
                        if { $buildTime >= 0.0 } {
                            Log [format "%3d: %-20s %-10s %5.2f minutes   %s" $libNumber $libName $libVersion $buildTime $stages] 0 false
                        } else {
                            Log [format "%3d: %-20s %-10s  %-14s %s" $libNumber $libName $libVersion "Simulation" $stages] 0 false
                        }
                    } else {
                        Log [format "%3d: %-20s %-10s  %-14s %s" $libNumber $libName $libVersion "Excluded" $buildError] 0 false
                    }
                } else {
                    if { $buildTime >= 0.0 || $buildTime == -2.0 } {
                        set stages ""
                        if { [UseStage "Update"] } {
                            set stages [GetUsedStages $libName]
                        }
                        if { $stages eq "None" } {
                            Log [format "%3d: %-20s %-10s  %-14s %s" $libNumber $libName $libVersion "None" ""] 0 false
                        } else {
                            Log [format "%3d: %-20s %-10s  %-14s %s" $libNumber $libName $libVersion "Update" [GetLibUpdateCause $libName]] 0 false
                        }
                    } else {
                        Log [format "%3d: %-20s %-10s  %-14s %s" $libNumber $libName $libVersion "None" $buildError] 0 false
                    }
                }
            }
            Log [string repeat "-" 70] 0 false
            Log [format "Total: %.2f minutes" [expr [GetTotalTime] / 1000.0 / 60.0]] 0 false
        }

        _PrintErrorsAndWarnings
    }
}

#
# Start of main program.
#

namespace import BawtLog::*
namespace import BawtZip::*
namespace import BawtFile::*
namespace import BawtBuild::*
namespace import BawtMain::*

BawtMain::_Init

if { $argc == 0 } {
    PrintUsage "No build options or libraries specified"
    exit 1
}

set curArg 0
set targetList  [list]

set optPrintUsage    false
set optPrintProcs    false
set optPrintProc     ""
set optPrintVersion  false
set optPrintDepends  false
set optHaveSetupFile false
set optShowLogViewer false

set optHaveListOpt   false
set optHaveActionOpt false

set optLogLevel -1
set optNumJobs  -1
set optSortMode [lindex [GetValidSortModes] 0]
set optWinCompiler [list]
set optUserConfigs [list]
set optSdkVersions [list]
set optJobConfigs  [list]

while { $curArg < $argc } {
    set curParam [lindex $argv $curArg]
    if { [string compare -length 1 $curParam "-"]  == 0 || \
         [string compare -length 2 $curParam "--"] == 0 } {
        set curOpt [string tolower [string trimleft $curParam "-"]]
        if { $curOpt eq "loglevel" } {
            incr curArg
            if { ! [string is integer -strict [lindex $argv $curArg]] } {
                PrintUsage "Invalid $curParam value: \"[lindex $argv $curArg]\""
                exit 1
            }
            set optLogLevel [lindex $argv $curArg]
        } elseif { $curOpt eq "nologtime" } {
            SetLogTiming false
        } elseif { $curOpt eq "logviewer" } {
            set optShowLogViewer true
        } elseif { $curOpt eq "help" } {
            set optPrintUsage true
        } elseif { $curOpt eq "procs" } {
            set optPrintProcs true
        } elseif { $curOpt eq "proc" } {
            incr curArg
            set optPrintProc [lindex $argv $curArg]
        } elseif { $curOpt eq "version" } {
            set optPrintVersion true
        } elseif { $curOpt eq "list" } {
            set optHaveListOpt true
        } elseif { $curOpt eq "platforms" } {
            set optHaveListOpt true
            AddCheckOption "Platforms"
        } elseif { $curOpt eq "wincompilers" } {
            set optHaveListOpt true
            AddCheckOption "Compilers"
        } elseif { $curOpt eq "dependencies" } {
            set optHaveListOpt true
            AddCheckOption "Dependencies"
        } elseif { $curOpt eq "dependency" } {
            set optPrintDepends true
            set optHaveListOpt true
        } elseif { $curOpt eq "authors" } {
            set optHaveListOpt true
            AddCheckOption "ScriptAuthor"
        } elseif { $curOpt eq "homepages" } {
            set optHaveListOpt true
            AddCheckOption "Homepage"
        } elseif { $curOpt eq "excludes" } {
            set optHaveListOpt true
            AddCheckOption "Excludes"
        } elseif { $curOpt eq "options" } {
            set optHaveListOpt true
            AddCheckOption "Options"
        } elseif { $curOpt eq "havetest" } {
            set optHaveListOpt true
            AddCheckOption "HaveTest"
        } elseif { $curOpt eq "sort" } {
            incr curArg
            if { [lsearch -exact -nocase [GetValidSortModes] [lindex $argv $curArg]] < 0 } {
                PrintUsage "Invalid $curParam value: \"[lindex $argv $curArg]\""
                exit 1
            }
            set optSortMode [lindex $argv $curArg]
        } elseif { $curOpt eq "clean" } {
            set optHaveActionOpt true
            EnableStage "Clean"
        } elseif { $curOpt eq "extract" } {
            set optHaveActionOpt true
            EnableStage "Extract"
        } elseif { $curOpt eq "configure" } {
            set optHaveActionOpt true
            EnableStage "Configure"
        } elseif { $curOpt eq "compile" } {
            set optHaveActionOpt true
            EnableStage "Compile"
        } elseif { $curOpt eq "distribute" } {
            set optHaveActionOpt true
            EnableStage "Distribute"
        } elseif { $curOpt eq "finalize" } {
            set optHaveActionOpt true
            EnableStage "Finalize"
        } elseif { $curOpt eq "test" } {
            set optHaveActionOpt true
            DisableAllStages
            EnableStage "Test"
        } elseif { $curOpt eq "complete" } {
            set optHaveActionOpt true
            EnableAllStages
        } elseif { $curOpt eq "update" } {
            set optHaveActionOpt true
            DisableAllStages
            EnableStage "Update"
            EnableStage "Finalize"
        } elseif { $curOpt eq "simulate" } {
            set optHaveActionOpt true
            DisableAllStages
            EnableStage "Update"
            AddCheckOption "Stages"
        } elseif { $curOpt eq "touch" } {
            set optHaveActionOpt true
            DisableAllStages
            EnableStage "Touch"
        } elseif { $curOpt eq "numjobs" } {
            incr curArg
            if { ! [string is integer -strict [lindex $argv $curArg]] || [lindex $argv $curArg] < 1 } {
                PrintUsage "Invalid $curParam value: \"[lindex $argv $curArg]\""
                exit 1
            }
            set optNumJobs [lindex $argv $curArg]
        } elseif { $curOpt eq "libjobs" } {
            incr curArg
            set jobOptLibName [lindex $argv $curArg]
            incr curArg
            set jobOptValue [lindex $argv $curArg]
            if { ! [string is integer -strict $jobOptValue] || $jobOptValue < 1 } {
                PrintUsage "Invalid $curParam value: \"$jobOptValue\""
                exit 1
            }
            lappend optJobConfigs $jobOptLibName $jobOptValue
        } elseif { $curOpt eq "timeout" } {
            incr curArg
            if { ! [string is double -strict [lindex $argv $curArg]] || [lindex $argv $curArg] < 0.0 } {
                PrintUsage "Invalid $curParam value: \"[lindex $argv $curArg]\""
                exit 1
            }
            SetTimeout [lindex $argv $curArg]
        } elseif { $curOpt eq "architecture" } {
            incr curArg
            if { [lsearch -exact [GetValidArchitectures] [lindex $argv $curArg]] < 0 } {
                PrintUsage "Invalid $curParam value: \"[lindex $argv $curArg]\""
                exit 1
            }
            SetArchitecture [lindex $argv $curArg]
        } elseif { $curOpt eq "compiler" } {
            incr curArg
            set compilerStr  [lindex $argv $curArg]
            set compilerList [split $compilerStr "+"]
            foreach compiler $compilerList {
                if { [lsearch -exact [GetValidCompilerVersions] $compiler] < 0 } {
                    PrintUsage "Invalid $curParam value: \"$compilerStr\""
                    exit 1
                }
            }
            SetCompilerVersions {*}$compilerList
        } elseif { $curOpt eq "forcevs" } {
            incr curArg
            ErrorAppend "Option --forcevs is obsolete. Use --wincc instead." "Warning"
            lappend optWinCompiler [lindex $argv $curArg] "vs"
        } elseif { $curOpt eq "wincc" } {
            incr curArg
            set winOptLibName [lindex $argv $curArg]
            incr curArg
            set winOptValue [lindex $argv $curArg]
            lappend optWinCompiler $winOptLibName $winOptValue
        } elseif { $curOpt eq "exclude" } {
            incr curArg
            SetExcludeOption [lindex $argv $curArg] "--exclude" 
        } elseif { $curOpt eq "gcc" || $curOpt eq "gccversion" } {
            incr curArg
            SetMingwGccVersion [lindex $argv $curArg]
        } elseif { $curOpt eq "msysversion" } {
            incr curArg
            SetMSysVersion [lindex $argv $curArg]
        } elseif { $curOpt eq "tclversion" } {
            incr curArg
            SetTclVersion [lindex $argv $curArg]
        } elseif { $curOpt eq "tkversion" } {
            incr curArg
            SetTkVersion [lindex $argv $curArg]
        } elseif { $curOpt eq "imgversion" } {
            incr curArg
            SetImgVersion [lindex $argv $curArg]
        } elseif { $curOpt eq "osgversion" } {
            incr curArg
            SetOsgVersion [lindex $argv $curArg]
        } elseif { $curOpt eq "tlsversion" } {
            incr curArg
            SetTlsVersion [lindex $argv $curArg]
        } elseif { $curOpt eq "libversion" } {
            incr curArg
            set versionLibName [lindex $argv $curArg]
            incr curArg
            set versionLibValue [lindex $argv $curArg]
            SetLibraryVersion $versionLibName $versionLibValue
        } elseif { $curOpt eq "zipfile" } {
            incr curArg
            set zipLibName [lindex $argv $curArg]
            incr curArg
            set zipLibValue [lindex $argv $curArg]
            SetLibraryZipFile $zipLibName $zipLibValue
        } elseif { $curOpt eq "buildtype" } {
            incr curArg
            if { [lsearch -exact [GetValidBuildTypes] [lindex $argv $curArg]] < 0 } {
                PrintUsage "Invalid $curParam value: \"[lindex $argv $curArg]\""
                exit 1
            }
            AppendBuildType ForceBuildType [lindex $argv $curArg]
        } elseif { $curOpt eq "zipkit" } {
            UseZipkit true
        } elseif { $curOpt eq "universal" } {
            UseUniversal true
        } elseif { $curOpt eq "mindarwin" } {
            incr curArg
            SetDarwinMinVersion [lindex $argv $curArg]
        } elseif { $curOpt eq "sanitizer" } {
            UseSanitizer true
        } elseif { $curOpt eq "url" } {
            incr curArg
            SetBawtUrl [lindex $argv $curArg]
        } elseif { $curOpt eq "cacert" } {
            incr curArg
            SetCurlCertificate [lindex $argv $curArg]
        } elseif { $curOpt eq "rootdir" } {
            incr curArg
            SetOutputRootDir [lindex $argv $curArg]
        } elseif { $curOpt eq "builddir" } {
            incr curArg
            ErrorAppend "Option --builddir is obsolete. Use --rootdir instead." "Warning"
            SetOutputRootDir [lindex $argv $curArg]
        } elseif { $curOpt eq "toolsdir" } {
            incr curArg
            SetOutputToolsDir [lindex $argv $curArg]
        } elseif { $curOpt eq "distdir" } {
            incr curArg
            SetOutputDistDir [lindex $argv $curArg]
        } elseif { $curOpt eq "libdir" } {
            incr curArg
            AddInputLibsDir [lindex $argv $curArg]
        } elseif { $curOpt eq "finalizefile" } {
            incr curArg
            SetFinalizeFile [lindex $argv $curArg]
        } elseif { $curOpt eq "noexit" } {
            ExitOnFatalError false
        } elseif { $curOpt eq "nosetupwarning" } {
            PrintSetupWarning false
        } elseif { $curOpt eq "noversion" } {
            UseTclPkgVersion false
        } elseif { $curOpt eq "noimportlibs" } {
            CreateImportLibs false
        } elseif { $curOpt eq "noruntimelibs" } {
            CopyRuntimeLibs false
        } elseif { $curOpt eq "nostrip" } {
            StripLibs false
        } elseif { $curOpt eq "noonline" } {
            UseOnlineRepository false
        } elseif { $curOpt eq "norecursive" } {
            UseRecursiveDependencies false
        } elseif { $curOpt eq "nosubdirs" } {
            SetShortRootDir true
        } elseif { $curOpt eq "nouserbuilds" } {
            UseUserBuildFiles false
        } elseif { $curOpt eq "iconfile" } {
            incr curArg
            SetTclkitIconFile "All" [lindex $argv $curArg]
        } elseif { $curOpt eq "resourcefile" } {
            incr curArg
            SetTclkitResourceFile "All" [lindex $argv $curArg]
        } elseif { $curOpt eq "certfile" } {
            incr curArg
            SetTclkitCertFile "All" [lindex $argv $curArg]
        } elseif { $curOpt eq "timestampurl" } {
            incr curArg
            SetTclkitTimeStampUrl "All" [lindex $argv $curArg]
        } elseif { $curOpt eq "html" } {
            GenerateHtml true
        } elseif { $curOpt eq "copt" } {
            incr curArg
            set configOptLibName [lindex $argv $curArg]
            incr curArg
            set configOptData [lindex $argv $curArg]
            lappend optUserConfigs $configOptLibName $configOptData
        } elseif { $curOpt eq "user" } {
            incr curArg
            set userOptLibName [lindex $argv $curArg]
            incr curArg
            set userOptFileName [lindex $argv $curArg]
            SetUserBuildFile $userOptLibName $userOptFileName
        } elseif { $curOpt eq "sdk" } {
            incr curArg
            set sdkOptLibName [lindex $argv $curArg]
            incr curArg
            set sdkOptVersion [lindex $argv $curArg]
            lappend optSdkVersions $sdkOptLibName $sdkOptVersion
        } else {
            PrintUsage "Invalid option \"$curParam\""
            exit 1
        }
    } else {
        if { ! $optHaveSetupFile } {
            SetSetupFile $curParam
            AddIncludePath [file dirname $curParam] false
            set optHaveSetupFile true
        } else {
            lappend targetList $curParam
        }
    }
    incr curArg
}

if { $optPrintUsage } {
    PrintUsage
    exit 0
}

if { $optPrintProcs } {
    set procList [BawtHelp::GetProcList]
    if { $optSortMode eq "dictionary" } {
        set procList [lsort -dictionary $procList]
    }
    foreach fullName $procList {
        puts [BawtHelp::GetProcShortName $fullName]
    }
    exit 0
}

if { $optPrintProc ne "" } {
    set procList [BawtHelp::GetProcList]
    set found false
    foreach fullName $procList {
        set shortName [BawtHelp::GetProcShortName $fullName]
        if { [string match -nocase "$optPrintProc*" $shortName] } {
            puts "$shortName [info args $fullName]"
            set procBody [info body $fullName]
            set bodyLines [split $procBody "\n"]
            foreach line $bodyLines {
                set trimmedLine [string trim $line]
                if { $trimmedLine eq "" || [string first "#" $trimmedLine] == 0 } {
                    puts "[string trimright [string trimleft $trimmedLine "#"]]"
                } else {
                    break
                }
            }
            set found true
        }
    }
    if { ! $found } {
        puts "No procedure $optPrintProc available"
    }
    exit 0
}

if { $optPrintVersion } {
    PrintVersion [expr $optLogLevel == 0]
    exit 0
}

if { $optHaveListOpt } {
    DisableAllStages
    EnableStage "Check"
    SetLogLevel 0
}

if { $optLogLevel >= 0 } {
    SetLogLevel $optLogLevel
} else {
    SetLogLevel [GetLogLevel]
}

Log "BAWT command line options:" 0 false
BawtBuild::_LogCmd $argv

if { [UseUniversal] && ! [IsDarwin] } {
    PrintUsage "Universal binaries only valid for Darwin."
    exit 1
}

if { [UseZipkit] && [GetMajor [GetTclVersion]] < 9 } {
    PrintUsage "Zipkits available for Tcl/Tk >= 9 only."
    exit 1
}

if { [UseSanitizer] && [IsReleaseBuild] } {
    PrintUsage "Sanitizer only valid with Debug build type."
    exit 1
}

if { ! [file exists [GetSetupFile]] } {
    PrintUsage "No valid setup file specified: [GetSetupFile]"
    exit 1
}

if { ! $optHaveListOpt && ! $optHaveActionOpt } {
    PrintUsage "No list or build action specified."
    exit 1
}

if { [llength $targetList] == 0 && ! [UseStage "Check"] } {
    PrintUsage "No library specified."
    exit 1
}

if { $optNumJobs >= 0 } {
    SetNumJobs $optNumJobs
}
foreach { libName value } $optJobConfigs {
    SetNumJobs $value $libName
}

if { $optShowLogViewer && [GetLogLevel] > 1 } {
    StartBawtLogViewerProg [GetLogFile]
}

if { ! [UseStage "Check"] } {
    Bootstrap
}

if { [UseVisualStudio] } {
    Log "VisualStudio environment: [GetVcvarsProg]"
}

if { [UseOnlineRepository] && ! [UseStage "Check"] } {
    Log ""
    Log "Check online repository"
    set configFileList [list "ModificationTimes.txt" "HashKeys.txt"]
    set buildDir [GetOutputRootDir]
    foreach configFile $configFileList {
        set configFileFullPath [file join $buildDir $configFile]
        DownloadFile "ConfigFile" "" $configFile $configFileFullPath "Warning"
        if { [file readable $configFileFullPath] } {
            source $configFileFullPath
        }
    }
}

Log "Setup (File [GetSetupFile]) "
source [GetSetupFile]

# Overwrite optional Setup parameters WinCompiler, UserConfiguration and SDK version.
foreach { libName value } $optWinCompiler {
    SetWinCompiler $libName $value
}
foreach { libName value } $optUserConfigs {
    AddUserConfig $libName $value
}
foreach { libName value } $optSdkVersions {
    SetSdkVersion $libName $value
}

foreach libName [GetLibs] {
    if { [info commands Init_$libName] eq "" } {
        ErrorAppend "Library $libName: No Init_$libName command defined." "Warning"
    } else {
        Init_$libName $libName [GetLibVersion $libName]
    }
}

if { $optSortMode eq "none" } {
    ; # No sorting necessary
} elseif { $optSortMode eq "dictionary" } {
    SortLibsByDictionary
} else {
    SortLibsByDependencies
}

if { $optPrintDepends } {
    foreach libName $targetList {
        PrintLibDependency $libName
    }
    exit 0
}

if { [UseStage "Check"] } {
    SetWorkingSet [GetLibs]
    DisableAllStages
} else {
    if { [lsearch -exact -nocase $targetList "all"] >= 0 } {
        SetWorkingSet [GetLibs]
    } else {
        foreach libNameOrNum $targetList {
            set libIndex [GetLibIndex $libNameOrNum]
            if { $libIndex >= 0 } {
                AppendToWorkingSet [GetLibName [GetLibNumber $libNameOrNum]]
            } elseif { [string is integer $libNameOrNum] } {
                if { $libNameOrNum >= 1 && $libNameOrNum <= [GetNumLibs] } {
                    AppendToWorkingSet [GetLibName $libNameOrNum]
                } else {
                    PrintUsage "Invalid library number \"$libNameOrNum\" specified."
                    exit 1
                }
            } elseif { [string match "*-*" $libNameOrNum] } {
                if { 2 == [scan $libNameOrNum "%d-%d" minNumber maxNumber] } {
                    for { set i $minNumber } { $i <= $maxNumber } { incr i } {
                        if { $i >= 1 && $i <= [GetNumLibs] } {
                            AppendToWorkingSet [GetLibName $i]
                        } else {
                            PrintUsage "Invalid library number \"$i\" specified."
                            exit 1
                        }
                    }
                } else {
                    PrintUsage "Invalid library \"$libNameOrNum\" specified."
                    exit 1
                }
            } else {
                PrintUsage "Invalid library \"$libNameOrNum\" specified."
                exit 1
            }
        }
    }
}

Log ""
Log "SetCompilerVersions [GetCompilerVersions]" 1 false
if { [UseStage "Test"] } {
    Log "SetStage Test" 1 false
}
Log ""
foreach libName [GetWorkingSet] {
    Log [format "WorkingSet %3d: %s \"%s\" \"%s\"" \
                [GetLibNumber $libName] \
                $libName \
                [GetLibVersion $libName] \
                [GetCompilerVersion -lib $libName]] \
                1 false
}
Log ""
foreach libName [GetWorkingSet] {
    if { [llength [GetUserConfig $libName]] > 0 } {
        Log [format "UserConfig %3d: %s \"%s\"" \
                    [GetLibNumber $libName] \
                    $libName \
                    [GetUserConfig $libName]] \
                    1 false
    }
}
Log ""

AddToPathEnv "[GetOutputDevDir]/bin"
AddToPathEnv "[GetOutputDevDir]/lib"

SetPathes

SetStartTime

foreach libName [GetWorkingSet] {
    if { [llength [GetBuildTypes ForceBuildType]] > 0 } {
        set buildTypeList [GetBuildTypes ForceBuildType]
    } else {
        set buildTypeList [GetBuildTypes $libName]
    }
    if { ! [UseStage "Check"] } {
        Log ""
        Log "Start $libName [GetLibVersion $libName] (Library #[GetLibNumber $libName] of [GetNumLibs])"
        if { ! [UseStage "Test"] } {
            Log "Build types : $buildTypeList" 2 false
        }
    }
    set buildTime [BuildLib $libName [GetLibVersion $libName] $buildTypeList]
    SetBuildTime $libName $buildTime
}

if { [UseStage "Finalize"] && ! [IsSimulationMode] } {
    Log ""
    Log "Start FinalizeStage"

    # Write out environment file.
    set outFileName [format "SetEnv-%s-%s%s" \
                    [GetCompilerVersion -platform] [GetArchitecture] [GetBatchSuffix]]
    set outDir [file join [GetOutputDevDir] "bin"]
    if { ! [file isdirectory $outDir] } {
        file mkdir $outDir
    }
    set outFilePath [file join $outDir $outFileName]
    Log "Environment file: $outFilePath" 2 false
    set retVal [catch {open $outFilePath w} outFp]
    if { $retVal != 0 } {
        ErrorAppend "Cannot write environment file $outFilePath" "FATAL"
    } else {
        puts $outFp [GetEnvVars]
        close $outFp
    }

    if { [IsWindows] && [UseVisualStudio] && [CopyRuntimeLibs] } {
        Log "VisualStudio runtime files" 2 false
        if { [GetVSRuntimeLibDir] ne "" } {
            MultiFileCopy [GetVSRuntimeLibDir]  [file join [GetOutputDevDir]  "bin"]  "*.dll"
            MultiFileCopy [GetVSRuntimeLibDir]  [file join [GetOutputDistDir] "bin"]  "*.dll"
        }
    }

    if { [GetFinalizeFile] ne "" } {
        source [GetFinalizeFile]
    }
    if { [info commands "Finalize"] ne "" } {
        Finalize
    }
    Log "End FinalizeStage"
}

if { [UseStage "Check"] } {
    PrintLibNames
    exit 0
}

PrintSummary

exit 0
