Rework directory determination in CMake builds.

Sets paths for finding componenents depending on the state of ENABLE_BINRELOC,
GNC_UNINSTALLED, GNC_BUILDDIR and whether any install paths have been set
outside of CMAKE_INSTALL_PREFIX.

GNUInstallDirs changes the name of CMAKE_INSTALL_LIBDIR depending on the
operating system and distro. When CMAKE_INSTALL_PREFIX is /usr,
/usr/local, or any subdirectory of /opt it also changes
CMAKE_INSTALL_FULL_SYSCONFDIR to /etc. An earlier commit by Aaron Laws
mirrors the name of CMAKE_INSTALL_LIBDIR to the build library directory.

It's possible for builders to set any of the install directories
anywhere they please.

Setting any directory outside of CMAKE_INSTALL_PREFIX breaks Binreloc so
the toplevel CMakeLists.txt now detects that and disables Binreloc.

If Binreloc is enabled then all path queries use it to find paths. This
works in the build directory because the gnucash executable and all of
the test programs are in build_directory/bin and LIBDIR, DATADIR, and
SYSCONFDIR can be found in the same root path.

If Binreloc is disabled then in order to build or run programs from the
build directory one must set GNC_UNINSTALLED and set GNC_BUILDDIR to the
absolute path of the build directory. When those are set GNC_BUILDDIR
replaces CMAKE_INSTALL_PREFIX in all paths that are subdirectories of
CMAKE_INSTALL_PREFIX; paths that are not in CMAKE_INSTALL_PREFIX are
appended whole to GNC_BUILDDIR. This process is constent between CMake
and gnc_path_get_foo. GnuCash is unlikely to run from a DESTDIR without
Binreloc.
This commit is contained in:
John Ralls 2017-12-05 14:48:45 -08:00
parent 84929c8e5b
commit 66817bb997
10 changed files with 254 additions and 109 deletions

View File

@ -73,6 +73,23 @@ OPTION (AUTOTOOLS_IN_DIST "Add autotools support to distribution tarballs." ON)
# These are also settable from the command line in a similar way.
SET(GNUCASH_BUILD_ID "" CACHE STRING "Overrides the GnuCash build identification (Build ID) which defaults to a description of the vcs commit from which gnucash is built. Distributions may want to insert a package management based version number instead")
# Check that all of the absolute install paths are inside
# ${CMAKE_INSTALL_PREFIX}. If they're not, disable binreloc as it
# won't be able to find paths that aren't relative to the location of
# the executable.
foreach(install_dir ${CMAKE_INSTALL_FULL_BINDIR}
${CMAKE_INSTALL_FULL_SYSCONFDIR} ${CMAKE_INSTALL_FULL_DATAROOTDIR}
${CMAKE_INSTALL_FULL_DATADIR} ${CMAKE_INSTALL_FULL_LIBDIR})
string(FIND ${install_dir} ${CMAKE_INSTALL_PREFIX} in_prefix)
if(in_prefix EQUAL -1)
set(ENABLE_BINRELOC OFF)
message(WARNING "${install_dir} is set outside of the intallation prefix ${CMAKE_INSTALL_PREFIX}. That will break relocation so ENABLE_BINRELOC is set to off. With relocation disabled GnuCash will run only in its configured install location. You must set GNC_UNINSTALLED=1 and GNC_BUILDDIR=/path/to/builddir to run from the build directory. GnuCash will not run from a DESTDIR.")
break()
endif()
endforeach()
# GnuCash installs two files in ${CMAKE_INSTALL_SYSCONFDIR}
SET(BINDIR ${CMAKE_INSTALL_BINDIR} CACHE STRING "user executables")
SET(SYSCONFDIR ${CMAKE_INSTALL_SYSCONFDIR} CACHE STRING "read-only single-machine data")
SET(DATAROOTDIR ${CMAKE_INSTALL_DATAROOTDIR} CACHE STRING "read-only arch.-independent data root")
@ -82,7 +99,7 @@ SET(LOCALEDIR ${DATAROOTDIR}/locale CACHE STRING "locale-dependent data")
SET(GNC_HELPDIR ${DATADIR} CACHE STRING "where to store help files")
SET(DATADIRNAME share)
SET(GNC_SYSTEM_XDG_DATA_DIRS /usr/local/share /usr/share)
SET(GNC_DBD_DIR ${CMAKE_PREFIX_PATH}/lib/dbd CACHE PATH "specify location of libdbi drivers")
SET(GNC_DBD_DIR ${CMAKE_PREFIX_LIBDIR}/dbd CACHE PATH "specify location of libdbi drivers")
SET(PKGLIBDIR ${CMAKE_INSTALL_LIBDIR}/gnucash)
SET(TEST_MYSQL_URL "" CACHE STRING "MySQL database URL for testing")
SET(TEST_PGSQL_URL "" CACHE STRING "PgSQL database URL for testing")

