From 9cce7d263ea3f2984a619cdfcb54d264c6a4c51d Mon Sep 17 00:00:00 2001 From: Robin Haberkorn Date: Tue, 5 Nov 2024 01:29:53 +0300 Subject: fully support relocatable binaries, improving AppImages * You can now specify `--with-scitecodatadir` as a relative path, that will be interpreted relative to the binary's location. * Win32 binaries already were relocatable, but this was a Windows-specific hack. Win32 binaries are now built with `--with-scitecodatadir=.` since everything is in a single directory. * Ubuntu packages are now also built `--with-scitecodatadir=../share/sciteco`. This is not crucial for ordinary installations, but is meant for AppImage creation. * Since AppImages are now built from relocatable packages, we no longer need the unionfs-workaround from pkg2appimage. This should fix the strange root contents when autocompleting in AppImage builds. * This might also fix the appimage.github.io CI issues. I assume that because I could reproduce the issue on FreeBSD's Linuxulator in dependence of pkg2appimage's "union"-setting. See https://github.com/AppImage/appimage.github.io/pull/3402 * Determining the binary location actually turned out be hard and very platform-dependant. There are now implementations for Windows (which could also read argv[0]), Linux and generic UNIX (which works on FreeBSD, but I am not sure about the others). I believe this could also be useful on Mac OS to create app bundles, but this needs to be tested - currently the Mac OS binaries are installed into fixed locations and don't use relocation. --- .github/workflows/nightly.yml | 19 ++++++---- AppImage/curses.yml | 5 --- AppImage/gtk.yml | 5 --- TODO | 5 --- configure.ac | 17 +++++++-- debian/rules | 9 ++++- distribute.mk.in | 5 ++- src/file-utils.c | 85 +++++++++++++++++++++++++++++++++++++++++++ src/file-utils.h | 12 ++++++ src/interface-gtk/interface.c | 32 ++++++---------- src/main.c | 23 +++++------- 11 files changed, 153 insertions(+), 64 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 4a6bc53..52c5425 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -218,9 +218,11 @@ jobs: mkdir build-wingui build-wincon (cd build-wingui ../configure --with-interface=pdcurses-gui --enable-html-manual --program-prefix=g \ + --with-scitecodatadir=. \ PDCURSES_LIBS="-lpdcurses_wingui -lgdi32 -lcomdlg32 -lwinmm") (cd build-wincon ../configure --with-interface=pdcurses --enable-html-manual \ + --with-scitecodatadir=. \ PDCURSES_LIBS="-lpdcurses_wincon -lgdi32 -lwinmm") - name: make @@ -244,9 +246,10 @@ jobs: run: | mkdir temp-bin/ cd temp-bin/ - cp /mingw32/bin/{gsciteco.exe,sciteco.exe,grosciteco.tes,tedoc.tes} ./ - cp -r /mingw32/share/sciteco/{lib,*.tmac} ./ - cp /mingw32/share/sciteco/sample.teco_ini .teco_ini + cp -r /mingw32/bin/{gsciteco.exe,sciteco.exe,grosciteco.tes,tedoc.tes} ./ + # datadir is relative to bindir + cp -r /mingw32/bin/{lib,*.tmac} ./ + cp /mingw32/bin/sample.teco_ini .teco_ini cp -r /mingw32/share/doc/sciteco/* ./ cp ../COPYING ../ChangeLog ./ cp /mingw32/bin/gspawn-win32-helper*.exe ./ @@ -300,7 +303,8 @@ jobs: LDFLAGS: -flto run: | autoreconf -i - ./configure --with-interface=gtk --enable-html-manual + ./configure --with-interface=gtk --enable-html-manual \ + --with-scitecodatadir=. - name: make run: make -j 2 @@ -317,11 +321,12 @@ jobs: mkdir temp-bin cd temp-bin cp /mingw32/bin/{sciteco.exe,grosciteco.tes,tedoc.tes} ./ - cp -r /mingw32/share/sciteco/{lib,*.tmac} ./ + # datadir is relative to bindir + cp -r /mingw32/bin/{lib,*.tmac} ./ # FIXME: Maybe there should be a separate win32/.teco_ini with # a few pre-enabled settings? - cp /mingw32/share/sciteco/sample.teco_ini .teco_ini - cp /mingw32/share/sciteco/fallback.css .teco_css + cp /mingw32/bin/sample.teco_ini .teco_ini + cp /mingw32/bin/fallback.css .teco_css cp -r /mingw32/share/doc/sciteco/* ./ cp ../COPYING ../ChangeLog ./ cp /mingw32/bin/gspawn-win32-helper*.exe ./ diff --git a/AppImage/curses.yml b/AppImage/curses.yml index 2989a39..edf9fd0 100755 --- a/AppImage/curses.yml +++ b/AppImage/curses.yml @@ -1,10 +1,5 @@ app: sciteco-curses -# We currently use paths hardcoded at build-time. -# Alternatively, it would be possible to customize the AppRun script or -# add a wrapper that sets $SCITECOPATH. -union: true - ingredients: packages: - sciteco-curses diff --git a/AppImage/gtk.yml b/AppImage/gtk.yml index d49e308..ea233db 100755 --- a/AppImage/gtk.yml +++ b/AppImage/gtk.yml @@ -1,10 +1,5 @@ app: sciteco-gtk -# We currently use paths hardcoded at build-time. -# Alternatively, it would be possible to customize the AppRun script or -# add a wrapper that sets $SCITECOPATH. -union: true - ingredients: packages: - sciteco-gtk diff --git a/TODO b/TODO index cb4fc7a..d38ada7 100644 --- a/TODO +++ b/TODO @@ -466,11 +466,6 @@ Features: * Get into mentors.debian.net. First step to being adopted into the Debian repositories. * Get meta-rhaberkorn into https://layers.openembedded.org - * Linux: Relocatable binaries instead of hardcoding the library path. - This makes it possible to run builds installed via - `make install DESTDIR=...` and will aid in creating AppImages. - Currently we have to define "union: true" and consequently - the root directory is not what we would expect. * sample.teco_ini: Support opening files on certain lines (filename:line). Theoretically, this could also be added to the syntax, diff --git a/configure.ac b/configure.ac index 870b7b0..a82eb1d 100644 --- a/configure.ac +++ b/configure.ac @@ -173,9 +173,12 @@ AC_CHECK_FUNCS([memset setlocale strchr strrchr fstat sscanf], , [ # glib defines G_OS_UNIX instead... case $host in *-*-linux* | *-*-*bsd* | *-*-darwin* | *-*-cygwin* | *-*-haiku*) - AC_CHECK_FUNCS([realpath fchown dup dup2 getpid open read kill mmap], , [ + AC_CHECK_FUNCS([realpath readlink fchown dup dup2 getpid open read kill mmap], , [ AC_MSG_ERROR([Missing libc function]) ]) + AC_SEARCH_LIBS(dladdr, [dl], , [ + AC_MSG_ERROR([No library providing dladdr()!]) + ]) ;; esac @@ -183,10 +186,17 @@ esac # Config options # +# NOTE: This can be relative to the binary location for relocateable builds. AC_ARG_WITH(scitecodatadir, AS_HELP_STRING([--with-scitecodatadir=PATH], [Installation directory of data [default=DATADIR/sciteco]]), - [scitecodatadir=$withval], [scitecodatadir=$datadir/$PACKAGE]) + [scitecodatadir_rel=$withval], [scitecodatadir_rel=$datadir/$PACKAGE]) +AC_SUBST(scitecodatadir_rel) +# The Automake installation directory needs to be absolute, though: +case "$scitecodatadir_rel" in +/*) scitecodatadir="$scitecodatadir_rel";; +*) scitecodatadir="$bindir/$scitecodatadir_rel";; +esac AC_SUBST(scitecodatadir) # SciTECO library macro directory scitecolibdir=$scitecodatadir/lib @@ -194,8 +204,7 @@ AC_SUBST(scitecolibdir) # These paths can be changed at install-time and # should not be written into config.h: -AM_CPPFLAGS="$AM_CPPFLAGS -D'SCITECODATADIR=\"\$(scitecodatadir)\"' \ - -D'SCITECOLIBDIR=\"\$(scitecolibdir)\"'" +AM_CPPFLAGS="$AM_CPPFLAGS -D'SCITECODATADIR=\"\$(scitecodatadir_rel)\"'" # FIXME: It does not really make sense to have this configurable. # It would make more sense to allow linking against an externally-provided diff --git a/debian/rules b/debian/rules index fb1d65e..8b8ad11 100755 --- a/debian/rules +++ b/debian/rules @@ -31,11 +31,15 @@ endif build build-arch build-indep: build-curses-stamp build-gtk-stamp; +# NOTE: The datadir will be relative to the binary location at runtime. +# This makes the binary relocateable, which is important when creating +# AppImages from the Debian packages. build-curses-stamp: dh_testdir rm -rf build-curses dh_auto_configure -Bbuild-curses -- \ - --with-interface=ncurses + --with-interface=ncurses \ + --with-scitecodatadir=../share/sciteco dh_auto_build -Bbuild-curses dh_auto_test -Bbuild-curses touch $@ @@ -47,7 +51,8 @@ build-gtk-stamp: rm -rf build-gtk dh_auto_configure -Bbuild-gtk -- \ --program-prefix=g \ - --with-interface=gtk + --with-interface=gtk \ + --with-scitecodatadir=../share/sciteco # NOTE: Since the Gtk+ version of SciTECO is called during the build, # we need an XServer which may be missing on the build server. # That's why we use xvfb. diff --git a/distribute.mk.in b/distribute.mk.in index 0d6942c..4eb9b91 100644 --- a/distribute.mk.in +++ b/distribute.mk.in @@ -110,6 +110,7 @@ mingw-binary : @PACKAGE@-@PACKAGE_VERSION@-win32.zip PKG_CONFIG_LIBDIR=/usr/i686-w64-mingw32/lib/pkgconfig \ ./configure --host=i686-w64-mingw32 build=i386-pc-linux-gnu \ --prefix=/usr \ + --with-scitecodatadir=. \ --enable-static-executables \ --disable-dependency-tracking \ --with-interface=$(MINGW_UI) \ @@ -122,11 +123,11 @@ mingw-binary : @PACKAGE@-@PACKAGE_VERSION@-win32.zip install-strip DESTDIR=`pwd`/temp-install rm -rf $(SCITECO_DIR)/ mkdir temp-bin/ - cp -r temp-install/usr/bin/* temp-install/usr/share/sciteco/* \ + cp -r temp-install/usr/bin/* \ temp-install/usr/share/doc/sciteco/* \ temp-bin/ cp @srcdir@/win32.teco_ini temp-bin/.teco_ini || \ - cp temp-install/usr/share/sciteco/sample.teco_ini \ + cp temp-install/usr/bin/sample.teco_ini \ temp-bin/.teco_ini cp @srcdir@/COPYING @srcdir@/ChangeLog temp-bin/ rm -rf temp-install/ diff --git a/src/file-utils.c b/src/file-utils.c index 3f8f721..b7e5418 100644 --- a/src/file-utils.c +++ b/src/file-utils.c @@ -19,6 +19,7 @@ #include "config.h" #endif +#define _GNU_SOURCE #include #include #include @@ -35,6 +36,10 @@ #include #include +#ifdef G_OS_UNIX +#include +#endif + #include "sciteco.h" #include "qreg.h" #include "interface.h" @@ -179,6 +184,86 @@ teco_file_is_visible(const gchar *path) #endif /* !G_OS_WIN32 */ +#ifdef G_OS_WIN32 + +gchar * +teco_file_get_program_path(void) +{ + TCHAR buf[MAX_PATH]; + if (!GetModuleFileNameW(NULL, buf, G_N_ELEMENTS(buf)) + return g_get_current_dir(); + g_autofree gchar *exe = g_utf16_to_utf8(buf, -1, NULL, NULL, NULL); + return g_path_get_dirname(exe); +} + +#elif defined(__linux__) + +gchar * +teco_file_get_program_path(void) +{ + gchar buf[PATH_MAX]; + ssize_t len = readlink("/proc/self/exe", buf, sizeof(buf)-1); + if (G_UNLIKELY(len < 0)) + /* almost certainly wrong */ + return g_get_current_dir(); + buf[len] = '\0'; + + return g_path_get_dirname(buf); +} + +#elif defined(G_OS_UNIX) + +/* + * At least works on FreeBSD, even though it also has + * sysctl(KERN_PROC_PATHNAME). + * We assume it works on all other UNIXes as well. + */ +gchar * +teco_file_get_program_path(void) +{ + Dl_info info; + return dladdr(teco_file_get_program_path, &info) + ? g_path_get_dirname(info.dli_fname) : g_get_current_dir(); +} + +#else /* !G_OS_WIN32 && !__linux__ && !G_OS_UNIX */ + +/* + * This is almost guaranteed to be wrong, + * meaning that SciTECO cannot be made relocatable on these platforms. + * It may be worth evaluating argv[0] on these platforms. + */ +gchar * +teco_file_get_program_path(void) +{ + return g_get_current_dir(); +} + +#endif + +/** + * Get the datadir. + * + * By default it is hardcoded to an absolute path at + * build time. + * However, you can also build relocateable binaries + * where the datadir is relative to the program's executable. + * + * @note Beginning with glib v2.58, we could directly use + * g_canonicalize_filename(). + */ +gchar * +teco_file_get_datadir(void) +{ + if (g_path_is_absolute(SCITECODATADIR)) + return g_strdup(SCITECODATADIR); + + /* relocateable binary - datadir is relative to binary */ + g_autofree gchar *program_path = teco_file_get_program_path(); + g_autofree gchar *datadir = g_build_filename(program_path, SCITECODATADIR, NULL); + return teco_file_get_absolute_path(datadir); +} + /** * Perform tilde expansion on a file name or path. * diff --git a/src/file-utils.h b/src/file-utils.h index 4ee59e6..e974e2f 100644 --- a/src/file-utils.h +++ b/src/file-utils.h @@ -68,6 +68,18 @@ teco_file_normalize_path(gchar *path) gboolean teco_file_is_visible(const gchar *path); +/** + * Get absolute path of the program executable. + * + * This may return the current working directory on + * unsupported platforms. + * + * @return Newly-allocated path. + */ +gchar *teco_file_get_program_path(void); + +gchar *teco_file_get_datadir(void); + gchar *teco_file_expand_path(const gchar *path); /** diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c index 3121b05..829310a 100644 --- a/src/interface-gtk/interface.c +++ b/src/interface-gtk/interface.c @@ -51,6 +51,7 @@ #include "sciteco.h" #include "error.h" #include "string-utils.h" +#include "file-utils.h" #include "cmdline.h" #include "qreg.h" #include "ring.h" @@ -1124,6 +1125,8 @@ teco_interface_event_loop(GError **error) } g_assert(scitecoconfig.data != NULL); + g_autofree gchar *datadir = teco_file_get_datadir(); + /* * Initialize the CSS variable provider and the CSS provider * for the included fallback.css. @@ -1138,14 +1141,7 @@ teco_interface_event_loop(GError **error) if (!g_file_test(user_css_file, G_FILE_TEST_IS_REGULAR)) { /* use fallback CSS */ g_free(user_css_file); - /* - * FIXME: See above for icons. - */ -#ifdef G_OS_WIN32 - user_css_file = g_build_filename(scitecoconfig.data, "fallback.css", NULL); -#else - user_css_file = g_build_filename(SCITECODATADIR, "fallback.css", NULL); -#endif + user_css_file = g_build_filename(datadir, "fallback.css", NULL); } GtkCssProvider *user_css_provider = gtk_css_provider_new(); @@ -1170,15 +1166,10 @@ teco_interface_event_loop(GError **error) /* * FIXME: This is necessary so that the icon themes are found in the same * directory as sciteco.exe. - * This fails of course when $SCITECOCONFIG is changed. - * We should perhaps always use the absolute path of sciteco.exe. - * If you want to install SciTECO differently, you can still set - * $XDG_DATA_DIRS. - * - * FIXME FIXME FIXME: This is also currently broken. */ - //g_autofree char *theme_path = g_build_filename(scitecoconfig.data, "icons"); - //gtk_icon_theme_prepend_search_path(gtk_icon_theme_get_default(), theme_path); + g_autofree gchar *program_path = teco_file_get_program_path(); + g_autofree gchar *theme_path = g_build_filename(program_path, "icons", NULL); + gtk_icon_theme_prepend_search_path(gtk_icon_theme_get_default(), theme_path); #else /* * Load icons for the GTK window. @@ -1186,17 +1177,16 @@ teco_interface_event_loop(GError **error) * as a resource will be used by default. */ static const gchar *icon_files[] = { - SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-48.png", - SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-32.png", - SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-16.png" + "sciteco-48.png", "sciteco-32.png", "sciteco-16.png" }; GList *icon_list = NULL; for (gint i = 0; i < G_N_ELEMENTS(icon_files); i++) { - GdkPixbuf *icon_pixbuf = gdk_pixbuf_new_from_file(icon_files[i], NULL); + g_autofree gchar *icon_file = g_build_filename(datadir, icon_files[i], NULL); + GdkPixbuf *icon_pixbuf = gdk_pixbuf_new_from_file(icon_file, NULL); /* fail silently if there's a problem with one of the icons */ - if (icon_pixbuf) + if (G_LIKELY(icon_pixbuf != NULL)) icon_list = g_list_append(icon_list, icon_pixbuf); } diff --git a/src/main.c b/src/main.c index 45149e6..e2c6b9e 100644 --- a/src/main.c +++ b/src/main.c @@ -77,15 +77,15 @@ volatile sig_atomic_t teco_interrupted = FALSE; * program's directory. */ static inline gchar * -teco_get_default_config_path(const gchar *program) +teco_get_default_config_path(void) { - return g_path_get_dirname(program); + return teco_file_get_program_path(); } #elif defined(G_OS_UNIX) && !defined(__HAIKU__) static inline gchar * -teco_get_default_config_path(const gchar *program) +teco_get_default_config_path(void) { return g_strdup(g_get_home_dir()); } @@ -99,7 +99,7 @@ teco_get_default_config_path(const gchar *program) * with config files. */ static inline gchar * -teco_get_default_config_path(const gchar *program) +teco_get_default_config_path(void) { return g_strdup(g_get_user_config_dir()); } @@ -210,7 +210,7 @@ teco_process_options(gchar ***argv) } static void -teco_initialize_environment(const gchar *program) +teco_initialize_environment(void) { g_autoptr(GError) error = NULL; gchar *abs_path; @@ -252,14 +252,11 @@ teco_initialize_environment(const gchar *program) /* * Initialize $SCITECOCONFIG and $SCITECOPATH */ - g_autofree gchar *default_configpath = teco_get_default_config_path(program); + g_autofree gchar *default_configpath = teco_get_default_config_path(); g_setenv("SCITECOCONFIG", default_configpath, FALSE); -#ifdef G_OS_WIN32 - g_autofree gchar *default_scitecopath = g_build_filename(default_configpath, "lib", NULL); - g_setenv("SCITECOPATH", default_scitecopath, FALSE); -#else - g_setenv("SCITECOPATH", SCITECOLIBDIR, FALSE); -#endif + g_autofree gchar *datadir = teco_file_get_datadir(); + g_autofree gchar *default_libdir = g_build_filename(datadir, "lib", NULL); + g_setenv("SCITECOPATH", default_libdir, FALSE); /* * $SCITECOCONFIG and $SCITECOPATH may still be relative. @@ -389,7 +386,7 @@ main(int argc, char **argv) /* current working directory ("$") */ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_workingdir_new()); /* environment defaults and registers */ - teco_initialize_environment(argv_utf8[0]); + teco_initialize_environment(); teco_qreg_table_t local_qregs; teco_qreg_table_init(&local_qregs, TRUE); -- cgit v1.2.3