libtrellis: Add support for portable execution with trellis database
In order to execute the ecpmulti, ecppack and ecpunpack programs they
must be able to access the trellis database files. These files are
installed alongside the tools in the data directory (normally
/usr/share). To enable portable execution of these programs such that
they can access the database relative to their executables path the
programs must be built with the relative path embedded and also be able
to access the executables path at run time.
Since libtrellis already depends on Boost it makes sense to use the
Boost dll module. This module provides the 'program_location' function
which can be used with the 'boost::filesystem::path' to build the
absolute path to the database at run time. The only downside with the
use of the Boost dll module is that a unused component of the included
source relies on certain dynamic linker functions which requires linking
to libdl on some platforms. This however is easily handled by the
CMAKE_DL_LIBS variable which expands to the correct linker arg on
targets that require linking with the dynamic linker.
The Boost dll module was only added in 1.61. In order to support older
versions of Boost such as that of Ubuntu 16.04 (1.58), a local
implementation of 'program_location' is added which is based on the
'proc_self_dirname' implementation from yosys. This version is chosen
due to its use in a number of tools similar to libtrellis (e.g.
icestorm) but also due to its ISC license which matches libtrellis. This
implementation is conditionally used if the Boost dll module is
unavailable.
The cmake configuration is also changed to generate a relative path from
datadir to bindir, this path is then provided during the build as
TRELLIS_RPATH_DATADIR.
Note: ecppll does not currently use the trellis database
Signed-off-by: Nathan Rossi <nathan@nathanrossi.com>
diff --git a/libtrellis/CMakeLists.txt b/libtrellis/CMakeLists.txt
index 267bd58..9b5be20 100644
--- a/libtrellis/CMakeLists.txt
+++ b/libtrellis/CMakeLists.txt
@@ -111,6 +111,7 @@
include(GNUInstallDirs)
file(RELATIVE_PATH TRELLIS_RPATH_LIBDIR /${CMAKE_INSTALL_BINDIR} /${CMAKE_INSTALL_LIBDIR})
+file(RELATIVE_PATH TRELLIS_RPATH_DATADIR /${CMAKE_INSTALL_BINDIR} /${CMAKE_INSTALL_DATADIR})
function(setup_rpath name)
if(APPLE)
@@ -126,23 +127,23 @@
endfunction()
add_executable(ecppack ${INCLUDE_FILES} tools/ecppack.cpp)
-target_compile_definitions(ecppack PRIVATE TRELLIS_PREFIX="${CMAKE_INSTALL_PREFIX}")
-target_link_libraries(ecppack trellis ${Boost_LIBRARIES} ${link_param})
+target_compile_definitions(ecppack PRIVATE TRELLIS_RPATH_DATADIR="${TRELLIS_RPATH_DATADIR}")
+target_link_libraries(ecppack trellis ${Boost_LIBRARIES} ${CMAKE_DL_LIBS} ${link_param})
setup_rpath(ecppack)
add_executable(ecpunpack ${INCLUDE_FILES} tools/ecpunpack.cpp)
-target_compile_definitions(ecpunpack PRIVATE TRELLIS_PREFIX="${CMAKE_INSTALL_PREFIX}")
-target_link_libraries(ecpunpack trellis ${Boost_LIBRARIES} ${link_param})
+target_compile_definitions(ecpunpack PRIVATE TRELLIS_RPATH_DATADIR="${TRELLIS_RPATH_DATADIR}")
+target_link_libraries(ecpunpack trellis ${Boost_LIBRARIES} ${CMAKE_DL_LIBS} ${link_param})
setup_rpath(ecpunpack)
add_executable(ecppll ${INCLUDE_FILES} tools/ecppll.cpp)
-target_compile_definitions(ecppll PRIVATE TRELLIS_PREFIX="${CMAKE_INSTALL_PREFIX}")
-target_link_libraries(ecppll trellis ${Boost_LIBRARIES} ${link_param})
+target_compile_definitions(ecppll PRIVATE TRELLIS_RPATH_DATADIR="${TRELLIS_RPATH_DATADIR}")
+target_link_libraries(ecppll trellis ${Boost_LIBRARIES} ${CMAKE_DL_LIBS} ${link_param})
setup_rpath(ecppll)
add_executable(ecpmulti ${INCLUDE_FILES} tools/ecpmulti.cpp)
-target_compile_definitions(ecpmulti PRIVATE TRELLIS_PREFIX="${CMAKE_INSTALL_PREFIX}")
-target_link_libraries(ecpmulti trellis ${Boost_LIBRARIES} ${link_param})
+target_compile_definitions(ecpmulti PRIVATE TRELLIS_RPATH_DATADIR="${TRELLIS_RPATH_DATADIR}")
+target_link_libraries(ecpmulti trellis ${Boost_LIBRARIES} ${CMAKE_DL_LIBS} ${link_param})
setup_rpath(ecpmulti)
if (BUILD_SHARED)
diff --git a/libtrellis/include/DatabasePath.hpp b/libtrellis/include/DatabasePath.hpp
new file mode 100644
index 0000000..4063802
--- /dev/null
+++ b/libtrellis/include/DatabasePath.hpp
@@ -0,0 +1,158 @@
+#ifndef LIBTRELLIS_DATABASEPATH_H
+#define LIBTRELLIS_DATABASEPATH_H
+
+#include <boost/version.hpp>
+#include <boost/filesystem.hpp>
+
+#if BOOST_VERSION >= 106100
+
+#include <boost/dll/runtime_symbol_info.hpp>
+
+std::string get_database_path()
+{
+ boost::filesystem::path executable_path = boost::dll::program_location().parent_path();
+ boost::filesystem::path database_datadir_relative(TRELLIS_RPATH_DATADIR "/trellis/database");
+ std::string database_folder = (executable_path /= database_datadir_relative).string();
+ return database_folder;
+}
+
+#else
+
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include <errno.h>
+#include <string>
+#include <sstream>
+#include <cstring>
+
+#ifdef _WIN32
+# include <windows.h>
+# include <io.h>
+#elif defined(__APPLE__)
+# include <mach-o/dyld.h>
+# include <unistd.h>
+#else
+# include <unistd.h>
+#endif
+
+#ifdef __FreeBSD__
+# include <sys/sysctl.h>
+#endif
+
+#include <limits.h>
+
+#if defined(__linux__) || defined(__CYGWIN__)
+std::string proc_self_dirname()
+{
+ char path[PATH_MAX];
+ ssize_t buflen = readlink("/proc/self/exe", path, sizeof(path));
+ if (buflen < 0) {
+ fprintf(stderr, "fatal error: readlink(\"/proc/self/exe\") failed: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ while (buflen > 0 && path[buflen-1] != '/')
+ buflen--;
+ return std::string(path, buflen);
+}
+#elif defined(__FreeBSD__)
+std::string proc_self_dirname()
+{
+ int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
+ size_t buflen;
+ char *buffer;
+ std::string path;
+ if (sysctl(mib, 4, NULL, &buflen, NULL, 0) != 0) {
+ fprintf(stderr, "fatal error: sysctl failed: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ buffer = (char*)malloc(buflen);
+ if (buffer == NULL) {
+ fprintf(stderr, "fatal error: malloc failed: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ if (sysctl(mib, 4, buffer, &buflen, NULL, 0) != 0) {
+ fprintf(stderr, "fatal error: sysctl failed: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ while (buflen > 0 && buffer[buflen-1] != '/')
+ buflen--;
+ path.assign(buffer, buflen);
+ free(buffer);
+ return path;
+}
+#elif defined(__APPLE__)
+std::string proc_self_dirname()
+{
+ char *path = NULL;
+ uint32_t buflen = 0;
+ while (_NSGetExecutablePath(path, &buflen) != 0)
+ path = (char *) realloc((void *) path, buflen);
+ while (buflen > 0 && path[buflen-1] != '/')
+ buflen--;
+ return std::string(path, buflen);
+}
+#elif defined(_WIN32)
+std::string proc_self_dirname()
+{
+ int i = 0;
+# ifdef __MINGW32__
+ char longpath[MAX_PATH + 1];
+ char shortpath[MAX_PATH + 1];
+# else
+ WCHAR longpath[MAX_PATH + 1];
+ TCHAR shortpath[MAX_PATH + 1];
+# endif
+ if (!GetModuleFileName(0, longpath, MAX_PATH+1)) {
+ fprintf(stderr, "fatal error: GetModuleFileName() failed.\n");
+ exit(EXIT_FAILURE);
+ }
+ if (!GetShortPathName(longpath, shortpath, MAX_PATH+1)) {
+ fprintf(stderr, "fatal error: GetShortPathName() failed.\n");
+ exit(EXIT_FAILURE);
+ }
+ while (shortpath[i] != 0)
+ i++;
+ while (i > 0 && shortpath[i-1] != '/' && shortpath[i-1] != '\\')
+ shortpath[--i] = 0;
+ std::string path;
+ for (i = 0; shortpath[i]; i++)
+ path += char(shortpath[i]);
+ return path;
+}
+#elif defined(EMSCRIPTEN)
+std::string proc_self_dirname()
+{
+ return "/";
+}
+#else
+ #error Dont know how to determine process executable base path!
+#endif
+
+std::string get_database_path()
+{
+ boost::filesystem::path executable_path = boost::filesystem::path(proc_self_dirname()).parent_path();
+ boost::filesystem::path database_datadir_relative(TRELLIS_RPATH_DATADIR "/trellis/database");
+ std::string database_folder = (executable_path /= database_datadir_relative).string();
+ return database_folder;
+}
+
+#endif
+
+#endif //LIBTRELLIS_DATABASEPATH_HPP
diff --git a/libtrellis/tools/ecpmulti.cpp b/libtrellis/tools/ecpmulti.cpp
index 67d7965..7862436 100644
--- a/libtrellis/tools/ecpmulti.cpp
+++ b/libtrellis/tools/ecpmulti.cpp
@@ -3,6 +3,7 @@
#include "Bitstream.hpp"
#include "Chip.hpp"
#include "Database.hpp"
+#include "DatabasePath.hpp"
#include "Tile.hpp"
#include <iostream>
#include <boost/program_options.hpp>
@@ -39,7 +40,8 @@
using namespace Trellis;
namespace po = boost::program_options;
- std::string database_folder = TRELLIS_PREFIX "/share/trellis/database";
+ std::string database_folder = get_database_path();
+
uint32_t flash_size_bytes = 0;
boost::optional<uint32_t> input_idcode;
boost::optional<uint32_t> output_idcode;
diff --git a/libtrellis/tools/ecppack.cpp b/libtrellis/tools/ecppack.cpp
index 31a3f2b..40ee45e 100644
--- a/libtrellis/tools/ecppack.cpp
+++ b/libtrellis/tools/ecppack.cpp
@@ -2,6 +2,7 @@
#include "Bitstream.hpp"
#include "Chip.hpp"
#include "Database.hpp"
+#include "DatabasePath.hpp"
#include <iostream>
#include <boost/program_options.hpp>
#include <stdexcept>
@@ -24,7 +25,7 @@
using namespace Trellis;
namespace po = boost::program_options;
- std::string database_folder = TRELLIS_PREFIX "/share/trellis/database";
+ std::string database_folder = get_database_path();
po::options_description options("Allowed options");
options.add_options()("help,h", "show help");
diff --git a/libtrellis/tools/ecpunpack.cpp b/libtrellis/tools/ecpunpack.cpp
index 68ad3a8..b7b960a 100644
--- a/libtrellis/tools/ecpunpack.cpp
+++ b/libtrellis/tools/ecpunpack.cpp
@@ -2,6 +2,7 @@
#include "Bitstream.hpp"
#include "Chip.hpp"
#include "Database.hpp"
+#include "DatabasePath.hpp"
#include <iostream>
#include <boost/optional.hpp>
#include <boost/program_options.hpp>
@@ -17,7 +18,7 @@
namespace po = boost::program_options;
boost::optional<uint32_t> idcode;
- std::string database_folder = TRELLIS_PREFIX "/share/trellis/database";
+ std::string database_folder = get_database_path();
po::options_description options("Allowed options");
options.add_options()("help,h", "show help");