Skip to content

Instantly share code, notes, and snippets.

@gromgit
Last active February 18, 2025 15:52
Show Gist options
  • Save gromgit/a43b69fade5979f0874a9cd4777abdb6 to your computer and use it in GitHub Desktop.
Save gromgit/a43b69fade5979f0874a9cd4777abdb6 to your computer and use it in GitHub Desktop.
building statically-linked tclsh with built-in SQLite
#!/usr/bin/env bash
fatal() {
printf 'FATAL ERROR: %s' "$*"
exit 1
}
base=tcl${ver:-9.0.1}
cd "$(dirname "$0")" || fatal "unable to cd to script dir"
instroot=$(pwd)/inst
[[ -s ${base}-src.tar.gz ]] || fatal "unable to find src tarball 'tcl${ver}-src.tar.gz'"
rm -fr build "$instroot" "$base"
tar xf "$base"-src.tar.gz || fatal "unable to unpack src tarball 'tcl${ver}-src.tar.gz'"
patch -b <sqtcl.patch || fatal "unable to patch src"
extra_cfg=("--disable-shared")
sqlite_lib="${sqlite_lib:-$(brew --prefix sqlite)/lib/libsqlite3.a}"
[[ -s $sqlite_lib ]] || fatal "can't find SQLite static lib '$sqlite_lib'"
mkdir build
( cd build || fatal "unable to cd to build dir"
../"${base}"/unix/configure --prefix="$instroot" "${extra_cfg[@]}"
make SQLITE_A="$sqlite_lib" install
) |& tee build.log
otool -L "$instroot"/bin/*tclsh*
*** tcl9.0.1/unix/Makefile.in.orig 2024-12-13 02:07:26.000000000 +0800
--- tcl9.0.1/unix/Makefile.in 2024-12-27 18:19:30.554064485 +0800
***************
*** 169,174 ****
--- 169,179 ----
TCLTEST_EXE = tcltest${EXE_SUFFIX}
NATIVE_TCLSH = @TCLSH_PROG@
+ # Static SQLite-enabled tclsh
+ SQTCL_EXE = sqtclsh${EXE_SUFFIX}
+ SQTCLSH_OBJS = sqtclAppInit.o
+ SQLITE_A = /opt/homebrew/opt/sqlite/lib/libsqlite.a
+
# The symbols below provide support for dynamic loading and shared libraries.
# See configure.ac for a description of what the symbols mean. The values of
# the symbols are normally set by the configure script. You shouldn't normally
***************
*** 769,778 ****
# Start of rules
#--------------------------------------------------------------------------
! all: binaries libraries doc packages
binaries: ${LIB_FILE} ${TCL_EXE}
libraries:
doc:
--- 774,785 ----
# Start of rules
#--------------------------------------------------------------------------
! all: binaries libraries doc packages extra-binaries
binaries: ${LIB_FILE} ${TCL_EXE}
+ extra-binaries: ${SQTCL_EXE}
+
libraries:
doc:
***************
*** 840,845 ****
--- 847,857 ----
fi; \
fi
+ ${SQTCL_EXE}: ${SQTCLSH_OBJS} ${TCL_LIB_FILE} ${TCL_STUB_LIB_FILE} ${TCL_ZIP_FILE}
+ ${CC} ${CFLAGS} ${LDFLAGS} ${SQTCLSH_OBJS} pkgs/sqlite*/*.o \
+ @TCL_BUILD_LIB_SPEC@ ${TCL_STUB_LIB_FILE} ${LIBS} ${SQLITE_A} @EXTRA_TCLSH_LIBS@ \
+ ${CC_SEARCH_FLAGS} -o ${SQTCL_EXE}
+
# Must be empty so it doesn't conflict with rule for ${TCL_EXE} above
${NATIVE_TCLSH}:
***************
*** 1000,1006 ****
INSTALL_DOC_TARGETS = install-doc
INSTALL_PACKAGE_TARGETS = install-packages
INSTALL_DEV_TARGETS = install-headers
! INSTALL_EXTRA_TARGETS = @EXTRA_INSTALL@
INSTALL_TARGETS = $(INSTALL_BASE_TARGETS) $(INSTALL_DOC_TARGETS) $(INSTALL_DEV_TARGETS) \
$(INSTALL_PACKAGE_TARGETS) $(INSTALL_EXTRA_TARGETS)
--- 1012,1018 ----
INSTALL_DOC_TARGETS = install-doc
INSTALL_PACKAGE_TARGETS = install-packages
INSTALL_DEV_TARGETS = install-headers
! INSTALL_EXTRA_TARGETS = @EXTRA_INSTALL@ install-extra-binaries
INSTALL_TARGETS = $(INSTALL_BASE_TARGETS) $(INSTALL_DOC_TARGETS) $(INSTALL_DEV_TARGETS) \
$(INSTALL_PACKAGE_TARGETS) $(INSTALL_EXTRA_TARGETS)
***************
*** 1038,1043 ****
--- 1050,1059 ----
@$(INSTALL_DATA_DIR) "$(LIB_INSTALL_DIR)/pkgconfig"
@$(INSTALL_DATA) tcl.pc "$(LIB_INSTALL_DIR)/pkgconfig/tcl.pc"
+ install-extra-binaries: extra-binaries
+ @echo "Installing ${SQTCL_EXE} as $(BIN_INSTALL_DIR)/sqtclsh$(VERSION)${EXE_SUFFIX}"
+ @$(INSTALL_PROGRAM) ${SQTCL_EXE} "$(BIN_INSTALL_DIR)/sqtclsh$(VERSION)${EXE_SUFFIX}"
+
install-libraries: libraries
@for i in "$(SCRIPT_INSTALL_DIR)" "$(MODULE_INSTALL_DIR)"; \
do \
***************
*** 1278,1283 ****
--- 1294,1302 ----
regerror.o: $(REGHDRS) $(GENERIC_DIR)/regerrs.h $(GENERIC_DIR)/regerror.c
$(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/regerror.c
+ sqtclAppInit.o: $(UNIX_DIR)/sqtclAppInit.c
+ $(CC) -c $(APP_CC_SWITCHES) $(UNIX_DIR)/sqtclAppInit.c
+
tclAppInit.o: $(UNIX_DIR)/tclAppInit.c
$(CC) -c $(APP_CC_SWITCHES) $(UNIX_DIR)/tclAppInit.c
*** tcl9.0.1/unix/sqtclAppInit.c.orig 2024-12-27 18:17:03.822888072 +0800
--- tcl9.0.1/unix/sqtclAppInit.c 2024-12-27 18:17:03.822976154 +0800
***************
*** 0 ****
--- 1,184 ----
+ /*
+ * tclAppInit.c --
+ *
+ * Provides a default version of the main program and Tcl_AppInit
+ * procedure for tclsh and other Tcl-based applications (without Tk).
+ *
+ * Copyright (c) 1993 The Regents of the University of California.
+ * Copyright (c) 1994-1997 Sun Microsystems, Inc.
+ * Copyright (c) 1998-1999 Scriptics Corporation.
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+ #include "tcl.h"
+ #if TCL_MAJOR_VERSION < 9
+ # if defined(USE_TCL_STUBS)
+ # error "Don't build with USE_TCL_STUBS!"
+ # endif
+ # if TCL_MINOR_VERSION < 7
+ # define Tcl_LibraryInitProc Tcl_PackageInitProc
+ # define Tcl_StaticLibrary Tcl_StaticPackage
+ # endif
+ #endif
+
+ #ifdef TCL_TEST
+ extern Tcl_LibraryInitProc Tcltest_Init;
+ extern Tcl_LibraryInitProc Tcltest_SafeInit;
+ #endif /* TCL_TEST */
+
+ #ifdef TCL_XT_TEST
+ extern void XtToolkitInitialize(void);
+ extern Tcl_LibraryInitProc Tclxttest_Init;
+ #endif /* TCL_XT_TEST */
+
+ /*
+ * The following #if block allows you to change the AppInit function by using
+ * a #define of TCL_LOCAL_APPINIT instead of rewriting this entire file. The
+ * #if checks for that #define and uses Tcl_AppInit if it does not exist.
+ */
+
+ #ifndef TCL_LOCAL_APPINIT
+ #define TCL_LOCAL_APPINIT Tcl_AppInit
+ #endif
+ #ifndef MODULE_SCOPE
+ # define MODULE_SCOPE extern
+ #endif
+ MODULE_SCOPE int TCL_LOCAL_APPINIT(Tcl_Interp *);
+ MODULE_SCOPE int main(int, char **);
+
+ /*
+ * The following #if block allows you to change how Tcl finds the startup
+ * script, prime the library or encoding paths, fiddle with the argv, etc.,
+ * without needing to rewrite Tcl_Main()
+ */
+
+ #ifdef TCL_LOCAL_MAIN_HOOK
+ MODULE_SCOPE int TCL_LOCAL_MAIN_HOOK(int *argc, char ***argv);
+ #endif
+
+ /*
+ *----------------------------------------------------------------------
+ *
+ * main --
+ *
+ * This is the main program for the application.
+ *
+ * Results:
+ * None: Tcl_Main never returns here, so this procedure never returns
+ * either.
+ *
+ * Side effects:
+ * Just about anything, since from here we call arbitrary Tcl code.
+ *
+ *----------------------------------------------------------------------
+ */
+
+ int
+ main(
+ int argc, /* Number of command-line arguments. */
+ char *argv[]) /* Values of command-line arguments. */
+ {
+ #ifdef TCL_XT_TEST
+ XtToolkitInitialize();
+ #endif
+
+ #ifdef TCL_LOCAL_MAIN_HOOK
+ TCL_LOCAL_MAIN_HOOK(&argc, &argv);
+ #elif (TCL_MAJOR_VERSION > 8 || TCL_MINOR_VERSION > 6) && (!defined(_WIN32) || defined(UNICODE))
+ /* New in Tcl 8.7. This doesn't work on Windows without UNICODE */
+ TclZipfs_AppHook(&argc, &argv);
+ #endif
+
+ Tcl_Main(argc, argv, TCL_LOCAL_APPINIT);
+ return 0; /* Needed only to prevent compiler warning. */
+ }
+
+ /*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_AppInit --
+ *
+ * This procedure performs application-specific initialization. Most
+ * applications, especially those that incorporate additional packages,
+ * will have their own version of this procedure.
+ *
+ * Results:
+ * Returns a standard Tcl completion code, and leaves an error message in
+ * the interp's result if an error occurs.
+ *
+ * Side effects:
+ * Depends on the startup script.
+ *
+ *----------------------------------------------------------------------
+ */
+
+ int Sqlite3_Init(Tcl_Interp *interp);
+
+ int
+ Tcl_AppInit(
+ Tcl_Interp *interp) /* Interpreter for application. */
+ {
+ if (Tcl_Init(interp) == TCL_ERROR) {
+ return TCL_ERROR;
+ }
+
+ #ifdef TCL_XT_TEST
+ if (Tclxttest_Init(interp) == TCL_ERROR) {
+ return TCL_ERROR;
+ }
+ #endif
+
+ #ifdef TCL_TEST
+ if (Tcltest_Init(interp) == TCL_ERROR) {
+ return TCL_ERROR;
+ }
+ Tcl_StaticLibrary(interp, "Tcltest", Tcltest_Init, Tcltest_SafeInit);
+ #endif /* TCL_TEST */
+
+ /*
+ * Call the init procedures for included packages. Each call should look
+ * like this:
+ *
+ * if (Mod_Init(interp) == TCL_ERROR) {
+ * return TCL_ERROR;
+ * }
+ *
+ * where "Mod" is the name of the module. (Dynamically-loadable packages
+ * should have the same entry-point name.)
+ */
+ if (Sqlite3_Init(interp) == TCL_ERROR) {
+ return TCL_ERROR;
+ }
+
+ /*
+ * Call Tcl_CreateObjCommand for application-specific commands, if they
+ * weren't already created by the init procedures called above.
+ */
+
+ /*
+ * Specify a user-specific startup file to invoke if the application is
+ * run interactively. Typically the startup file is "~/.apprc" where "app"
+ * is the name of the application. If this line is deleted then no
+ * user-specific startup file will be run under any conditions.
+ */
+ #ifdef DJGPP
+ #define INITFILENAME "tclshrc.tcl"
+ #else
+ #define INITFILENAME ".tclshrc"
+ #endif
+
+ (void) Tcl_EvalEx(interp,
+ "set tcl_rcFileName [file tildeexpand ~/" INITFILENAME "]",
+ -1, TCL_EVAL_GLOBAL);
+ return TCL_OK;
+ }
+
+ /*
+ * Local Variables:
+ * mode: c
+ * c-basic-offset: 4
+ * fill-column: 78
+ * End:
+ */
@gromgit
Copy link
Author

gromgit commented Feb 18, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment