From 4ee3cbf382d0694dd06b9cb53bfcfd2752545ddf Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Wed, 14 Mar 2018 20:52:22 -0700 Subject: [PATCH 1/8] LINKER_USE_RPATH=ORIGIN Use Linux specific linker trick to allow relocation of built tree. relative rpath use $ORIGIN to reference libraries in other modules by relative path. fix rel. RPATH --- configure/CONFIG_BASE | 2 ++ configure/CONFIG_COMMON | 2 ++ configure/CONFIG_SITE | 9 ++++++- configure/RULES_BUILD | 7 ++++++ configure/os/CONFIG.Common.linuxCommon | 2 ++ src/tools/Makefile | 2 ++ src/tools/makeRPath.py | 33 ++++++++++++++++++++++++++ 7 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 src/tools/makeRPath.py diff --git a/configure/CONFIG_BASE b/configure/CONFIG_BASE index 137ce0098..32349ebd5 100644 --- a/configure/CONFIG_BASE +++ b/configure/CONFIG_BASE @@ -44,6 +44,8 @@ FULLPATHNAME = $(PERL) $(TOOLS)/fullPathName.pl TAPTOJUNIT = $(PERL) $(TOOLS)/tap-to-junit-xml.pl GENVERSIONHEADER = $(PERL) $(TOOLS)/genVersionHeader.pl $(QUIET_FLAG) $(QUESTION_FLAG) +MAKERPATH = $(PYTHON) $(TOOLS)/makeRPath.py + #--------------------------------------------------------------- # tools for installing libraries and products INSTALL = $(PERL) $(TOOLS)/installEpics.pl $(QUIET_FLAG) diff --git a/configure/CONFIG_COMMON b/configure/CONFIG_COMMON index eef4d6745..d7766e50a 100644 --- a/configure/CONFIG_COMMON +++ b/configure/CONFIG_COMMON @@ -38,6 +38,8 @@ BUILD_ARCHS = $(EPICS_HOST_ARCH) $(CROSS1) $(CROSS2) # otherwise override this in os/CONFIG_SITE..Common PERL = perl -CSD +PYTHON = python + #------------------------------------------------------- # Check configure/RELEASE file for consistency CHECK_RELEASE_YES = checkRelease diff --git a/configure/CONFIG_SITE b/configure/CONFIG_SITE index b657f5b65..49f82c7a8 100644 --- a/configure/CONFIG_SITE +++ b/configure/CONFIG_SITE @@ -169,10 +169,17 @@ EPICS_SITE_VERSION = GCC_PIPE = NO # Set RPATH when linking executables and libraries. -# Must be either YES or NO. If you set this to NO you must also provide a +# Must be either YES, NO, or ORIGIN. If you set this to NO you must also provide a # way for Base executables to find their shared libraries when they are # run at build-time, e.g. set the LD_LIBRARY_PATH environment variable. +# ORIGIN is Linux specific. LINKER_USE_RPATH = YES +# Only used when LINKER_USE_RPATH=ORIGIN +# The build time root of the relocatable tree. +# Linking to libraries under this root directory will be relative. +# Linking to libraries outside of this root will be absolute. +LINKER_ORIGIN_ROOT = $(INSTALL_LOCATION) + # Overrides for the settings above may appear in a CONFIG_SITE.local file -include $(CONFIG)/CONFIG_SITE.local diff --git a/configure/RULES_BUILD b/configure/RULES_BUILD index 21a838790..f2f4d8873 100644 --- a/configure/RULES_BUILD +++ b/configure/RULES_BUILD @@ -194,6 +194,13 @@ ifeq ($(EPICS_HOST_ARCH),$(T_A)) $(info Warning: RELEASE file consistency checks have been disabled) endif +# $(FINAL_DIR) signals eventual install locations to makeRPath script +$(TESTPRODNAME): FINAL_DIR=. +$(PRODNAME): FINAL_DIR=$(INSTALL_BIN) +$(TESTSHRLIBNAME): FINAL_DIR=. +$(SHRLIBNAME): FINAL_DIR=$(INSTALL_SHRLIB) +$(LOADABLE_SHRLIBNAME): FINAL_DIR=$(INSTALL_SHRLIB) + #--------------------------------------------------------------- # The order of the following rules is # VERY IMPORTANT !!!! diff --git a/configure/os/CONFIG.Common.linuxCommon b/configure/os/CONFIG.Common.linuxCommon index 965de09b8..e8e9ab3ca 100644 --- a/configure/os/CONFIG.Common.linuxCommon +++ b/configure/os/CONFIG.Common.linuxCommon @@ -25,11 +25,13 @@ STATIC_LDLIBS_YES= -Wl,-Bdynamic # Set runtime path for shared libraries if USE_RPATH=YES and STATIC_BUILD=NO SHRLIBDIR_RPATH_LDFLAGS_YES_NO = $(SHRLIB_DEPLIB_DIRS:%=-Wl,-rpath,%) +SHRLIBDIR_RPATH_LDFLAGS_ORIGIN_NO = $(shell $(MAKERPATH) -O '\$$ORIGIN' -F $(FINAL_DIR) -R $(LINKER_ORIGIN_ROOT) $(SHRLIB_DEPLIB_DIRS)) SHRLIBDIR_LDFLAGS += \ $(SHRLIBDIR_RPATH_LDFLAGS_$(LINKER_USE_RPATH)_$(STATIC_BUILD)) # Set runtime path for products if USE_RPATH=YES and STATIC_BUILD=NO PRODDIR_RPATH_LDFLAGS_YES_NO = $(PROD_DEPLIB_DIRS:%=-Wl,-rpath,%) +PRODDIR_RPATH_LDFLAGS_ORIGIN_NO = $(shell $(MAKERPATH) -O '\$$ORIGIN' -F $(FINAL_DIR) -R $(LINKER_ORIGIN_ROOT) $(PROD_DEPLIB_DIRS)) PRODDIR_LDFLAGS += \ $(PRODDIR_RPATH_LDFLAGS_$(LINKER_USE_RPATH)_$(STATIC_BUILD)) diff --git a/src/tools/Makefile b/src/tools/Makefile index 39497a417..8c4f25adc 100644 --- a/src/tools/Makefile +++ b/src/tools/Makefile @@ -39,6 +39,8 @@ PERL_SCRIPTS += tap-to-junit-xml.pl PERL_SCRIPTS += useManifestTool.pl PERL_SCRIPTS += genVersionHeader.pl +PERL_SCRIPTS += makeRPath.py + HTMLS = style.css HTMLS += EPICS/Getopts.html HTMLS += EPICS/Path.html diff --git a/src/tools/makeRPath.py b/src/tools/makeRPath.py new file mode 100644 index 000000000..edad80085 --- /dev/null +++ b/src/tools/makeRPath.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +from __future__ import print_function + +import sys +import os + +from argparse import ArgumentParser + +if os.environ.get('EPICS_DEBUG_RPATH','')=='YES': + sys.stderr.write('%s'%sys.argv) + +P = ArgumentParser() +P.add_argument('-F','--final',default=os.getcwd(), help='Final install location for ELF file') +P.add_argument('-R','--root',default='/') +P.add_argument('-O', '--origin', default='$ORIGIN') +P.add_argument('path', nargs='*') +args = P.parse_args() + +fdir = os.path.abspath(args.final) + +output = [] +for path in args.path: + path = os.path.abspath(path) + + if args.root and os.path.relpath(path, args.root).startswith('../'): + pass # absolute rpath + else: + path = os.path.relpath(path, fdir) + + output.append('-Wl,-rpath,'+os.path.join(args.origin, path)) + +print(' '.join(output)) From 32340584b4fee7e3080e8d19332d959ef6de0e0a Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Wed, 25 Apr 2018 22:57:03 -0700 Subject: [PATCH 2/8] epicsGetExecDir() paths relative to executable For linux, enable softIoc to find .dbd relative to the executable location. The same could be done for other targets *bsd may have symlink /proc/curproc/file fallback to sysctl() with KERN_PROC_PATHNAME solaris getexecname() mac _NSGetExecutablePath() WIN32 GetModuleFileName(NULL) others out of luck... --- modules/database/src/std/softIoc/softMain.cpp | 41 ++++++++++++++- modules/libcom/src/misc/unixFileName.h | 22 ++++++++ modules/libcom/src/osi/Makefile | 1 + modules/libcom/src/osi/os/Linux/osdgetexec.c | 50 +++++++++++++++++++ modules/libcom/src/osi/os/WIN32/osiFileName.h | 22 ++++++++ .../libcom/src/osi/os/cygwin32/osiFileName.h | 22 ++++++++ .../libcom/src/osi/os/default/osdgetexec.c | 14 ++++++ 7 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 modules/libcom/src/osi/os/Linux/osdgetexec.c create mode 100644 modules/libcom/src/osi/os/default/osdgetexec.c diff --git a/modules/database/src/std/softIoc/softMain.cpp b/modules/database/src/std/softIoc/softMain.cpp index 8400a6554..01ef19b2f 100644 --- a/modules/database/src/std/softIoc/softMain.cpp +++ b/modules/database/src/std/softIoc/softMain.cpp @@ -62,23 +62,59 @@ #include "epicsThread.h" #include "epicsExit.h" #include "epicsStdio.h" +#include "epicsString.h" #include "dbStaticLib.h" #include "subRecord.h" #include "dbAccess.h" #include "asDbLib.h" #include "iocInit.h" #include "iocsh.h" +#include "osiFileName.h" #include "epicsInstallDir.h" extern "C" int softIoc_registerRecordDeviceDriver(struct dbBase *pdbbase); -#define DBD_FILE EPICS_BASE "/dbd/softIoc.dbd" -#define EXIT_FILE EPICS_BASE "/db/softIocExit.db" +#define DBD_BASE "dbd/softIoc.dbd" +#define EXIT_BASE "db/softIocExit.db" +#define DBD_FILE_REL "../../" DBD_BASE +#define EXIT_FILE_REL "../../" EXIT_BASE +#define DBD_FILE EPICS_BASE "/" DBD_BASE +#define EXIT_FILE EPICS_BASE "/" EXIT_BASE const char *arg0; const char *base_dbd = DBD_FILE; const char *exit_db = EXIT_FILE; +static void preparePath(void) +{ + FILE *fp; + char *prefix = epicsGetExecDir(); + char *dbd, *exit; + if(!prefix) return; + + dbd = (char*)malloc(strlen(prefix) + strlen(DBD_FILE_REL) + 1); + if(dbd) { + dbd[0] = '\0'; + strcat(dbd, prefix); + strcat(dbd, DBD_FILE_REL); + printf("Testing '%s'\n", dbd); + if((fp = fopen(dbd, "rb"))!=NULL) { + fclose(fp); + base_dbd = dbd; + } + } + + exit = (char*)malloc(strlen(prefix) + strlen(EXIT_FILE_REL) + 1); + if(exit) { + exit[0] = '\0'; + strcat(exit, prefix); + strcat(exit, EXIT_FILE_REL); + if((fp = fopen(exit, "rb"))!=NULL) { + fclose(fp); + exit_db = exit; + } + } +} static void exitSubroutine(subRecord *precord) { epicsExitLater((precord->a == 0.0) ? EXIT_SUCCESS : EXIT_FAILURE); @@ -96,6 +132,7 @@ static void usage(int status) { int main(int argc, char *argv[]) { + preparePath(); char *dbd_file = const_cast(base_dbd); char *macros = NULL; char xmacro[PVNAME_STRINGSZ + 4]; diff --git a/modules/libcom/src/misc/unixFileName.h b/modules/libcom/src/misc/unixFileName.h index 36e818c8f..9d7af252c 100644 --- a/modules/libcom/src/misc/unixFileName.h +++ b/modules/libcom/src/misc/unixFileName.h @@ -14,7 +14,29 @@ #ifndef unixFileNameH #define unixFileNameH +#include + +#ifdef __cplusplus +extern "C" { +#endif + #define OSI_PATH_LIST_SEPARATOR ":" #define OSI_PATH_SEPARATOR "/" +/** Return the absolute path of the current executable. + @returns NULL or the path. Caller must free() + */ +epicsShareFunc +char *epicsGetExecName(void); + +/** Return the absolute path of the directory containing the current executable. + @returns NULL or the path. Caller must free() + */ +epicsShareFunc +char *epicsGetExecDir(void); + +#ifdef __cplusplus +} +#endif + #endif /* unixFileNameH */ diff --git a/modules/libcom/src/osi/Makefile b/modules/libcom/src/osi/Makefile index ecbf4c23b..0352e9ffe 100644 --- a/modules/libcom/src/osi/Makefile +++ b/modules/libcom/src/osi/Makefile @@ -123,6 +123,7 @@ Com_SRCS += osdMonotonic.c Com_SRCS += osdProcess.c Com_SRCS += osdNetIntf.c Com_SRCS += osdMessageQueue.c +Com_SRCS += osdgetexec.c Com_SRCS += devLibVME.c Com_SRCS += devLibVMEOSD.c diff --git a/modules/libcom/src/osi/os/Linux/osdgetexec.c b/modules/libcom/src/osi/os/Linux/osdgetexec.c new file mode 100644 index 000000000..2ea8ca4ae --- /dev/null +++ b/modules/libcom/src/osi/os/Linux/osdgetexec.c @@ -0,0 +1,50 @@ + +#include +#include +#include +#include + +#define epicsExportSharedSymbols +#include + +char *epicsGetExecName(void) +{ + size_t max = PATH_MAX; + char *ret = NULL; + ssize_t n; + + while(1) { + char *temp = realloc(ret, max); + if(!temp) { + /* we treat alloc failure as terminal */ + free(ret); + ret = NULL; + break; + } + ret = temp; + + n = readlink("/proc/self/exe", ret, max); + if(n < max) { + /* readlink() never adds a nil */ + ret[n] = '\0'; + break; + } + + max += 64; + } + + return ret; +} + +char *epicsGetExecDir(void) +{ + char *ret = epicsGetExecName(); + if(ret) { + char *sep = strrchr(ret, '/'); + if(sep) { + /* nil the charactor after the / */ + sep[1] = '\0'; + } + } + return ret; +} diff --git a/modules/libcom/src/osi/os/WIN32/osiFileName.h b/modules/libcom/src/osi/os/WIN32/osiFileName.h index 6ff0308b2..ced745f71 100644 --- a/modules/libcom/src/osi/os/WIN32/osiFileName.h +++ b/modules/libcom/src/osi/os/WIN32/osiFileName.h @@ -15,7 +15,29 @@ #ifndef osiFileNameH #define osiFileNameH +#include + +#ifdef __cplusplus +extern "C" { +#endif + #define OSI_PATH_LIST_SEPARATOR ";" #define OSI_PATH_SEPARATOR "\\" +/** Return the absolute path of the current executable. + @returns NULL or the path. Caller must free() + */ +epicsShareFunc +char *epicsGetExecName(void); + +/** Return the absolute path of the directory containing the current executable. + @returns NULL or the path. Caller must free() + */ +epicsShareFunc +char *epicsGetExecDir(void); + +#ifdef __cplusplus +} +#endif + #endif /* osiFileNameH */ diff --git a/modules/libcom/src/osi/os/cygwin32/osiFileName.h b/modules/libcom/src/osi/os/cygwin32/osiFileName.h index 6d7fd6eb9..1e7799098 100644 --- a/modules/libcom/src/osi/os/cygwin32/osiFileName.h +++ b/modules/libcom/src/osi/os/cygwin32/osiFileName.h @@ -14,7 +14,29 @@ #ifndef osiFileNameH #define osiFileNameH +#include + +#ifdef __cplusplus +extern "C" { +#endif + #define OSI_PATH_LIST_SEPARATOR ";" #define OSI_PATH_SEPARATOR "\\" +/** Return the absolute path of the current executable. + @returns NULL or the path. Caller must free() + */ +epicsShareFunc +char *epicsGetExecName(void); + +/** Return the absolute path of the directory containing the current executable. + @returns NULL or the path. Caller must free() + */ +epicsShareFunc +char *epicsGetExecDir(void); + +#ifdef __cplusplus +} +#endif + #endif /* osiFileNameH */ diff --git a/modules/libcom/src/osi/os/default/osdgetexec.c b/modules/libcom/src/osi/os/default/osdgetexec.c new file mode 100644 index 000000000..0bec9ead9 --- /dev/null +++ b/modules/libcom/src/osi/os/default/osdgetexec.c @@ -0,0 +1,14 @@ +#include + +#define epicsExportSharedSymbols +#include + +char *epicsGetExecName(void) +{ + return NULL; +} + +char *epicsGetExecDir(void) +{ + return NULL; +} From 84831e13e7bfea253c5a545c1c9c9f059d4b02bb Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 26 Apr 2018 14:33:58 -0700 Subject: [PATCH 3/8] epicsGetExecName WIN32, Darwin, solaris, freebsd --- modules/libcom/src/osi/os/Darwin/osdgetexec.c | 50 ++++++++++++++ modules/libcom/src/osi/os/Linux/osdgetexec.c | 6 +- modules/libcom/src/osi/os/WIN32/osdgetexec.c | 52 ++++++++++++++ .../libcom/src/osi/os/freebsd/osdgetexec.c | 68 +++++++++++++++++++ .../libcom/src/osi/os/solaris/osdgetexec.c | 30 ++++++++ modules/libcom/test/Makefile | 5 ++ modules/libcom/test/testexecname.c | 24 +++++++ 7 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 modules/libcom/src/osi/os/Darwin/osdgetexec.c create mode 100644 modules/libcom/src/osi/os/WIN32/osdgetexec.c create mode 100644 modules/libcom/src/osi/os/freebsd/osdgetexec.c create mode 100644 modules/libcom/src/osi/os/solaris/osdgetexec.c create mode 100644 modules/libcom/test/testexecname.c diff --git a/modules/libcom/src/osi/os/Darwin/osdgetexec.c b/modules/libcom/src/osi/os/Darwin/osdgetexec.c new file mode 100644 index 000000000..4e4961c59 --- /dev/null +++ b/modules/libcom/src/osi/os/Darwin/osdgetexec.c @@ -0,0 +1,50 @@ + +#include +#include + +#include + +#define epicsExportSharedSymbols +#include + +char *epicsGetExecName(void) +{ + uint32_t max = 64u; + char *ret = NULL; + + while(1) { + char *temp = realloc(ret, max); + if(!temp) { + /* we treat alloc failure as terminal */ + free(ret); + ret = NULL; + break; + } + ret = temp; + + /* cf. "man 3 dyld" */ + if(_NSGetExecutablePath(ret, &max)==0) { + /* max left unchanged */ + ret[max-1] = '\0'; + break; + } + /* max has been updated with required size */ + } + + /* TODO: _NSGetExecutablePath() doesn't follow symlinks */ + + return ret; +} + +char *epicsGetExecDir(void) +{ + char *ret = epicsGetExecName(); + if(ret) { + char *sep = strrchr(ret, '/'); + if(sep) { + /* nil the charactor after the / */ + sep[1] = '\0'; + } + } + return ret; +} diff --git a/modules/libcom/src/osi/os/Linux/osdgetexec.c b/modules/libcom/src/osi/os/Linux/osdgetexec.c index 2ea8ca4ae..cba5d78a8 100644 --- a/modules/libcom/src/osi/os/Linux/osdgetexec.c +++ b/modules/libcom/src/osi/os/Linux/osdgetexec.c @@ -24,7 +24,11 @@ char *epicsGetExecName(void) ret = temp; n = readlink("/proc/self/exe", ret, max); - if(n < max) { + if(n == -1) { + free(ret); + ret = NULL; + break; + } else if(n < max) { /* readlink() never adds a nil */ ret[n] = '\0'; break; diff --git a/modules/libcom/src/osi/os/WIN32/osdgetexec.c b/modules/libcom/src/osi/os/WIN32/osdgetexec.c new file mode 100644 index 000000000..a46ce50cd --- /dev/null +++ b/modules/libcom/src/osi/os/WIN32/osdgetexec.c @@ -0,0 +1,52 @@ + +#include +#include +#include + +#define epicsExportSharedSymbols +#include + +char *epicsGetExecName(void) +{ + size_t max = 128; + char *ret = NULL; + DWORD n; + + while(1) { + char *temp = realloc(ret, max); + if(!temp) { + /* we treat alloc failure as terminal */ + free(ret); + ret = NULL; + break; + } + ret = temp; + + n = GetModuleFileName(NULL, ret, max); + if(n == 0) { + free(ret); + ret = NULL; + break; + } else if(n < max) { + ret[n] = '\0'; + break; + } + + max += 64; + } + + return ret; +} + +char *epicsGetExecDir(void) +{ + char *ret = epicsGetExecName(); + if(ret) { + char *sep = strrchr(ret, '\\'); + if(sep) { + /* nil the charactor after the / */ + sep[1] = '\0'; + } + } + return ret; +} diff --git a/modules/libcom/src/osi/os/freebsd/osdgetexec.c b/modules/libcom/src/osi/os/freebsd/osdgetexec.c new file mode 100644 index 000000000..39c0a163d --- /dev/null +++ b/modules/libcom/src/osi/os/freebsd/osdgetexec.c @@ -0,0 +1,68 @@ + +#include +#include +#include +#include + +#define epicsExportSharedSymbols +#include + +char *epicsGetExecName(void) +{ + size_t max = PATH_MAX; + char *ret = NULL; + ssize_t n; + + while(1) { + char *temp = realloc(ret, max); + if(!temp) { + /* we treat alloc failure as terminal */ + free(ret); + ret = NULL; + break; + } + ret = temp; + + n = readlink("/proc/curproc/file", ret, max); + if(n == -1) { + free(ret); + ret = NULL; + break; + } else if(n < max) { + /* readlink() never adds a nil */ + ret[n] = '\0'; + break; + } + + max += 64; + } + + if(!ret) { + int mib[4]; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = -1; + + ret = malloc(max); + if(ret) { + sysctl(mib, 4, ret, &cb, NULL, 0); + /* TODO: error check */ + } + } + + return ret; +} + +char *epicsGetExecDir(void) +{ + char *ret = epicsGetExecName(); + if(ret) { + char *sep = strrchr(ret, '/'); + if(sep) { + /* nil the charactor after the / */ + sep[1] = '\0'; + } + } + return ret; +} diff --git a/modules/libcom/src/osi/os/solaris/osdgetexec.c b/modules/libcom/src/osi/os/solaris/osdgetexec.c new file mode 100644 index 000000000..ff9739ca9 --- /dev/null +++ b/modules/libcom/src/osi/os/solaris/osdgetexec.c @@ -0,0 +1,30 @@ + +#include +#include + +#define epicsExportSharedSymbols +#include + +char *epicsGetExecName(void) +{ + const char *raw = getexecname(); + char *ret = NULL; + /* manpage says getexecname() might return a relative path. we treat this as an error */ + if(raw[0]=='/') { + ret = strdup(raw); + } + return ret; +} + +char *epicsGetExecDir(void) +{ + char *ret = epicsGetExecName(); + if(ret) { + char *sep = strrchr(ret, '/'); + if(sep) { + /* nil the charactor after the / */ + sep[1] = '\0'; + } + } + return ret; +} diff --git a/modules/libcom/test/Makefile b/modules/libcom/test/Makefile index 1c26c44b7..7e1ff1abc 100755 --- a/modules/libcom/test/Makefile +++ b/modules/libcom/test/Makefile @@ -232,6 +232,11 @@ osiSockTest_SRCS += osiSockTest.c testHarness_SRCS += osiSockTest.c TESTS += osiSockTest +TESTPROD_HOST += testexecname +testexecname_SRCS += testexecname.c +# no point in including in testHarness. Not implemented for RTEMS/vxWorks. +TESTS += testexecname + ifeq ($(BUILD_CLASS),HOST) ifneq ($(OS_CLASS),WIN32) # This test can only be run on a build host, and is broken on Windows diff --git a/modules/libcom/test/testexecname.c b/modules/libcom/test/testexecname.c new file mode 100644 index 000000000..87be847a0 --- /dev/null +++ b/modules/libcom/test/testexecname.c @@ -0,0 +1,24 @@ + +#include + +#include +#include + +#include + +MAIN(testexecname) +{ + testPlan(1); + + { + char *buf = epicsGetExecName(); + if(!buf) { + testSkip(1, "epicsGetExecName() not available for this target"); + } else { + char *loc = strstr(buf, "testexecname"); + testOk(!!loc, "Find \"testexecname\" in \"%s\"", buf); + } + } + + return testDone(); +} From 7bdbded47d78232d7871f523ea0d713c76d4deef Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 20 Nov 2018 21:59:34 -0800 Subject: [PATCH 4/8] travis-ci test rpath $ORIGIN --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d6130c753..e7b1ed57a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ addons: script: - .ci/travis-build.sh env: - - CMPLR=gcc + - CMPLR=gcc EXTRA=LINKER_USE_RPATH=ORIGIN - CMPLR=clang - CMPLR=gcc STATIC=YES - CMPLR=clang STATIC=YES From f5194b2274360cabab8ce9097ed31665f07e4ef4 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 13 Dec 2018 11:22:07 -0800 Subject: [PATCH 5/8] older binutils compat --- src/tools/makeRPath.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tools/makeRPath.py b/src/tools/makeRPath.py index edad80085..5d151a41a 100644 --- a/src/tools/makeRPath.py +++ b/src/tools/makeRPath.py @@ -26,6 +26,10 @@ for path in args.path: if args.root and os.path.relpath(path, args.root).startswith('../'): pass # absolute rpath else: + # some older binutils don't seem to handle $ORIGIN correctly + # when locating dependencies of libraries. So also provide + # the absolute path for internal use by 'ld' only. + output.append('-Wl,-rpath-link,'+path) path = os.path.relpath(path, fdir) output.append('-Wl,-rpath,'+os.path.join(args.origin, path)) From 87d5ca1619ec99055c5fdd0ecfcf7c8fc9edca57 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Mon, 24 Jun 2019 10:35:25 -0700 Subject: [PATCH 6/8] rpath $ORIGIN doc --- configure/CONFIG_SITE | 2 +- src/tools/makeRPath.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/configure/CONFIG_SITE b/configure/CONFIG_SITE index 49f82c7a8..a39911783 100644 --- a/configure/CONFIG_SITE +++ b/configure/CONFIG_SITE @@ -172,7 +172,7 @@ GCC_PIPE = NO # Must be either YES, NO, or ORIGIN. If you set this to NO you must also provide a # way for Base executables to find their shared libraries when they are # run at build-time, e.g. set the LD_LIBRARY_PATH environment variable. -# ORIGIN is Linux specific. +# ORIGIN is a feature of the ELF executable format used by Linux, freebsd, and solaris. LINKER_USE_RPATH = YES # Only used when LINKER_USE_RPATH=ORIGIN diff --git a/src/tools/makeRPath.py b/src/tools/makeRPath.py index 5d151a41a..eeda8ab55 100644 --- a/src/tools/makeRPath.py +++ b/src/tools/makeRPath.py @@ -10,9 +10,17 @@ from argparse import ArgumentParser if os.environ.get('EPICS_DEBUG_RPATH','')=='YES': sys.stderr.write('%s'%sys.argv) -P = ArgumentParser() +P = ArgumentParser(description='''Compute and output -rpath entries for each of the given paths. + Paths under --root will be computed as relative to --final .''', +epilog=''' +eg. A library to be placed in /build/lib and linked against libraries in +'/build/lib', '/build/module/lib', and '/other/lib' would pass: + + "makeRPath.py -F /build/lib -R /build /build/lib /build/module/lib /other/lib" +which prints "-Wl,-rpath,$ORIGIN/. -Wl,-rpath,$ORIGIN/../module/lib -Wl,-rpath,/other/lib" +''') P.add_argument('-F','--final',default=os.getcwd(), help='Final install location for ELF file') -P.add_argument('-R','--root',default='/') +P.add_argument('-R','--root',default='/', help='Root of relocatable tree.') P.add_argument('-O', '--origin', default='$ORIGIN') P.add_argument('path', nargs='*') args = P.parse_args() From ea1b208c33295f0b81f369bcb638a73c5a83c439 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Mon, 24 Jun 2019 13:23:28 -0700 Subject: [PATCH 7/8] redo softIoc to be more c++y --- modules/database/src/std/softIoc/softMain.cpp | 419 ++++++++---------- 1 file changed, 197 insertions(+), 222 deletions(-) diff --git a/modules/database/src/std/softIoc/softMain.cpp b/modules/database/src/std/softIoc/softMain.cpp index 01ef19b2f..bc945c80e 100644 --- a/modules/database/src/std/softIoc/softMain.cpp +++ b/modules/database/src/std/softIoc/softMain.cpp @@ -9,55 +9,12 @@ /* Author: Andrew Johnson Date: 2003-04-08 */ -/* Usage: - * softIoc [-D softIoc.dbd] [-h] [-S] [-s] [-a ascf] - * [-m macro=value,macro2=value2] [-d file.db] - * [-x prefix] [st.cmd] - * - * If used the -D option must come first, and specify the - * path to the softIoc.dbd file. The compile-time install - * location is saved in the binary as a default. - * - * Usage information will be printed if -h is given, then - * the program will exit normally. - * - * The -S option prevents an interactive shell being started - * after all arguments have been processed. - * - * Previous versions accepted a -s option to cause a shell - * to be started; this option is still accepted but ignored - * since a command shell is now started by default. - * - * Access Security can be enabled with the -a option giving - * the name of the configuration file; if any macros were - * set with -m before the -a option was given, they will be - * used as access security substitution macros. - * - * Any number of -m and -d arguments can be interspersed; - * the macros are applied to the following .db files. Each - * later -m option causes earlier macros to be discarded. - * - * The -x option loads the softIocExit.db with the macro - * IOC set to the string provided. This database contains - * a subroutine record named $(IOC):exit which has its field - * SNAM set to "exit". When this record is processed, the - * subroutine that runs will call epicsExit() with the value - * of the field A determining whether the exit status is - * EXIT_SUCCESS if (A == 0.0) or EXIT_FAILURE (A != 0.0). - * - * A st.cmd file is optional. If any databases were loaded - * the st.cmd file will be run *after* iocInit. To perform - * iocsh commands before iocInit, all database loading must - * be performed by the script itself, or by the user from - * the interactive IOC shell. - */ - -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include "registryFunction.h" #include "epicsThread.h" #include "epicsExit.h" @@ -74,6 +31,12 @@ extern "C" int softIoc_registerRecordDeviceDriver(struct dbBase *pdbbase); +#ifndef EPICS_BASE +// so IDEs knows EPICS_BASE is a string constant +# define EPICS_BASE "/" +# error -DEPICS_BASE required +#endif + #define DBD_BASE "dbd/softIoc.dbd" #define EXIT_BASE "db/softIocExit.db" #define DBD_FILE_REL "../../" DBD_BASE @@ -81,190 +44,202 @@ extern "C" int softIoc_registerRecordDeviceDriver(struct dbBase *pdbbase); #define DBD_FILE EPICS_BASE "/" DBD_BASE #define EXIT_FILE EPICS_BASE "/" EXIT_BASE -const char *arg0; -const char *base_dbd = DBD_FILE; -const char *exit_db = EXIT_FILE; - -static void preparePath(void) -{ - FILE *fp; - char *prefix = epicsGetExecDir(); - char *dbd, *exit; - if(!prefix) return; - - dbd = (char*)malloc(strlen(prefix) + strlen(DBD_FILE_REL) + 1); - if(dbd) { - dbd[0] = '\0'; - strcat(dbd, prefix); - strcat(dbd, DBD_FILE_REL); - printf("Testing '%s'\n", dbd); - if((fp = fopen(dbd, "rb"))!=NULL) { - fclose(fp); - base_dbd = dbd; - } - } - - exit = (char*)malloc(strlen(prefix) + strlen(EXIT_FILE_REL) + 1); - if(exit) { - exit[0] = '\0'; - strcat(exit, prefix); - strcat(exit, EXIT_FILE_REL); - if((fp = fopen(exit, "rb"))!=NULL) { - fclose(fp); - exit_db = exit; - } - } -} +namespace { static void exitSubroutine(subRecord *precord) { epicsExitLater((precord->a == 0.0) ? EXIT_SUCCESS : EXIT_FAILURE); } -static void usage(int status) { - printf("Usage: %s [-D softIoc.dbd] [-h] [-S] [-a ascf]\n", arg0); - puts("\t[-m macro=value,macro2=value2] [-d file.db]"); - puts("\t[-x prefix] [st.cmd]"); - puts("Compiled-in path to softIoc.dbd is:"); - printf("\t%s\n", base_dbd); - epicsExit(status); +void usage(const char *arg0, const std::string& base_dbd) { + std::cout<<"Usage: "< If used, must come first. Specify the path to the softIoc.dbdfile." + " The compile-time install location is saved in the binary as a default.\n" + "\n" + " -h Print this mesage and exit.\n" + "\n" + " -S Prevents an interactive shell being started.\n" + "\n" + " -s Previously caused a shell to be started. Now accepted and ignored.\n" + "\n" + " -a Access Security configuration file. Macro substitution is\n" + " performed.\n" + "\n" + " -m =,... Set/replace macro definitions used by subsequent -d and\n" + " -a.\n" + "\n" + " -d Load records from file (dbLoadRecords). Macro substitution is\n" + " performed.\n" + "\n" + " -x Load softIocExit.db. Provides a record \":exit\".\n" + " Put 0 to exit with success, or non-zero to exit with an error.\n" + "\n" + "Any number of -m and -d arguments can be interspersed; the macros are applied\n" + "to the following .db files. Each later -m option causes earlier macros to be\n" + "discarded.\n" + "\n" + "A st.cmd file is optional. If any databases were loaded the st.cmd file will\n" + "be run *after* iocInit. To perform iocsh commands before iocInit, all database\n" + "loading must be performed by the script itself, or by the user from the\n" + "interactive IOC shell.\n" + "\n" + "Compiled-in path to softIoc.dbd is:\n" + "\t"<(base_dbd); - char *macros = NULL; - char xmacro[PVNAME_STRINGSZ + 4]; - int startIocsh = 1; /* default = start shell */ - int loadedDb = 0; - - arg0 = strrchr(*argv, '/'); - if (!arg0) { - arg0 = *argv; - } else { - ++arg0; /* skip the '/' */ - } - - --argc, ++argv; - - /* Do this here in case the dbd file not available */ - if (argc>0 && **argv=='-' && (*argv)[1]=='h') { - usage(EXIT_SUCCESS); - } - - if (argc>1 && **argv=='-' && (*argv)[1]=='D') { - dbd_file = *++argv; - argc -= 2; - ++argv; - } - - if (dbLoadDatabase(dbd_file, NULL, NULL)) { - epicsExit(EXIT_FAILURE); - } - - softIoc_registerRecordDeviceDriver(pdbbase); - registryFunctionAdd("exit", (REGISTRYFUNCTION) exitSubroutine); + try { + std::string dbd_file(DBD_FILE), + exit_file(EXIT_FILE), + macros, // scratch space for macros (may be given more than once) + xmacro; + bool interactive = true; + bool loadedDb = false; - while (argc>1 && **argv == '-') { - switch ((*argv)[1]) { - case 'a': - if (macros) asSetSubstitutions(macros); - asSetFilename(*++argv); - --argc; - break; - - case 'd': - if (dbLoadRecords(*++argv, macros)) { - epicsExit(EXIT_FAILURE); - } - loadedDb = 1; - --argc; - break; - - case 'h': - usage(EXIT_SUCCESS); - - case 'm': - macros = *++argv; - --argc; - break; - - case 'S': - startIocsh = 0; - break; - - case 's': - break; - - case 'x': - epicsSnprintf(xmacro, sizeof xmacro, "IOC=%s", *++argv); - if (dbLoadRecords(exit_db, xmacro)) { - epicsExit(EXIT_FAILURE); - } - loadedDb = 1; - --argc; - break; - - default: - printf("%s: option '%s' not recognized\n", arg0, *argv); - usage(EXIT_FAILURE); - } - --argc; - ++argv; + // attempt to compute relative paths + { + std::string prefix; + char *cprefix = epicsGetExecDir(); + if(cprefix) { + try { + prefix = cprefix; + free(cprefix); + } catch(...) { + free(cprefix); + throw; + } + } + + dbd_file = prefix + DBD_FILE_REL; + exit_file = prefix + EXIT_FILE_REL; + } + + int opt; + + while ((opt = getopt(argc, argv, "ha:d:m:Ssx:")) != -1) { + switch (opt) { + case 'h': /* Print usage */ + usage(argv[0], dbd_file); + epicsExit(0); + return 0; + default: + usage(argv[0], dbd_file); + std::cerr<<"Unknown argument: -"<0 && **argv=='-') { - switch((*argv)[1]) { - case 'a': - case 'd': - case 'm': - case 'x': - printf("%s: missing argument to option '%s'\n", arg0, *argv); - usage(EXIT_FAILURE); - - case 'h': - usage(EXIT_SUCCESS); - - case 'S': - startIocsh = 0; - break; - - case 's': - break; - - default: - printf("%s: option '%s' not recognized\n", arg0, *argv); - usage(EXIT_FAILURE); - } - --argc; - ++argv; - } - - if (loadedDb) { - iocInit(); - epicsThreadSleep(0.2); - } - - /* run user's startup script */ - if (argc>0) { - if (iocsh(*argv)) epicsExit(EXIT_FAILURE); - epicsThreadSleep(0.2); - loadedDb = 1; /* Give it the benefit of the doubt... */ - } - - /* start an interactive shell if it was requested */ - if (startIocsh) { - iocsh(NULL); - } else { - if (loadedDb) { - epicsThreadExitMain(); - } else { - printf("%s: Nothing to do!\n", arg0); - usage(EXIT_FAILURE); - } - } - epicsExit(EXIT_SUCCESS); - /*Note that the following statement will never be executed*/ - return 0; } From 784d619bdee4653f8412f664b7338fe42f67e047 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Mon, 24 Jun 2019 15:41:48 -0700 Subject: [PATCH 8/8] makeRPath allow multiple root directories Allows handling of complex situations like a package build where some libraries are in a staging area, but will be copied to the same final location. eg. LINKER_ORIGIN_ROOT=/usr/lib/epics:/build/mymodule Where build TOP is /build/mymodule --- configure/CONFIG_SITE | 5 ++-- src/tools/makeRPath.py | 59 +++++++++++++++++++++++++++++++++--------- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/configure/CONFIG_SITE b/configure/CONFIG_SITE index a39911783..c46703f84 100644 --- a/configure/CONFIG_SITE +++ b/configure/CONFIG_SITE @@ -176,9 +176,10 @@ GCC_PIPE = NO LINKER_USE_RPATH = YES # Only used when LINKER_USE_RPATH=ORIGIN -# The build time root of the relocatable tree. -# Linking to libraries under this root directory will be relative. +# The build time root(s) of the relocatable tree (separate multiple w/ ':'). +# Linking to libraries under any root directory will be relative. # Linking to libraries outside of this root will be absolute. +# All root directories are considered to be the same. LINKER_ORIGIN_ROOT = $(INSTALL_LOCATION) # Overrides for the settings above may appear in a CONFIG_SITE.local file diff --git a/src/tools/makeRPath.py b/src/tools/makeRPath.py index eeda8ab55..000b8b450 100644 --- a/src/tools/makeRPath.py +++ b/src/tools/makeRPath.py @@ -4,6 +4,7 @@ from __future__ import print_function import sys import os +from collections import OrderedDict # used as OrderedSet from argparse import ArgumentParser @@ -20,26 +21,60 @@ eg. A library to be placed in /build/lib and linked against libraries in which prints "-Wl,-rpath,$ORIGIN/. -Wl,-rpath,$ORIGIN/../module/lib -Wl,-rpath,/other/lib" ''') P.add_argument('-F','--final',default=os.getcwd(), help='Final install location for ELF file') -P.add_argument('-R','--root',default='/', help='Root of relocatable tree.') +P.add_argument('-R','--root',default='', help='Root(s) of relocatable tree. Separate with :') P.add_argument('-O', '--origin', default='$ORIGIN') P.add_argument('path', nargs='*') args = P.parse_args() -fdir = os.path.abspath(args.final) +# eg. +# target to be installed as: /build/bin/blah +# +# post-install will copy as: /install/bin/blah +# +# Need to link against: +# /install/lib/libA.so +# /build/lib/libB.so +# /other/lib/libC.so +# +# Want final result to be: +# -rpath $ORIGIN/../lib -rpath /other/lib \ +# -rpath-link /build/lib -rpath-link /install/lib -output = [] +fdir = os.path.abspath(args.final) +roots = [os.path.abspath(root) for root in args.root.split(':') if len(root)] + +# find the root which contains the final location +froot = None +for root in roots: + frel = os.path.relpath(fdir, root) + if not frel.startswith('..'): + # final dir is under this root + froot = root + break + +if froot is None: + sys.stderr.write("makeRPath: Final location %s\nNot under any of: %s\n"%(fdir, roots)) + sys.exit(1) + +output = OrderedDict() for path in args.path: path = os.path.abspath(path) - if args.root and os.path.relpath(path, args.root).startswith('../'): - pass # absolute rpath - else: - # some older binutils don't seem to handle $ORIGIN correctly - # when locating dependencies of libraries. So also provide - # the absolute path for internal use by 'ld' only. - output.append('-Wl,-rpath-link,'+path) - path = os.path.relpath(path, fdir) + for root in roots: + rrel = os.path.relpath(path, root) + if not rrel.startswith('..'): + # path is under this root - output.append('-Wl,-rpath,'+os.path.join(args.origin, path)) + # some older binutils don't seem to handle $ORIGIN correctly + # when locating dependencies of libraries. So also provide + # the absolute path for internal use by 'ld' only. + output['-Wl,-rpath-link,'+path] = True + + # frel is final location relative to enclosing root + # rrel is target location relative to enclosing root + path = os.path.relpath(rrel, frel) + break + + output['-Wl,-rpath,'+os.path.join(args.origin, path)] = True print(' '.join(output))