Tcl 9.1 Static Build

Table of Contents

1. Introduction

This CMake recipe builds and installs Tcl/Tk 9 from scratch on Windows, Linux and MacOS. The Tcl and Tk interpreters tclsh and wish are built statically across platforms and can be installed in a local workspace with no other requirements. The goal is to provide a convenient environment to build and even deploy Tcl and Tk GUI applications without having set up any dependencies or rely on package managers.

The Tcl Standard Library is also installed along with the parts accelerated by Critcl. This includes many useful libraries that are not part of the standard Tcl distribution like encoding/decoding JSON.

In addition a static version of the Tcl interpreter is also built at configure time. This allows using this recipe to also be used as part of a CMake super build in which subsequent steps have full access to Tcl which is much easier to work with than CMake. As an example, it is used in this recipe to check the version of the Tcl interpreter to avoid rebuilding it.

2. Getting Started

2.1. Linux and MacOS

Requirements:

  • cmake > 3.14
  • gcc (tested with gcc 15)
  • Autoconf
  • make

Assuming this repo has been cloned to tcl-static-build this should download Tcl/Tk sources, build and then install them to ~/MyWorkspace/bin/:

> cd tcl-static-build
> mkdir build
> cd build
> cmake .. -DCMAKE_INSTALL_PREFIX=~/MyWorkspace -DTCL_STATIC_BUILD_TK=On
> make

And checking the runtime dependencies …:

> ldd ~/MyWorkspace/bin/tclsh
        linux-vdso.so.1 (0x00007fb0c6af1000)
        libz.so.1 => /usr/lib/libz.so.1 (0x00007fb0c6a9f000)
        libm.so.6 => /usr/lib/libm.so.6 (0x00007fb0c66e2000)
        libtommath.so.1 => /usr/lib/libtommath.so.1 (0x00007fb0c6a87000)
        libc.so.6 => /usr/lib/libc.so.6 (0x00007fb0c64f1000)
        /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fb0c6af3000)
> ldd ~/MyWorkspace/bin/wish9.1
        linux-vdso.so.1 (0x00007ff057210000)
        libXft.so.2 => /usr/lib/libXft.so.2 (0x00007ff056de6000)
        libfontconfig.so.1 => /usr/lib/libfontconfig.so.1 (0x00007ff056d95000)
        libX11.so.6 => /usr/lib/libX11.so.6 (0x00007ff056c53000)
        ... ( a whole bunch of X11/Wayland runtime dependencies, standard for GUI applications )

2.2. Windows

Requirements:

Please make sure to start the build in Visual Studio Developer Command Prompt, this ensures required tools like nmake and vcc available at the command prompt which is a lot easier than hunting down explicit paths.

Assuming this repo has been cloned to tcl-static-build this should download Tcl/Tk sources, build and install them to C:\Users\<your-username>\Tcl\bin:

> cd tcl-static-build
> mkdir build
> cd build
> cmake.exe .. -DCMAKE_INSTALL_PREFIX=C:\Users\<your-username>\Tcl -DTCL_STATIC_BUILD_TK=On
> MSBuild.exe ALL_BUILD.vcxproj

And checking the runtime dependencies …:

> cd C:\Users\<your-username>\Tcl\bin
> dumpbin.exe /DEPENDENTS tclsh.exe
Microsoft (R) COFF/PE Dumper Version 14.39.33521.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file tclsh91s.exe

File Type: EXECUTABLE IMAGE

  Image has the following dependencies:

    VCRUNTIME140.dll
    KERNEL32.dll
    ADVAPI32.dll
    NETAPI32.dll
    USER32.dll
    USERENV.dll
    WS2_32.dll
    api-ms-win-crt-stdio-l1-1-0.dll
    api-ms-win-crt-string-l1-1-0.dll
    api-ms-win-crt-convert-l1-1-0.dll
    api-ms-win-crt-math-l1-1-0.dll
    api-ms-win-crt-runtime-l1-1-0.dll
    api-ms-win-crt-environment-l1-1-0.dll
    api-ms-win-crt-time-l1-1-0.dll
    api-ms-win-crt-utility-l1-1-0.dll
    api-ms-win-crt-heap-l1-1-0.dll
    api-ms-win-crt-locale-l1-1-0.dll

  Summary

        3000 .data
       13000 .pdata
       A2000 .rdata
        3000 .reloc
        F000 .rsrc
      1CD000 .text