View File

@ -43,10 +43,19 @@ ADD_CUSTOM_TARGET(swig-runtime-h DEPENDS ${SWIG_RUNTIME_H})
SET_SOURCE_FILES_PROPERTIES (gnc-guile-utils.c PROPERTIES OBJECT_DEPENDS ${SWIG_RUNTIME_H})
SET(prefix ${CMAKE_INSTALL_PREFIX})
SET(datadir ${DATADIR})
SET(bindir ${BINDIR})
SET(libdir ${LIBDIR})
SET(sysconfdir ${SYSCONFDIR})
if(ENABLE_BINRELOC)
SET(datadir ${DATADIR})
SET(bindir ${BINDIR})
SET(libdir ${LIBDIR})
SET(sysconfdir ${SYSCONFDIR})
SET(localedir ${LOCALEDIR})
else()
SET(datadir ${CMAKE_INSTALL_FULL_DATADIR})
SET(bindir ${CMAKE_INSTALL_FULL_BINDIR})
SET(libdir ${CMAKE_INSTALL_FULL_LIBDIR})
SET(sysconfdir ${CMAKE_INSTALL_FULL_SYSCONFDIR})
SET(localedir "${CMAKE_INSTALL_FULL_DATAROOTDIR}/locale")
endif()
GNC_CONFIGURE(gncla-dir.h.in gncla-dir.h)
### Create gnc-version.h ###

View File

