From 6156becfecbaeca67e39994e0ca68706ffc5ac53 Mon Sep 17 00:00:00 2001 From: Nathan Glenn Date: Thu, 12 Oct 2023 14:24:17 -0500 Subject: [PATCH] Support custom Tcl binary suffix Fixes #396. --- Core/ClientSMLSWIG/Tcl/SConscript | 2 +- SConstruct | 7 +- Tcl/SConscript | 6 +- build_support/tcl.py | 177 ++++++++++++++++++------------ 4 files changed, 115 insertions(+), 77 deletions(-) diff --git a/Core/ClientSMLSWIG/Tcl/SConscript b/Core/ClientSMLSWIG/Tcl/SConscript index 2e52e10590..d42a73925a 100644 --- a/Core/ClientSMLSWIG/Tcl/SConscript +++ b/Core/ClientSMLSWIG/Tcl/SConscript @@ -16,7 +16,7 @@ tcl_sml_alias = clone['SML_TCL_ALIAS'] LIB_NAME = 'Tcl_sml_ClientInterface' -if clone.PrepareForCompilingWithTcl(GetOption('tcl')): +if clone.PrepareForCompilingWithTcl(clone["TCL_PATH"], clone["TCL_SUFFIX"]): print(f'{env["INDENT"]}Tcl SML library is buildable') else: print(f'{env["INDENT"]}Tcl SML library is *not* buildable') diff --git a/SConstruct b/SConstruct index a50c8f82d5..40e9839ae6 100644 --- a/SConstruct +++ b/SConstruct @@ -46,7 +46,7 @@ print(f" SWIG: {SML_PYTHON_ALIAS} {SML_TCL_ALIAS} {SML_JAVA_ALIAS print(f" Extras: debugger* headers* {COMPILE_DB_ALIAS}* tclsoarlib {MSVS_ALIAS} list") print("Custom Settings available: *default") print(" Build Type: --dbg, --opt*, --static") -print(" Custom Paths: --out, --build, --tcl") +print(" Custom Paths: --out, --build, --tcl, --tcl-suffix") print(" Compilation time: --no-svs, --scu*, --no-scu, --no-scu-kernel, --no-scu-cli") print(" Customizations: --cc, --cxx, --cflags, --lnflags, --no-default-flags, --verbose,") print("Supported platforms are 64-bit Windows, Linux, and macOS (Intel and ARM)") @@ -133,7 +133,8 @@ AddOption('--scu', action='store_true', dest='scu', default=True, help='Build us AddOption('--out', action='store', type='string', dest='outdir', default=DEF_OUT, nargs=1, metavar='DIR', help='Directory to install binaries. Defaults to "out".') AddOption('--build', action='store', type='string', dest='build-dir', default=DEF_BUILD, nargs=1, metavar='DIR', help='Directory to store intermediate (object) files. Defaults to "build".') AddOption('--python', action='store', type='string', dest='python', default=sys.executable, nargs=1, help='Python executable; defaults to same executable used to run SCons') -AddOption('--tcl', action='store', type='string', dest='tcl', nargs=1, help='Active TCL (>= 8.6) libraries') +AddOption('--tcl', action='store', type='string', dest='tcl', nargs=1, help='Path to Tcl installation (ActiveTcl or otherwise)') +AddOption('--tcl-suffix', action='store', type='string', dest='tcl_suffix', default="t", nargs=1, help='Tcl binary suffix (defaults to "t", which is used to indicate full threading support in the standard Tcl build)') AddOption('--static', action='store_true', dest='static', default=False, help='Use static linking') AddOption('--dbg', action='store_true', dest='dbg', default=False, help='Enable debug build. Disables compiler optimizations, includes debugging symbols, debug trace statements and assertions') AddOption('--opt', action='store_false', dest='dbg', default=False, help='Enable optimized build. Enables compiler optimizations, removes debugging symbols, debug trace statements and assertions') @@ -149,6 +150,8 @@ env = Environment( NO_SCU_CLI=GetOption('no_scu_cli'), BUILD_DIR=GetOption('build-dir'), OUT_DIR=os.path.realpath(GetOption('outdir')), + TCL_PATH = GetOption('tcl'), + TCL_SUFFIX = GetOption('tcl_suffix'), SOAR_VERSION=SOAR_VERSION, VISHIDDEN=False, # needed by swig JAVAVERSION='11.0', diff --git a/Tcl/SConscript b/Tcl/SConscript index d6c76abd19..e87aa2b19c 100644 --- a/Tcl/SConscript +++ b/Tcl/SConscript @@ -2,12 +2,8 @@ # Project: Soar # Author: Mazin Assanie -from dataclasses import dataclass -from pathlib import Path -import subprocess import os import sys -import SCons.Script Import('env', 'InstallDir') clone = env.Clone() @@ -15,7 +11,7 @@ clone = env.Clone() # Set up variables (some are hardcoded for now) LIB_NAME = 'tclsoarlib' -if clone.PrepareForCompilingWithTcl(GetOption('tcl')): +if clone.PrepareForCompilingWithTcl(clone["TCL_PATH"], clone["TCL_SUFFIX"]): print(f'{env["INDENT"]}TclSoarLib is buildable') else: print(f'{env["INDENT"]}TclSoarLib is *not* buildable') diff --git a/build_support/tcl.py b/build_support/tcl.py index ba5cadc602..fbebbb2ecb 100644 --- a/build_support/tcl.py +++ b/build_support/tcl.py @@ -34,17 +34,20 @@ def is_valid(self): return True, "" -def __get_tcl_from_local_dir_mac(env, local_compiled_dir=None) -> Optional[TclInstallInfo]: +def __get_tcl_from_local_dir_mac(env, local_compiled_dir=None, tcl_suffix=None) -> Optional[TclInstallInfo]: if not local_compiled_dir: return None + if tcl_suffix is None: + tcl_suffix = "" + home_dir = Path(local_compiled_dir) install_info = TclInstallInfo( home=home_dir, lib_dir=home_dir / "lib", include_dir=home_dir / "include", - dyn_lib_name="libtcl8.6.dylib", - include_lib_name="tcl8.6", + dyn_lib_name=f"libtcl8.6{tcl_suffix}.dylib", + include_lib_name=f"tcl8.6{tcl_suffix}", ) valid, msg = install_info.is_valid() if not valid: @@ -95,10 +98,92 @@ def __get_system_tcl_install_info_mac(env) -> Optional[TclInstallInfo]: return install_info -def __get_tcl_install_info_mac(env, local_compiled_dir=None) -> Optional[TclInstallInfo]: - return __get_tcl_from_local_dir_mac(env, local_compiled_dir) or \ +def __prepare_for_compiling_with_tcl_mac(env, tcl_path_override, tcl_suffix): + install_info = __get_tcl_from_local_dir_mac(env, tcl_path_override, tcl_suffix) or \ __get_brew_tcl_install_info_mac(env) or \ __get_system_tcl_install_info_mac(env) + if not install_info: + return False + + print(f"{env['INDENT']}Found Tcl: " + str(install_info.home)) + + __append_tcl_compile_flags(env, install_info) + if install_info.using_framework: + env.Append(LINKFLAGS=["-framework", install_info.dyn_lib_name]) + else: + env.Append(LIBS=[install_info.dyn_lib_name]) + + # Link error occurs if we include the -bundle flag with -flat_namespace, so we removed it + env.Append(SHLINKFLAGS=env.Split('$LINKFLAGS -flat_namespace -undefined suppress -fmessage-length=0')) + + return True + + +def __prepare_for_compiling_with_tcl_linux(env, tcl_path_override, tcl_suffix): + indent = env["INDENT"] + + if tcl_suffix is None: + tcl_suffix = "t" + + if tcl_path_override: + home_dir = Path(tcl_path_override) + install_info = TclInstallInfo( + home=home_dir, + lib_dir=home_dir / "lib", + include_dir=home_dir / "include", + dyn_lib_name=f"tcl86{tcl_suffix}.so", + include_lib_name=f"tcl86{tcl_suffix}", + ) + valid, msg = install_info.is_valid() + if not valid: + print(f"{indent}Tcl not found in {home_dir}: {msg}") + return False + print(f"{indent}Found Tcl at specified location: " + str(install_info.home)) + __append_tcl_compile_flags(env, install_info) + else: + try: + env.ParseConfig("pkg-config tcl --libs --cflags") + except OSError: + print( + f"{indent}pkg-config didn't find tcl package; try `apt-get install tcl-dev`" + ) + return False + print(f"{indent}Found Tcl with pkg-config") + + return True + + +def __prepare_for_compiling_with_tcl_windows(env, tcl_path_override, tcl_suffix): + indent = env["INDENT"] + + if tcl_suffix is None: + tcl_suffix = "t" + + if tcl_path_override: + home_dir = Path(tcl_path_override) + else: + home_dir = Path("C:/ActiveTcl") + + install_info = TclInstallInfo( + home=home_dir, + lib_dir=home_dir / "lib", + include_dir=home_dir / "include", + dyn_lib_name=f"tcl86{tcl_suffix}.lib", + include_lib_name=f"tcl86{tcl_suffix}", + ) + valid, msg = install_info.is_valid() + if not valid: + print(f"{indent}Tcl not found in {home_dir}: {msg}") + print(f"{indent}{NO_TCL_MSG}") + return False + print(f"{indent}Found Tcl: " + str(install_info.home)) + + __append_tcl_compile_flags(env, install_info) + # Windows DLLs need to get linked to dependencies, whereas Linux and Mac shared objects do not + # (not sure if this is really needed for TclSoarLib) + env.Append(LIBS=[install_info.include_lib_name]) + + return True def __append_tcl_compile_flags(env, install_info: TclInstallInfo): @@ -107,88 +192,42 @@ def __append_tcl_compile_flags(env, install_info: TclInstallInfo): env.Append(LIBPATH=[install_info.lib_dir.absolute()]) -def prepare_for_compiling_with_tcl(env, tcl_path_override=None): +# TODO: a more ideal solution would figure out the suffix for you by examining the files in +# the Tcl directory. That's a lot of work for not many users right now; plus, Tcl 8.7 won't +# even have a suffix anymore. +def prepare_for_compiling_with_tcl(env, tcl_path_override=None, tcl_suffix=None): """Find the Tcl library and add the necessary compiler/linker flags to env. Return True if successful, False otherwise. - TODO: tcl_path_override currently ignored on Linux. + tcl_path_override: If specified, this path will be searched first for a Tcl installation. + tcl_suffix: If specified, the suffix will be expected on Tcl binary names. Tcl 8.6 uses + "t" on Linux and Windows and "" on Mac by default. The suffix is supposed to indicate the + type of the Tcl build, but can be overridden by the user when building Tcl. """ + indent = env["INDENT"] + print("Looking for Tcl...") if tcl_path_override: - print(f"{env['INDENT']}Tcl path override specified: {tcl_path_override}") + print(f"{indent}Tcl path override specified: {tcl_path_override}") if not Path(tcl_path_override).exists(): raise FileNotFoundError( f"Specified Tcl path does not exist: {tcl_path_override}" ) if sys.platform == "darwin": - install_info = __get_tcl_install_info_mac(env, tcl_path_override) - if not install_info: - print(f"{env['INDENT']}{NO_TCL_MSG}") + if not __prepare_for_compiling_with_tcl_mac(env, tcl_path_override, tcl_suffix): + print(f"{indent}{NO_TCL_MSG}") return False - print(f"{env['INDENT']}Found Tcl: " + str(install_info.home)) - - __append_tcl_compile_flags(env, install_info) - if install_info.using_framework: - env.Append(LINKFLAGS=["-framework", install_info.dyn_lib_name]) - else: - env.Append(LIBS=[install_info.dyn_lib_name]) - - # Link error occurs if we include the -bundle flag with -flat_namespace, so we removed it - env.Append(SHLINKFLAGS=env.Split('$LINKFLAGS -flat_namespace -undefined suppress -fmessage-length=0')) elif sys.platform.startswith("linux"): - if tcl_path_override: - home_dir = Path(tcl_path_override) - install_info = TclInstallInfo( - home=home_dir, - lib_dir=home_dir / "lib", - include_dir=home_dir / "include", - dyn_lib_name="tcl86t.so", - include_lib_name="tcl86t", - ) - valid, msg = install_info.is_valid() - if not valid: - print(f"{env['INDENT']}Tcl not found in {home_dir}: {msg}") - print(f"{env['INDENT']}{NO_TCL_MSG}") - return False - print(f"{env['INDENT']}Found Tcl at specified location: " + str(install_info.home)) - __append_tcl_compile_flags(env, install_info) - else: - try: - env.ParseConfig("pkg-config tcl --libs --cflags") - except OSError: - print( - f"{env['INDENT']}pkg-config didn't find tcl package; try `apt-get install tcl-dev`" - ) - print(f"{env['INDENT']}{NO_TCL_MSG}") - return False - print(f"{env['INDENT']}Found Tcl with pkg-config") + if not __prepare_for_compiling_with_tcl_linux(env, tcl_path_override, tcl_suffix): + print(f"{indent}{NO_TCL_MSG}") + return False elif sys.platform == "win32": - if tcl_path_override: - home_dir = Path(tcl_path_override) - else: - home_dir = Path("C:/ActiveTcl") - - install_info = TclInstallInfo( - home=home_dir, - lib_dir=home_dir / "lib", - include_dir=home_dir / "include", - dyn_lib_name="tcl86t.lib", - include_lib_name="tcl86t", - ) - valid, msg = install_info.is_valid() - if not valid: - print(f"{env['INDENT']}Tcl not found in {home_dir}: {msg}") - print(f"{env['INDENT']}{NO_TCL_MSG}") + if not __prepare_for_compiling_with_tcl_windows(env, tcl_path_override, tcl_suffix): + print(f"{indent}{NO_TCL_MSG}") return False - print(f"{env['INDENT']}Found Tcl: " + str(install_info.home)) - - __append_tcl_compile_flags(env, install_info) - # Windows DLLs need to get linked to dependencies, whereas Linux and Mac shared objects do not - # (not sure if this is really needed for TclSoarLib) - env.Append(LIBS=[install_info.include_lib_name]) if os.name == "posix": # -fPic is needed to make the code position independent, which is necessary for Tcl.