> dumpbin.exe /DEPENDENTS wish91s.exe
Microsoft (R) COFF/PE Dumper Version 14.39.33521.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file wish91s.exe

File Type: EXECUTABLE IMAGE

  Image has the following dependencies:

    api-ms-win-crt-runtime-l1-1-0.dll
    WS2_32.dll
    OLEAUT32.dll
    COMDLG32.dll
    COMCTL32.dll
    IMM32.dll
    UxTheme.dll
    VCRUNTIME140.dll
    KERNEL32.dll
    ADVAPI32.dll
    NETAPI32.dll
    GDI32.dll
    USER32.dll
    USERENV.dll
    WINSPOOL.DRV
    SHELL32.dll
    ole32.dll
    OLEACC.dll
    api-ms-win-crt-string-l1-1-0.dll
    api-ms-win-crt-stdio-l1-1-0.dll
    api-ms-win-crt-math-l1-1-0.dll
    api-ms-win-crt-environment-l1-1-0.dll
    api-ms-win-crt-time-l1-1-0.dll
    api-ms-win-crt-convert-l1-1-0.dll
    api-ms-win-crt-heap-l1-1-0.dll
    api-ms-win-crt-utility-l1-1-0.dll
    api-ms-win-crt-locale-l1-1-0.dll

  Summary

        7000 .data
       20000 .pdata
       FB000 .rdata
        7000 .reloc
       23000 .rsrc
      2FB000 .text

3. CMake Variables

Variable Default Value Documentation
TCL_STATIC_BUILD_BUILD_AND_INSTALL_TCL_TK On Whether to build and install Tcl and Tk. This recipe can be used to build Tcl only at configure time
    so it can be part of a super build which needs Tcl in some subsequent step without installing it at build time.
TCL_STATIC_BUILD_TCL_LOCAL_SOURCES “” Use a local copy of Tcl sources for offline builds
TCL_STATIC_BUILD_CRITCL_LOCAL_SOURCES “” Use a local copy of Critcl sources
TCL_STATIC_BUILD_TCLLIB_LOCAL_SOURCES “” Use a local copy of TclLib, the Tcl standard library
TCL_STATIC_BUILD_TK_LOCAL_SOURCES “” Use a local copy of Tk sources for offline builds
TCL_STATIC_BUILD_TK Off Optionally build Tk, only required for GUI applications
TCL_STATIC_BUILD_UNIX_TCLSH Configure time location of the Tcl interpreter on Unix Path to the configure time location of tclsh for Unix and MacOs builds
TCL_STATIC_BUILD_WIN_TCLSH Configure time location of the Tcl interperter on Windows Path to the configure time location of tclsh for Windows builds

4. Implementation

4.1. Preamble

cmake_minimum_required(VERSION 3.14)
project(tcl-static-build)
include(ExternalProject)
include(FetchContent)

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/modules")
include(Utils)

if(NOT (MSVC OR APPLE OR UNIX))
  message(FATAL_ERROR "This build currenly works only with macOS, Microsoft Visual Studio and Linux.")
endif()

TCL_STATIC_BUILD_BUILD_AND_INSTALL_TCL_TK toggles whether to build and install Tcl/Tk, if the user has not set it assume we do.

if(NOT DEFINED TCL_STATIC_BUILD_BUILD_AND_INSTALL_TCL_TK)
  set(TCL_STATIC_BUILD_BUILD_AND_INSTALL_TCL_TK On)
endif()

4.2. Check for zip

In theory if zip is not available the Tcl/Tk sources ship with minizip, a small drop in replacement, but that doesn’t seem to work on Unix builds. On most distros zip is available by default and if not (looking at you Arch) it is an easy enough dependency to add.

if(APPLE OR UNIX)
  find_program(TCL_STATIC_BUILD_ZIP_EXECUTABLE NAMES zip)
  if(NOT TCL_STATIC_BUILD_ZIP_EXECUTABLE)
    message(FATAL_ERROR "Could not find 'zip' which is necessary for building with ZipFs support.")
  endif()
endif()

4.3. Check for libtool