@ -33,7 +33,6 @@
#ifndef __BINRELOC_C__
#define __BINRELOC_C__
#include <config.h>
#include "gncla-dir.h"
#include <platform.h>
#if PLATFORM(WINDOWS)
@ -53,7 +52,9 @@
#include <limits.h>
#include <string.h>
#include "binreloc.h"
#include "gnc-filepath-utils.h"
#include <glib.h>
#include "gncla-dir.h"
G_BEGIN_DECLS
@ -84,7 +85,7 @@ _br_find_exe (Gnc_GbrInitError *error)
the current process */
prefix = g_win32_get_package_installation_directory_of_module (NULL);
result = g_build_filename (prefix,
"bin", "gnucash.exe",
BINDIR, "gnucash.exe",
(char*)NULL);
g_free (prefix);
return result;
@ -385,13 +386,14 @@ gnc_gbr_find_exe_dir (const gchar *default_dir)
* will be returned. If default_prefix is NULL, then NULL will be
* returned.
*/
gchar *
gnc_gbr_find_prefix (const gchar *default_prefix)
static inline gchar *
get_mac_bundle_prefix()
{
#if defined ENABLE_BINRELOC && defined MAC_INTEGRATION
gchar *id = gtkosx_application_get_bundle_id ();
gchar *path = gtkosx_application_get_resource_path ();
if (id == NULL)
if (id == NULL)
{
gchar *dirname = g_path_get_dirname (path);
g_free (path);
@ -400,9 +402,24 @@ gnc_gbr_find_prefix (const gchar *default_prefix)
}
g_free (id);
return path;
#else
gchar *dir1, *dir2;
#endif
return NULL;
}
static inline gchar*
get_builddir_prefix()
{
if (g_getenv ("GNC_UNINSTALLED"))
return g_strdup (g_getenv ("GNC_BUILDDIR"));
return NULL;
}
gchar *
gnc_gbr_find_prefix (const gchar *default_prefix)
{
gchar *dir1, *dir2;
if ((dir2 = get_builddir_prefix()) || (dir2 = get_mac_bundle_prefix()))
return dir2;
if (exe == NULL)
{
/* BinReloc not initialized. */
@ -415,7 +432,6 @@ gnc_gbr_find_prefix (const gchar *default_prefix)
dir2 = g_path_get_dirname (dir1);
g_free (dir1);
return dir2;
#endif //ENABLE_BINRELOC && defined MAC_INTEGRATION
}
@ -435,7 +451,7 @@ gnc_gbr_find_prefix (const gchar *default_prefix)
gchar *
gnc_gbr_find_bin_dir (const gchar *default_bin_dir)
{
gchar *prefix, *dir;
gchar *prefix, *dir, *bindir;
prefix = gnc_gbr_find_prefix (NULL);
if (prefix == NULL)
{
@ -445,47 +461,13 @@ gnc_gbr_find_bin_dir (const gchar *default_bin_dir)
else
return NULL;
}
dir = g_build_filename (prefix, "bin", NULL);
bindir = gnc_file_path_relative_part(PREFIX, BINDIR);
dir = g_build_filename (prefix, bindir, NULL);
g_free (bindir);
g_free (prefix);
return dir;
}
/** Locate the application's superuser binary folder.
*
* The path is generated by the following pseudo-code evaluation:
* \code
* prefix + "/sbin"
* \endcode
*
* @param default_sbin_dir A default path which will used as fallback.
* @return A string containing the sbin folder's path, which must be freed when
* no longer necessary. If BinReloc is not initialized, or if the
* initialization function failed, then a copy of default_sbin_dir will
* be returned. If default_bin_dir is NULL, then NULL will be returned.
*/
gchar *
gnc_gbr_find_sbin_dir (const gchar *default_sbin_dir)
{
gchar *prefix, *dir;
prefix = gnc_gbr_find_prefix (NULL);
if (prefix == NULL)
{
/* BinReloc not initialized. */
if (default_sbin_dir != NULL)
return g_strdup (default_sbin_dir);
else
return NULL;
}
dir = g_build_filename (prefix, "sbin", NULL);
g_free (prefix);
return dir;
}
/** Locate the application's data folder.
*
* The path is generated by the following pseudo-code evaluation:
@ -503,7 +485,7 @@ gnc_gbr_find_sbin_dir (const gchar *default_sbin_dir)
gchar *
gnc_gbr_find_data_dir (const gchar *default_data_dir)
{
gchar *prefix, *dir;
gchar *prefix, *dir, *datadir;
prefix = gnc_gbr_find_prefix (NULL);
if (prefix == NULL)
@ -515,12 +497,13 @@ gnc_gbr_find_data_dir (const gchar *default_data_dir)
return NULL;
}
dir = g_build_filename (prefix, "share", NULL);
datadir = gnc_file_path_relative_part(PREFIX, DATADIR);
dir = g_build_filename (prefix, datadir, NULL);
g_free (datadir);
g_free (prefix);
return dir;
}
/** Locate the application's library folder.
*
* The path is generated by the following pseudo-code evaluation:
@ -537,7 +520,7 @@ gnc_gbr_find_data_dir (const gchar *default_data_dir)
gchar *
gnc_gbr_find_lib_dir (const gchar *default_lib_dir)
{
gchar *prefix, *dir;
gchar *prefix, *dir, *libdir;
prefix = gnc_gbr_find_prefix (NULL);
if (prefix == NULL)
@ -549,46 +532,13 @@ gnc_gbr_find_lib_dir (const gchar *default_lib_dir)
return NULL;
}
dir = g_build_filename (prefix, LIBDIR, NULL);
libdir = gnc_file_path_relative_part(PREFIX, LIBDIR);
dir = g_build_filename (prefix, libdir, NULL);
g_free (libdir);
g_free (prefix);
return dir;
}
/** Locate the application's libexec folder.
*
* The path is generated by the following pseudo-code evaluation:
* \code
* prefix + "/libexec"
* \endcode
*
* @param default_libexec_dir A default path which will used as fallback.
* @return A string containing the libexec folder's path, which must be freed when
* no longer necessary. If BinReloc is not initialized, or if the initialization
* function failed, then a copy of default_libexec_dir will be returned.
* If default_libexec_dir is NULL, then NULL will be returned.
*/
gchar *
gnc_gbr_find_libexec_dir (const gchar *default_libexec_dir)
{
gchar *prefix, *dir;
prefix = gnc_gbr_find_prefix (NULL);
if (prefix == NULL)
{
/* BinReloc not initialized. */
if (default_libexec_dir != NULL)
return g_strdup (default_libexec_dir);
else
return NULL;
}
dir = g_build_filename (prefix, "libexec", NULL);
g_free (prefix);
return dir;
}
/** Locate the application's configuration files folder.
*
* The path is generated by the following pseudo-code evaluation:
@ -605,7 +555,7 @@ gnc_gbr_find_libexec_dir (const gchar *default_libexec_dir)
gchar *
gnc_gbr_find_etc_dir (const gchar *default_etc_dir)
{
gchar *prefix, *dir;
gchar *prefix, *dir, *sysconfdir;
prefix = gnc_gbr_find_prefix (NULL);
if (prefix == NULL)
@ -617,7 +567,9 @@ gnc_gbr_find_etc_dir (const gchar *default_etc_dir)
return NULL;
}
dir = g_build_filename (prefix, "etc", NULL);
sysconfdir = gnc_file_path_relative_part(PREFIX, SYSCONFDIR);
dir = g_build_filename (prefix, sysconfdir, NULL);
g_free (sysconfdir);
g_free (prefix);
return dir;
}

View File

@ -193,6 +193,14 @@ gnc_resolve_file_path (const gchar * filefrag)
}
gchar *gnc_file_path_relative_part (const gchar *prefix, const gchar *path)
{
std::string p{path};
if (p.find(prefix) == 0)
return g_strdup(p.substr(strlen(prefix)).c_str());
return g_strdup(path);
}
/* Searches for a file fragment paths set via GNC_DOC_PATH environment
* variable. If this variable is not set, fall back to search in
* - a html directory in the local user's gnucash settings directory

View File

@ -41,6 +41,14 @@
*/
gchar *gnc_resolve_file_path (const gchar *filefrag);
/** Given a prefix and a path return the relative portion of the path.
* @param prefix The prefix that might be the first part of the path
* @param path The path from which the prefix might be removed
* @return a char* that must be g_freed containing the path without
* the prefix if path begins with prefix, otherwise a copy of path.
*/
gchar *gnc_file_path_relative_part (const gchar *prefix, const gchar *path);
/** @brief Find an absolute path to a localized version of a given
* relative path to a html or html related file.
* If no localized version exists, an absolute path to the file

View File

@ -138,11 +138,16 @@ gchar *gnc_path_get_gtkbuilderdir()
* @returns A newly allocated string. */
gchar *gnc_path_get_localedir()
{
gchar *prefix = gnc_path_get_prefix();
gchar *result = g_build_filename (prefix, LOCALE_DATADIRNAME, "locale", (char*)NULL);
g_free (prefix);
//printf("Returning localedir %s\n", result);
return result;
if (g_path_is_absolute (LOCALEDIR))
return g_strdup(LOCALEDIR);
else
{
gchar *prefix = gnc_path_get_prefix();
gchar *result = g_build_filename (prefix, LOCALEDIR, (char*)NULL);
g_free (prefix);
//printf("Returning localedir %s\n", result);
return result;
}
}
/** Returns the accounts file path, usually

View File

@ -30,4 +30,4 @@
#define BINDIR "@-bindir-@"
#define LIBDIR "@-libdir-@"
#define LOCALE_DATADIRNAME "@-DATADIRNAME-@"
#define LOCALEDIR "@-localedir-@"

View File

@ -1,8 +1,8 @@
set(MODULEPATH ${CMAKE_SOURCE_DIR}/libgnucash/core-utils)
SET(CORE_UTILS_TEST_INCLUDE_DIRS
${CMAKE_BINARY_DIR}/common # for config.h
${CMAKE_SOURCE_DIR}/libgnucash/core-utils
${MODULEPATH}
${CMAKE_SOURCE_DIR}/common/test-core
${GLIB2_INCLUDE_DIRS}
${GTK_MAC_INCLUDE_DIRS}
@ -27,5 +27,31 @@ IF (MAC_INTEGRATION)
TARGET_COMPILE_DEFINITIONS(test-userdata-dir PRIVATE ${GTK_MAC_CFLAGS_OTHER})
ENDIF(MAC_INTEGRATION)
SET_DIST_LIST(test_core_utils_DIST CMakeLists.txt Makefile.am test-gnc-glib-utils.c
test-resolve-file-path.c test-userdata-dir.c test-userdata-dir-invalid-home.c)
set(gtest_core_utils_INCLUDES
${MODULEPATH}
${CMAKE_BINARY_DIR}/common # for config.h
${CMAKE_SOURCE_DIR}/common # for platform.h
${CMAKE_BINARY_DIR}/libgnucash/core-utils # for gncla-dir.h
${GLIB2_INCLUDE_DIRS}
${GTEST_INCLUDE_DIR}
)
SET(gtest_core_utils_LIBS
${GLIB2_LDFLAGS}
${Boost_LIBRARIES}
${GTHREAD_LDFLAGS}
${GTEST_LIB})
set(test_gnc_path_util_SOURCES
${MODULEPATH}/gnc-path.c
${MODULEPATH}/binreloc.c
${MODULEPATH}/gnc-filepath-utils.cpp
gtest-path-utilities.cpp
${GTEST_SRC})
gnc_add_test(test-gnc-path-util "${test_gnc_path_util_SOURCES}"
gtest_core_utils_INCLUDES gtest_core_utils_LIBS "GNC_UNINSTALLED=yes")
SET_DIST_LIST(test_core_utils_DIST CMakeLists.txt Makefile.am
test-gnc-glib-utils.c test-resolve-file-path.c test-userdata-dir.c
test-userdata-dir-invalid-home.c gtest-path-utilities.cpp)

View File

@ -0,0 +1,125 @@
extern "C"
{
#include <config.h>
#include <glib.h>
#include <gncla-dir.h>
#include <gnc-path.h>
#include <binreloc.h>
#include <gnc-filepath-utils.h>
}
#include <gtest/gtest.h>
struct PathTest : public testing::Test
{
PathTest() : m_prefix{nullptr} {}
void SetUp()
{
#ifdef ENABLE_BINRELOC
gnc_gbr_init(nullptr);
#endif
char *builddir = g_strdup(g_getenv("GNC_BUILDDIR"));
if (builddir)
m_prefix = builddir;
else
m_prefix = g_get_current_dir();
}
void TearDown()
{
if (m_prefix)
g_free(m_prefix);
}
char *m_prefix;
};
TEST_F(PathTest, gnc_path_get_prefix)
{
#ifdef ENABLE_BINRELOC
EXPECT_STREQ(gnc_path_get_prefix(), m_prefix);
#else
g_setenv("GNC_UNINSTALLED", "1", TRUE);
g_setenv("GNC_BUILDDIR", m_prefix, 1);
EXPECT_STREQ(gnc_path_get_prefix(), m_prefix);
g_unsetenv("GNC_UNINSTALLED");
g_unsetenv("GNC_BUILDDIR");
EXPECT_STREQ(gnc_path_get_prefix(), PREFIX);
#endif
}
TEST_F(PathTest, gnc_path_get_bindir)
{
gchar *dirname = gnc_file_path_relative_part(PREFIX, BINDIR);
gchar *binpath = g_build_filename(m_prefix, dirname, NULL);
g_free(dirname);
#ifdef ENABLE_BINRELOC
EXPECT_STREQ(gnc_path_get_bindir(), binpath);
g_free(binpath);
#else
g_setenv("GNC_UNINSTALLED", "1", TRUE);
g_setenv("GNC_BUILDDIR", m_prefix, 1);
EXPECT_STREQ(gnc_path_get_bindir(), binpath);
g_free(binpath);
g_unsetenv("GNC_UNINSTALLED");
g_unsetenv("GNC_BUILDDIR");
EXPECT_STREQ(gnc_path_get_bindir(), BINDIR);
#endif
}
TEST_F(PathTest, gnc_path_get_libdir)
{
gchar *dirname = gnc_file_path_relative_part(PREFIX, LIBDIR);
gchar *libpath = g_build_filename(m_prefix, dirname, NULL);
g_free(dirname);
#ifdef ENABLE_BINRELOC
EXPECT_STREQ(gnc_path_get_libdir(), libpath);
g_free(libpath);
#else
g_setenv("GNC_UNINSTALLED", "1", TRUE);
g_setenv("GNC_BUILDDIR", m_prefix, 1);
EXPECT_STREQ(gnc_path_get_libdir(), libpath);
g_free(libpath);
g_unsetenv("GNC_UNINSTALLED");
g_unsetenv("GNC_BUILDDIR");
EXPECT_STREQ(gnc_path_get_libdir(), LIBDIR);
#endif
}
TEST_F(PathTest, gnc_path_get_datadir)
{
gchar *dirname = gnc_file_path_relative_part(PREFIX, DATADIR);
gchar *datapath = g_build_filename(m_prefix, dirname, NULL);
g_free(dirname);
#ifdef ENABLE_BINRELOC
EXPECT_STREQ(gnc_path_get_datadir(), datapath);
g_free(datapath);
#else
g_setenv("GNC_UNINSTALLED", "1", TRUE);
g_setenv("GNC_BUILDDIR", m_prefix, 1);
EXPECT_STREQ(gnc_path_get_datadir(), datapath);
g_free(datapath);
g_unsetenv("GNC_UNINSTALLED");
g_unsetenv("GNC_BUILDDIR");
EXPECT_STREQ(gnc_path_get_datadir(), DATADIR);
#endif
}
TEST_F(PathTest, gnc_path_get_sysconfdir)
{
gchar *dirname = gnc_file_path_relative_part(PREFIX, SYSCONFDIR);
gchar *sysconfpath = g_build_filename(m_prefix, dirname, "gnucash", NULL);
g_free(dirname);
#ifdef ENABLE_BINRELOC
EXPECT_STREQ(gnc_path_get_pkgsysconfdir(), sysconfpath);
g_free(sysconfpath);
#else
g_setenv("GNC_UNINSTALLED", "1", TRUE);
g_setenv("GNC_BUILDDIR", m_prefix, 1);
EXPECT_STREQ(gnc_path_get_pkgsysconfdir(), sysconfpath);
g_free(sysconfpath);
g_unsetenv("GNC_UNINSTALLED");
g_unsetenv("GNC_BUILDDIR");
sysconfpath = g_build_filename(SYSCONFDIR, "gnucash", NULL);
EXPECT_STREQ(gnc_path_get_pkgsysconfdir(), sysconfpath);
g_free(sysconfpath);
#endif
}

View File

@ -105,12 +105,7 @@ static char* get_default_module_dir(const char* rel_path)
if (uninstalled)
{
#ifdef CMAKE_BUILD
#ifdef WIN32
#define MODULE_LIBDIR "bin"
#else
#define MODULE_LIBDIR LIBDIR "/gnucash"
#endif
pkglibdir = g_build_path (G_DIR_SEPARATOR_S, builddir, MODULE_LIBDIR, NULL);
pkglibdir = gnc_path_get_pkglibdir ();
#else
if (rel_path)
pkglibdir = g_build_path (G_DIR_SEPARATOR_S, builddir,