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");