if(APPLE OR UNIX)
  find_program(TCL_STATIC_BUILD_LIBTOOL libtool)
  if(NOT TCL_STATIC_BUILD_LIBTOOL)
    message(FATAL_ERROR "Could not find 'libtool' which is necessary to build static archives")
  endif()
endif()

4.4. Set Tcl/Tk version

set(TCL_STATIC_BUILD_VERSION "9.1")
set(TCL_STATIC_ZIP_VERSION 91a1)

4.5. Set Tcl Standard Library Versions

set(TCL_STATIC_BUILD_CRITCL_VERSION "3.3.1")
set(TCL_STATIC_BUILD_TCLLIB_VERSION "2.0")

4.6. Set Tcl/Tk Source URLs

Check if the user has specified offline paths to the Tcl/Tk source otherwise get it from the official site.

if(TCL_STATIC_BUILD_TCL_LOCAL_SOURCES)
  to_absolute_path(${TCL_STATIC_BUILD_TCL_LOCAL_SOURCES} TCL_STATIC_BUILD_URL)
else()
  set(TCL_STATIC_BUILD_URL http://prdownloads.sourceforge.net/tcl/tcl${TCL_STATIC_ZIP_VERSION}-src.zip)
endif()
if(TCL_STATIC_BUILD_TK_LOCAL_SOURCES)
  to_absolute_path(${TCL_STATIC_BUILD_TK_LOCAL_SOURCES} TK_STATIC_BUILD_URL)
else()
  set(TK_STATIC_BUILD_URL http://prdownloads.sourceforge.net/tcl/tk${TCL_STATIC_ZIP_VERSION}-src.zip)
endif()

4.7. Set Critcl Source URL

if(TCL_STATIC_BUILD_CRITCL_LOCAL_SOURCES)
  to_absolute_path(${TCL_STATIC_BUILD_CRITCL_LOCAL_SOURCES} CRITCL_STATIC_BUILD_URL)
else()
  set(CRITCL_STATIC_BUILD_URL http://github.com/andreas-kupries/critcl/zipball/${TCL_STATIC_BUILD_CRITCL_VERSION})
endif()

4.8. Set Tcllib Source URL

if(TCL_STATIC_BUILD_TCLLIB_LOCAL_SOURCES)
  to_absolute_path(${TCL_STATIC_BUILD_TCLLIB_LOCAL_SOURCES} CRITCL_STATIC_BUILD_URL)
else()
  set(TCLLIB_STATIC_BUILD_URL https://core.tcl-lang.org/tcllib/uv/tcllib-${TCL_STATIC_BUILD_TCLLIB_VERSION}.zip)
endif()

4.9. Make Tcl/Tk sources available at configure time

4.9.1. Warn if install location is not overridden

This build is meant to be used in an isolated workspace so if CMAKE_INSTALL_PREFIX is still pointing to the system wide install location let the user know.

if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
  if(APPLE OR UNIX)
    set(EXAMPLE_INSTALL_PREFIX "/home/user/MyWorkspace")
  else()
    set(EXAMPLE_INSTALL_PREFIX "C:\User\user\MyWorkspace")
  endif()
  message(WARNING "CMAKE_INSTALL_PREFIX is the default ${CMAKE_INSTALL_PREFIX}. It is recommended to override it to your local workspace, eg. 'cmake .. -DCMAKE_INSTALL_PREFIX=${EXAMPLE_INSTALL_PREFIX}'")
endif()

4.9.2. Download or Pull in Local Tcl/Tk sources

  1. Declare Tcl sources
    set(TCL_SOURCES tcl${TCL_STATIC_BUILD_VERSION})
    FetchContent_Declare(
      ${TCL_SOURCES}
      URL ${TCL_STATIC_BUILD_URL}
      DOWNLOAD_EXTRACT_TIMESTAMP FALSE
    )
    
  2. Declare Critcl sources
    set(CRITCL_SOURCES critcl${TCL_STATIC_BUILD_CRITCL_VERSION})
    FetchContent_Declare(
      ${CRITCL_SOURCES}
      URL ${CRITCL_STATIC_BUILD_URL}
      DOWNLOAD_EXTRACT_TIMESTAMP FALSE
    )
    
  3. Declare Tcllib sources
    set(TCLLIB_SOURCES tcllib${TCL_STATIC_BUILD_TCLLIB_VERSION})
    FetchContent_Declare(
      ${TCLLIB_SOURCES}
      URL ${TCLLIB_STATIC_BUILD_URL}
      DOWNLOAD_EXTRACT_TIMESTAMP FALSE
    )
    
  4. Declare Tk sources
    if(TCL_STATIC_BUILD_TK)
      set(TK_SOURCES tk${TCL_STATIC_BUILD_VERSION})
      FetchContent_Declare(
        ${TK_SOURCES}
        URL ${TK_STATIC_BUILD_URL}
        DOWNLOAD_EXTRACT_TIMESTAMP FALSE
      )
    endif()
    
  5. Download Tcl sources
    if(NOT ${TCL_SOURCES}_POPULATED)
      if(NOT TCL_STATIC_BUILD_TCL_LOCAL_SOURCES)
        message(STATUS "Downloading Tcl sources: ${TCL_STATIC_BUILD_URL} ...")
      else()
        message(STATUS "Using local Tcl sources: ${TCL_STATIC_BUILD_URL}")
      endif()
      FetchContent_MakeAvailable(${TCL_SOURCES})
      if(NOT TCL_STATIC_BUILD_TCL_LOCAL_SOURCES)
        message(STATUS "Completed download: ${${TCL_SOURCES}_SOURCE_DIR}")
      endif()
    endif()
    
  6. Download Critcl sources
    if(NOT ${CRITCL_SOURCES}_POPULATED)
      if(NOT TCL_STATIC_BUILD_CRITCL_LOCAL_SOURCES)
        message(STATUS "Downloading Critcl sources: ${CRITCL_STATIC_BUILD_URL} ...")
      else()
        message(STATUS "Using local Critcl sources: ${CRITCL_STATIC_BUILD_URL}")
      endif()
      FetchContent_MakeAvailable(${CRITCL_SOURCES})
      if(NOT TCL_STATIC_BUILD_TCL_LOCAL_SOURCES)
        message(STATUS "Completed download: ${${CRITCL_SOURCES}_SOURCE_DIR}")
      endif()
    endif()
    
  7. Download Tcllib sources
    if(NOT ${TCLLIB_SOURCES}_POPULATED)
      if(NOT TCL_STATIC_BUILD_TCLLIB_LOCAL_SOURCES)
        message(STATUS "Downloading Tcllib sources: ${TCLLIB_STATIC_BUILD_URL} ...")
      else()
        message(STATUS "Using local Tcllib sources: ${TCLLIB_STATIC_BUILD_URL}")
      endif()
      FetchContent_MakeAvailable(${TCLLIB_SOURCES})
      if(NOT TCL_STATIC_BUILD_TCL_LOCAL_SOURCES)
        message(STATUS "Completed download: ${${TCLLIB_SOURCES}_SOURCE_DIR}")
      endif()
    endif()
    
  8. Download Tk sources
    if(TCL_STATIC_BUILD_TK)
      if(NOT ${TK_SOURCES}_POPULATED)
        if(NOT TCL_STATIC_BUILD_TK_LOCAL_SOURCES)
          message(STATUS "Downloading Tk sources: ${TK_STATIC_BUILD_URL} ...")
        else()
          message(STATUS "Using local Tk sources: ${TK_STATIC_BUILD_URL}")
        endif()
        FetchContent_MakeAvailable(${TK_SOURCES})
        if(NOT TCL_STATIC_BUILD_TK_LOCAL_SOURCES)
          message(STATUS "Completed download: ${${TK_SOURCES}_SOURCE_DIR}")
        endif()
      endif()
    endif()
    

4.10. Kick off a Unix or Window build

if(APPLE OR UNIX)
 include(LinuxMacosBuild)
else()
  include(Windows)
endif()

4.11. Linux and MacOS

4.11.1. Set configure time path to tclsh

set(TCL_STATIC_BUILD_TCLSH_EXE "tclsh9.1")
set(TCL_STATIC_BUILD_WISH_EXE "wish9.1")
normalize_path(TCL_STATIC_BUILD_UNIX_TCLSH ${FETCHCONTENT_BASE_DIR} "bin" ${TCL_STATIC_BUILD_TCLSH_EXE})
normalize_path(TCL_STATIC_BUILD_UNIX_INSTALLED_TCLSH ${CMAKE_INSTALL_PREFIX} "bin" ${TCL_STATIC_BUILD_TCLSH_EXE})

4.11.2. Build Tcl at configure time

if(EXISTS ${TCL_STATIC_BUILD_UNIX_TCLSH})
  check_tcl_version_matches_required_version(TCL_SOURCES_TCLSH_MATCHES_REQUIRED ${TCL_STATIC_BUILD_UNIX_TCLSH})
  if(${TCL_SOURCES_TCLSH_MATCHES_REQUIRED})
    message(STATUS "The Tcl executable already exists: ${TCL_STATIC_BUILD_UNIX_TCLSH}")
  else()
    message(STATUS "Rebuilding Tcl since the existing executable is not the required version: ${TCL_STATIC_BUILD_VERSION}")
    build_tcl_unix_at_configure_time(${${TCL_SOURCES}_SOURCE_DIR} ${FETCHCONTENT_BASE_DIR})
  endif()
else()
  build_tcl_unix_at_configure_time(${${TCL_SOURCES}_SOURCE_DIR} ${FETCHCONTENT_BASE_DIR})
endif()

4.11.3. Add Build Time Tcl Target

if(TCL_STATIC_BUILD_BUILD_AND_INSTALL_TCL_TK)
  build_tcl_unix(${${TCL_SOURCES}_SOURCE_DIR} ${CMAKE_INSTALL_PREFIX})
  symlink_tcl(${TCL_STATIC_BUILD_TCLSH_EXE})
  build_critcl(${TCL_STATIC_BUILD_UNIX_INSTALLED_TCLSH} ${${CRITCL_SOURCES}_SOURCE_DIR})
  build_tcllib(${TCL_STATIC_BUILD_UNIX_INSTALLED_TCLSH} ${${TCLLIB_SOURCES}_SOURCE_DIR} ${CMAKE_INSTALL_PREFIX})
  if(TCL_STATIC_BUILD_TK)
    build_tk_unix(${${TK_SOURCES}_SOURCE_DIR} ${CMAKE_INSTALL_PREFIX} ${CMAKE_INSTALL_PREFIX})
    symlink_tk(${TCL_STATIC_BUILD_WISH_EXE})
  endif()
endif()

4.12. Windows

4.12.1. Set configure time path to tclsh

Note the “s” after tclsh91 and wish91, probably denotes a static build …

set(TCL_STATIC_BUILD_TCLSH_EXE "tclsh91s.exe")
set(TCL_STATIC_BUILD_WISH_EXE "wish91s.exe")
normalize_path(TCL_STATIC_BUILD_WIN_TCLSH ${FETCHCONTENT_BASE_DIR} "bin" ${TCL_STATIC_BUILD_TCLSH_EXE})
normalize_path(TCL_STATIC_BUILD_WIN_INSTALLED_TCLSH ${CMAKE_INSTALL_PREFIX} "bin" ${TCL_STATIC_BUILD_TCLSH_EXE})

4.12.2. Build Tcl at configure time

if(EXISTS ${TCL_STATIC_BUILD_WIN_TCLSH})
  check_tcl_version_matches_required_version(TCL_SOURCES_TCLSH_MATCHES_REQUIRED ${TCL_STATIC_BUILD_WIN_TCLSH})
  if(${TCL_SOURCES_TCLSH_MATCHES_REQUIRED})
    message(STATUS "The Tcl executable already exists: ${TCL_STATIC_BUILD_WIN_TCLSH}")
  else()
    message(STATUS "Rebuilding Tcl since the existing executable is not the required version: ${TCL_STATIC_BUILD_VERSION}")
    build_tcl_win_at_configure_time(${${TCL_SOURCES}_SOURCE_DIR} ${FETCHCONTENT_BASE_DIR})
  endif()
else()
  build_tcl_win_at_configure_time(${${TCL_SOURCES}_SOURCE_DIR} ${FETCHCONTENT_BASE_DIR})
endif()

4.12.3. Add Build Time Tcl Target

if(TCL_STATIC_BUILD_BUILD_AND_INSTALL_TCL_TK)
  build_tcl_win(${${TCL_SOURCES}_SOURCE_DIR} ${CMAKE_INSTALL_PREFIX})
  symlink_tcl_win(${TCL_STATIC_BUILD_TCLSH_EXE})
  build_critcl(${TCL_STATIC_BUILD_WIN_INSTALLED_TCLSH} ${${CRITCL_SOURCES}_SOURCE_DIR})
  build_tcllib_win(${TCL_STATIC_BUILD_WIN_INSTALLED_TCLSH} ${${TCLLIB_SOURCES}_SOURCE_DIR})
  if(TCL_STATIC_BUILD_TK)
    build_tk_win(${${TK_SOURCES}_SOURCE_DIR} ${${TCL_SOURCES}_SOURCE_DIR} ${CMAKE_INSTALL_PREFIX})
    symlink_tk_win(${TCL_STATIC_BUILD_WISH_EXE})
  endif()
endif()

4.13. Utilities

4.13.1. Concatenate and normalize a list of paths

function(normalize_path output_path path)
  cmake_path(
    APPEND path ${ARGN}
    OUTPUT_VARIABLE UNNORMALIZED_PATH_LOCAL
  )
  cmake_path(
    NORMAL_PATH UNNORMALIZED_PATH_LOCAL
    OUTPUT_VARIABLE NORMALIZED_PATH_LOCAL
  )
  set(${output_path} ${NORMALIZED_PATH_LOCAL} PARENT_SCOPE)
endfunction()

4.13.2. Convert path to absolute path

function(to_absolute_path path out_variable)
  normalize_path(PATH_LOCAL_NORMALIZED ${path})
  file(REAL_PATH ${PATH_LOCAL_NORMALIZED} PATH_LOCAL BASE_DIRECTORY "${CMAKE_BINARY_DIR}")
  set(${out_variable} ${PATH_LOCAL} PARENT_SCOPE)
endfunction()

4.13.3. Check Tcl Version

If Tcl has already been built once before check that the version Tcl executable matches what we expect.

function(check_tcl_version_matches_required_version output_variable tclsh)
  normalize_path(CHECK_VERSION_TCL_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR} "modules" "check_version.tcl")
  execute_process(
    COMMAND ${tclsh} ${CHECK_VERSION_TCL_SCRIPT} "${TCL_STATIC_BUILD_VERSION}"
    RESULT_VARIABLE TCL_VERSION_CHECK_RESULT_LOCAL
  )
  if(TCL_VERSION_CHECK_RESULT_LOCAL EQUAL 0)
    set(${output_variable} Y PARENT_SCOPE)
  else()
    set(${output_variable} N PARENT_SCOPE)
  endif()
endfunction()

Check the required version against the major/minor/patch version of this Tcl interpreter but only the prefix, eg. 9.1 will match 9.1a

set required_version [lindex $argv 0]
if {[string first $required_version [info patchlevel]] eq 0} {
    exit 0
} else {
    exit 1
}

4.13.4. Build Tcl at configure time

function(build_tcl_unix_at_configure_time source_dir install_prefix)
  find_program(MAKE_EXE NAMES make REQUIRED)
  normalize_path(TCL_SOURCES_UNIX_DIRECTORY ${source_dir} "unix")
  normalize_path(TCL_SOURCES_UNIX_CONFIGURE ${TCL_SOURCES_UNIX_DIRECTORY} "configure")
  execute_process(
    COMMAND ${TCL_SOURCES_UNIX_CONFIGURE} --disable-shared --prefix=${install_prefix}
    WORKING_DIRECTORY ${TCL_SOURCES_UNIX_DIRECTORY}
    COMMAND_ECHO STDOUT
  )
  execute_process(
    COMMAND make install
    WORKING_DIRECTORY ${TCL_SOURCES_UNIX_DIRECTORY}
    COMMAND_ECHO STDOUT
  )
endfunction()
function(build_tcl_win_at_configure_time source_dir install_prefix)
  find_program(MAKE_EXE NAMES nmake nmake.exe REQUIRED)
  normalize_path(TCL_SOURCES_WIN_DIRECTORY ${source_dir} "win")
  execute_process(
    COMMAND ${MAKE_EXE} /f makefile.vc release OPTS=static INSTALLDIR=${install_prefix}
    WORKING_DIRECTORY ${TCL_SOURCES_WIN_DIRECTORY}
    COMMAND_ECHO STDOUT
  )
  execute_process(
    COMMAND ${MAKE_EXE} /f makefile.vc install OPTS=static INSTALLDIR=${install_prefix}
    WORKING_DIRECTORY ${TCL_SOURCES_WIN_DIRECTORY}
    COMMAND_ECHO STDOUT
  )
endfunction()

4.13.5. Build Tcl

function(build_tcl_unix source_dir install_prefix)
  find_program(MAKE_EXE make REQUIRED)
  normalize_path(TCL_SOURCES_UNIX_DIRECTORY ${source_dir} "unix")
  normalize_path(TCL_SOURCES_UNIX_CONFIGURE ${TCL_SOURCES_UNIX_DIRECTORY} "configure")
  ExternalProject_Add(
    tcl
    SOURCE_DIR ${TCL_SOURCES_UNIX_DIRECTORY}
    CONFIGURE_COMMAND ${TCL_SOURCES_UNIX_CONFIGURE} --disable-shared --prefix=${install_prefix}
    BUILD_COMMAND ${MAKE_EXE}
    INSTALL_COMMAND ${MAKE_EXE} install
  )
endfunction()

4.13.6. Build Tcl (Windows)

NOTE:

  • CONFIGURE_COMMAND must be explicitly set to the empty string on Windows or MSBuild.exe will attempt to build tcl as a CMake project
  • BUILD_IN_SOURCE tells MSBuild.exe to cd into the tcl Windows source directory, if it isn’t set MSBuild.exe will attempt to build from the top level directory and will error on not being able find makefile.vc
function(build_tcl_win source_dir install_prefix)
  find_program(MAKE_EXE NAMES nmake nmake.exe REQUIRED)
  normalize_path(TCL_SOURCES_WIN_DIRECTORY ${source_dir} "win")
  ExternalProject_Add(
    tcl
    SOURCE_DIR ${TCL_SOURCES_WIN_DIRECTORY}
    CONFIGURE_COMMAND ""
    BUILD_COMMAND ${MAKE_EXE} /f makefile.vc release OPTS=static INSTALLDIR=${install_prefix}
    INSTALL_COMMAND ${MAKE_EXE} /f makefile.vc install OPTS=static INSTALLDIR=${install_prefix}
    BUILD_IN_SOURCE TRUE
  )
endfunction()

4.13.7. Symlink Tcl

function(symlink_tcl tclsh_exe)
  add_custom_command(
    TARGET tcl
    COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_INSTALL_PREFIX}/bin/${tclsh_exe} ${CMAKE_INSTALL_PREFIX}/bin/tclsh
    POST_BUILD
  )
endfunction()

4.13.8. Symlink Tcl (Windows)

On Windows symlinking unfortunately requires higher privileges, as a placeholder we just copy the binary as a safe workaround until we come up with a better alternative.

function(symlink_tcl_win tclsh_exe)
  add_custom_command(
    TARGET tcl
    COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_INSTALL_PREFIX}/bin/${tclsh_exe} ${CMAKE_INSTALL_PREFIX}/bin/tclsh.exe
    POST_BUILD
  )
endfunction()

4.13.9. Build Critcl

function(build_critcl tclsh source_dir)
  ExternalProject_Add(
    critcl
    SOURCE_DIR ${source_dir}
    CONFIGURE_COMMAND ""
    BUILD_COMMAND ${CMAKE_COMMAND} -E env --modify PATH=path_list_prepend:${CMAKE_INSTALL_PREFIX}/bin ${tclsh} ${source_dir}/build.tcl install
    INSTALL_COMMAND ""
    BUILD_IN_SOURCE TRUE
    DEPENDS tcl
  )
endfunction()

4.13.10. Build Tcllib

function(build_tcllib tclsh source_dir install_prefix)
  find_program(MAKE_EXE make REQUIRED)
  normalize_path(TCLLIB_SOURCES_UNIX_CONFIGURE ${source_dir} "configure")
  ExternalProject_Add(
    tcllib
    SOURCE_DIR ${source_dir}
    CONFIGURE_COMMAND ${TCLLIB_SOURCES_UNIX_CONFIGURE} --with-tclsh=${tclsh} --prefix=${install_prefix}
    BUILD_COMMAND ${CMAKE_COMMAND} -E env --modify PATH=path_list_prepend:${CMAKE_INSTALL_PREFIX}/bin ${MAKE_EXE}
    INSTALL_COMMAND ${CMAKE_COMMAND} -E env --modify PATH=path_list_prepend:${CMAKE_INSTALL_PREFIX}/bin ${MAKE_EXE} install
    DEPENDS critcl
  )
endfunction()

4.13.11. Build TclLib (Windows)

On Windows, critcl is installed as script, critcl.tcl instead of an executable so we have to set the CRITCL environment variable.

function(build_tcllib_win tclsh source_dir)
  ExternalProject_Add(
    tcllib
    SOURCE_DIR ${source_dir}
    CONFIGURE_COMMAND ""
    BUILD_COMMAND ${CMAKE_COMMAND} -E env --modify PATH=path_list_prepend:${CMAKE_INSTALL_PREFIX}/bin ${CMAKE_COMMAND} -E env --modify CRITCL=path_list_prepend:${CMAKE_INSTALL_PREFIX}/bin/critcl.tcl ${tclsh} sak.tcl critcl
    INSTALL_COMMAND ${CMAKE_COMMAND} -E env --modify PATH=path_list_prepend:${CMAKE_INSTALL_PREFIX}/bin ${tclsh} installer.tcl
    BUILD_IN_SOURCE TRUE
    DEPENDS critcl
  )
endfunction()

4.13.12. Build Tk

function(build_tk_unix source_dir tcl_install_prefix install_prefix)
  find_program(MAKE_EXE make REQUIRED)
  normalize_path(TK_SOURCES_UNIX_DIRECTORY ${source_dir} "unix")
  normalize_path(TK_SOURCES_UNIX_CONFIGURE ${TK_SOURCES_UNIX_DIRECTORY} "configure")
  normalize_path(TCL_LIB_PATH ${tcl_install_prefix} "lib")
  ExternalProject_Add(
    tk
    DEPENDS tcl
    SOURCE_DIR ${TK_SOURCES_UNIX_DIRECTORY}
    CONFIGURE_COMMAND ${TK_SOURCES_UNIX_CONFIGURE} --disable-shared --with-tcl=${TCL_LIB_PATH} --prefix=${install_prefix}
    BUILD_COMMAND ${MAKE_EXE}
    INSTALL_COMMAND ${MAKE_EXE} install
  )
endfunction()

4.13.13. Build Tk (Windows)

NOTE:

  • CONFIGURE_COMMAND must be explicitly set to the empty string on Windows or MSBuild.exe will attempt to build tk as a CMake project
  • BUILD_IN_SOURCE tells MSBuild.exe to cd into the tk Windows source directory, if it isn’t set MSBuild.exe will attempt to build from the top level directory and will error on not being able find makefile.vc
function(build_tk_win source_dir tcl_sources_dir install_prefix)
  find_program(MAKE_EXE NAMES nmake nmake.exe REQUIRED)
  normalize_path(TK_SOURCES_WIN_DIRECTORY ${source_dir} "win")
  ExternalProject_Add(
    tk
    DEPENDS tcl
    SOURCE_DIR ${TK_SOURCES_WIN_DIRECTORY}
    CONFIGURE_COMMAND ""
    BUILD_COMMAND ${MAKE_EXE} /f makefile.vc release OPTS=static INSTALLDIR=${install_prefix} TCLDIR=${tcl_sources_dir}
    INSTALL_COMMAND ${MAKE_EXE} /f makefile.vc install OPTS=static INSTALLDIR=${install_prefix} TCLDIR=${tcl_sources_dir}
    BUILD_IN_SOURCE TRUE
  )
endfunction()

4.13.14. Symlink Tk

function(symlink_tk wish_exe)
  add_custom_command(
    TARGET tk
    COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_INSTALL_PREFIX}/bin/${wish_exe} ${CMAKE_INSTALL_PREFIX}/bin/wish
    POST_BUILD
  )
endfunction()

4.13.15. Symlink Tk (Windows)

We run into the same issue symlinking Tk as symlinking tclsh, just copy for now and figure it out later …

function(symlink_tk_win wish_exe)
  add_custom_command(
    TARGET tk
    COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_INSTALL_PREFIX}/bin/${wish_exe} ${CMAKE_INSTALL_PREFIX}/bin/wish.exe
    POST_BUILD
  )
endfunction()

Author: Aditya Siram