diff --git a/CMakeLists.txt b/CMakeLists.txt index 93b4c4101..b81c2acf0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,13 @@ if (BUILD_SDF) # Find tinyxml2. gz_find_package(TINYXML2 REQUIRED) + ################################################# + # Find DL if doing relocatable installation + if (GZ_ENABLE_RELOCATABLE_INSTALL) + gz_find_package(DL REQUIRED) + endif() + + ################################################ # Find urdfdom parser. Logic: # diff --git a/include/sdf/InstallationDirectories.hh b/include/sdf/InstallationDirectories.hh new file mode 100644 index 000000000..be3b0c678 --- /dev/null +++ b/include/sdf/InstallationDirectories.hh @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef SDF_INSTALLATION_DIRECTORIES_HH_ +#define SDF_INSTALLATION_DIRECTORIES_HH_ + +#include + +#include +#include + +namespace sdf +{ + inline namespace SDF_VERSION_NAMESPACE { + + /// \brief getInstallPrefix return the install prefix of the library + /// i.e. CMAKE_INSTALL_PREFIX unless the library has been moved + SDFORMAT_VISIBLE std::string getInstallPrefix(); + + /// \brief getSharePath return the share directory used by sdformat + SDFORMAT_VISIBLE std::string getSharePath(); + + } +} + +#endif diff --git a/include/sdf/config.hh.in b/include/sdf/config.hh.in index 6a32cb8bc..456eb12cc 100644 --- a/include/sdf/config.hh.in +++ b/include/sdf/config.hh.in @@ -48,11 +48,11 @@ #cmakedefine SDFORMAT_DISABLE_CONSOLE_LOGFILE 1 #ifndef SDF_SHARE_PATH -#define SDF_SHARE_PATH "${CMAKE_INSTALL_FULL_DATAROOTDIR}/" +#define SDF_SHARE_PATH _Pragma ("GCC warning \"'SDF_SHARE_PATH' macro is deprecated, use sdf::getSharePath() function instead. \"") "${CMAKE_INSTALL_FULL_DATAROOTDIR}/" #endif #ifndef SDF_VERSION_PATH -#define SDF_VERSION_PATH "${CMAKE_INSTALL_FULL_DATAROOTDIR}/sdformat${PROJECT_VERSION_MAJOR}/${PROJECT_VERSION}" +#define SDF_VERSION_PATH _Pragma ("GCC warning \"'SDF_VERSION_PATH' macro is deprecated and should not be used. \"") "${CMAKE_INSTALL_FULL_DATAROOTDIR}/sdformat${PROJECT_VERSION_MAJOR}/${PROJECT_VERSION}" #endif #endif // #ifndef SDF_CONFIG_HH_ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dfd569186..57dab8757 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,6 +23,24 @@ gz_create_core_library(SOURCES ${sources} CXX_STANDARD 17 LEGACY_PROJECT_PREFIX SDFormat ) +gz_add_get_install_prefix_impl(GET_INSTALL_PREFIX_FUNCTION sdf::getInstallPrefix + GET_INSTALL_PREFIX_HEADER sdf/InstallationDirectories.hh + OVERRIDE_INSTALL_PREFIX_ENV_VARIABLE SDF_INSTALL_PREFIX) + +# CMAKE_INSTALL_DATAROOTDIR may be an absolute path, let's make sure to use the +# relative version +if(IS_ABSOLUTE "${CMAKE_INSTALL_DATAROOTDIR}") + file(RELATIVE_PATH CMAKE_INSTALL_RELATIVE_DATAROOTDIR "${CMAKE_INSTALL_PREFIX}" "${CMAKE_INSTALL_DATAROOTDIR}") +else() + set(CMAKE_INSTALL_RELATIVE_DATAROOTDIR "${CMAKE_INSTALL_DATAROOTDIR}") +endif() + +set_property( + SOURCE InstallationDirectories.cc + PROPERTY COMPILE_DEFINITIONS + CMAKE_INSTALL_RELATIVE_DATAROOTDIR="${CMAKE_INSTALL_RELATIVE_DATAROOTDIR}" +) + target_link_libraries(${PROJECT_LIBRARY_TARGET_NAME} PUBLIC @@ -91,6 +109,11 @@ if (BUILD_TESTING) -DGZ_SDFORMAT_STATIC_DEFINE ) + if(WIN32) + target_link_libraries(${PROJECT_LIBRARY_TARGET_NAME} + PRIVATE shlwapi) + endif() + gz_build_tests( TYPE UNIT SOURCES ${gtest_sources} @@ -99,6 +122,8 @@ if (BUILD_TESTING) ${PROJECT_SOURCE_DIR}/test LIB_DEPS library_for_tests + ENVIRONMENT + SDF_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} ) if (TARGET UNIT_gz_TEST) diff --git a/src/InstallationDirectories.cc b/src/InstallationDirectories.cc new file mode 100644 index 000000000..167df10eb --- /dev/null +++ b/src/InstallationDirectories.cc @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2024 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include + +#include +#include + +#ifdef _WIN32 +#include +#endif + +namespace sdf +{ +inline namespace SDF_VERSION_NAMESPACE { + +// We locally import the gz::common::joinPaths function +// See https://github.com/gazebosim/gz-physics/pull/507#discussion_r1186919267 +// for more details + +// Function imported from +// https://github.com/gazebosim/gz-common/blob/ignition-common4_4.6.2/src/FilesystemBoost.cc#L507 +#ifndef WIN32 +static const char preferred_separator = '/'; +#else // Windows +static const char preferred_separator = '\\'; +#endif +const std::string separator(const std::string &_p) +{ + return _p + preferred_separator; +} + +// Function imported from +// https://github.com/gazebosim/gz-common/blob/ignition-common4_4.6.2/src/Filesystem.cc#L227 +std::string checkWindowsPath(const std::string _path) +{ + if (_path.empty()) + return _path; + + // Check if this is a http or https, if so change backslashes generated by + // jointPaths to '/' + if ((_path.size() > 7 && 0 == _path.compare(0, 7, "http://")) || + (_path.size() > 8 && 0 == _path.compare(0, 8, "https://"))) + { + return std::regex_replace(_path, std::regex(R"(\\)"), "/"); + } + + // This is a Windows path, convert all '/' into backslashes + std::string result = std::regex_replace(_path, std::regex(R"(/)"), "\\"); + std::string drive_letters; + + // only Windows contains absolute paths starting with drive letters + if (result.length() > 3 && 0 == result.compare(1, 2, ":\\")) + { + drive_letters = result.substr(0, 3); + result = result.substr(3); + } + result = drive_letters + std::regex_replace( + result, std::regex("[<>:\"|?*]"), ""); + return result; +} + +// Function imported from +// https://github.com/gazebosim/gz-common/blob/ignition-common4_4.6.2/src/Filesystem.cc#L256 +std::string joinPaths(const std::string &_path1, + const std::string &_path2) +{ + + /// This function is used to avoid duplicated path separators at the + /// beginning/end of the string, and between the two paths being joined. + /// \param[in] _path This is the string to sanitize. + /// \param[in] _stripLeading True if the leading separator should be + /// removed. + auto sanitizeSlashes = [](const std::string &_path, + bool _stripLeading = false) + { + // Shortcut + if (_path.empty()) + return _path; + + std::string result = _path; + + // Use the appropriate character for each platform. +#ifndef _WIN32 + char replacement = '/'; +#else + char replacement = '\\'; +#endif + + // Sanitize the start of the path. + size_t index = 0; + size_t leadingIndex = _stripLeading ? 0 : 1; + for (; index < result.length() && result[index] == replacement; ++index) + { + } + if (index > leadingIndex) + result.erase(leadingIndex, index-leadingIndex); + + // Sanitize the end of the path. + index = result.length()-1; + for (; index < result.length() && result[index] == replacement; --index) + { + } + index += 1; + if (index < result.length()-1) + result.erase(index+1); + return result; + }; + + std::string path; +#ifndef _WIN32 + path = sanitizeSlashes(sanitizeSlashes(separator(_path1)) + + sanitizeSlashes(_path2, true)); +#else // _WIN32 + std::string path1 = sanitizeSlashes(checkWindowsPath(_path1)); + std::string path2 = sanitizeSlashes(checkWindowsPath(_path2), true); + std::vector combined(path1.length() + path2.length() + 2); + if (::PathCombineA(combined.data(), path1.c_str(), path2.c_str()) != NULL) + { + path = sanitizeSlashes(checkWindowsPath(std::string(combined.data()))); + } + else + { + path = sanitizeSlashes(checkWindowsPath(separator(path1) + path2)); + } +#endif // _WIN32 + return path; +} + +std::string getSharePath() +{ + return sdf::joinPaths( + getInstallPrefix(), CMAKE_INSTALL_RELATIVE_DATAROOTDIR); +} + +} +} diff --git a/src/SDF.cc b/src/SDF.cc index e033aac2b..e090ed097 100644 --- a/src/SDF.cc +++ b/src/SDF.cc @@ -29,6 +29,7 @@ #include "sdf/Console.hh" #include "sdf/Error.hh" #include "sdf/Filesystem.hh" +#include "sdf/InstallationDirectories.hh" #include "sdf/SDFImpl.hh" #include "SDFImplPrivate.hh" #include "sdf/sdf_config.h" @@ -47,11 +48,15 @@ std::string SDF::version = SDF_VERSION; // NOLINT(runtime/string) std::string sdfSharePath() { -#ifdef SDF_SHARE_PATH - if (std::string(SDF_SHARE_PATH) != "/") - return SDF_SHARE_PATH; -#endif - return ""; + std::string sharePath = sdf::getSharePath(); + if (sharePath != "/") + { + return sharePath; + } + else + { + return ""; + } } ///////////////////////////////////////////////// diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt index b7d846e95..e7e4b2c93 100644 --- a/test/integration/CMakeLists.txt +++ b/test/integration/CMakeLists.txt @@ -77,6 +77,7 @@ endif() gz_build_tests(TYPE ${TEST_TYPE} SOURCES ${tests} INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/test + ENVIRONMENT SDF_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} ) diff --git a/test/performance/CMakeLists.txt b/test/performance/CMakeLists.txt index c809bf32e..fe3e17931 100644 --- a/test/performance/CMakeLists.txt +++ b/test/performance/CMakeLists.txt @@ -4,4 +4,7 @@ set(tests parser_urdf.cc ) -gz_build_tests(TYPE ${TEST_TYPE} SOURCES ${tests} INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/test) +gz_build_tests(TYPE ${TEST_TYPE} + SOURCES ${tests} + INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/test + ENVIRONMENT SDF_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX})