diff --git a/include/gz/sim/Util.hh b/include/gz/sim/Util.hh index efc9458647..5630e87d72 100644 --- a/include/gz/sim/Util.hh +++ b/include/gz/sim/Util.hh @@ -287,19 +287,31 @@ namespace ignition std::optional IGNITION_GAZEBO_VISIBLE sphericalCoordinates( Entity _entity, const EntityComponentManager &_ecm); - /// \brief Environment variable holding resource paths. + /// \brief Environment variable holding resource paths. Prefer using + /// GZ_SIM_RESOURCE_PATH for compatibility with newer versions of Gazebo. const std::string kResourcePathEnv{"IGN_GAZEBO_RESOURCE_PATH"}; + /// \brief Environment variable holding resource paths. + const std::string kResourcePathEnvGzSim{"GZ_SIM_RESOURCE_PATH"}; /// \brief Environment variable used by SDFormat to find URIs inside /// `` const std::string kSdfPathEnv{"SDF_PATH"}; - /// \brief Environment variable holding server config paths. + /// \brief Environment variable holding server config paths. Prefer using + /// GZ_SIM_SERVER_CONFIG_PATH for compatibility with newer versions of + /// Gazebo. const std::string kServerConfigPathEnv{"IGN_GAZEBO_SERVER_CONFIG_PATH"}; + /// \brief Environment variable holding server config paths. + const std::string kServerConfigPathEnvGzSim{"GZ_SIM_SERVER_CONFIG_PATH"}; /// \brief Environment variable holding paths to custom rendering engine - /// plugins. + /// plugins. Prefer using GZ_SIM_RENDER_ENGINE_PATH for compatibility with + /// newer versions of Gazebo const std::string kRenderPluginPathEnv{"IGN_GAZEBO_RENDER_ENGINE_PATH"}; + /// \brief Environment variable holding paths to custom rendering engine + /// plugins. + const std::string kRenderPluginPathEnvGzSim{"GZ_SIM_RENDER_ENGINE_PATH"}; + } } } diff --git a/src/Conversions.cc b/src/Conversions.cc index 5ce4c3e1c8..abaff726ae 100644 --- a/src/Conversions.cc +++ b/src/Conversions.cc @@ -1713,9 +1713,8 @@ msgs::ParticleEmitter ignition::gazebo::convert(const sdf::ParticleEmitter &_in) { std::string path = asFullPath(_in.ColorRangeImage(), _in.FilePath()); - common::SystemPaths systemPaths; - systemPaths.SetFilePathEnv(kResourcePathEnv); - std::string absolutePath = systemPaths.FindFile(path); + const std::string absolutePath = + common::SystemPaths::LocateLocalFile(path, gazebo::resourcePaths()); if (!absolutePath.empty()) out.mutable_color_range_image()->set_data(absolutePath); diff --git a/src/ServerConfig.cc b/src/ServerConfig.cc index 573e0e7f0e..9685855a93 100644 --- a/src/ServerConfig.cc +++ b/src/ServerConfig.cc @@ -922,30 +922,26 @@ ignition::gazebo::parsePluginsFromString(const std::string &_str) std::list ignition::gazebo::loadPluginInfo(bool _isPlayback) { - std::list ret; // 1. Check contents of environment variable - std::string envConfig; - bool configSet = common::env(kServerConfigPathEnv, - envConfig, - true); - - if (configSet) + auto parsePlugins = [](const std::string &_serverConfigPathEnv, + const std::string &_envConfig) { - if (common::exists(envConfig)) + std::list ret; + if (common::exists(_envConfig)) { // Parse configuration stored in environment variable - ret = gz::sim::parsePluginsFromFile(envConfig); + ret = gz::sim::parsePluginsFromFile(_envConfig); if (ret.empty()) { // This may be desired behavior, but warn just in case. // Some users may want to defer all loading until later // during runtime. - ignwarn << kServerConfigPathEnv - << " set but no plugins found\n"; + ignwarn << _serverConfigPathEnv + << " set but no plugins found\n"; } igndbg << "Loaded (" << ret.size() << ") plugins from file " << - "[" << envConfig << "]\n"; + "[" << _envConfig << "]\n"; return ret; } @@ -954,11 +950,31 @@ ignition::gazebo::loadPluginInfo(bool _isPlayback) // This may be desired behavior, but warn just in case. // Some users may want to defer all loading until late // during runtime. - ignwarn << kServerConfigPathEnv - << " set but no file found," - << " no plugins loaded\n"; + ignwarn << _serverConfigPathEnv + << " set but no file found," + << " no plugins loaded\n"; return ret; } + }; + + { + std::string envConfig; + bool configSet = common::env(kServerConfigPathEnv, envConfig, true); + if (configSet) + { + return parsePlugins(kServerConfigPathEnv, envConfig); + } + } + + // Process the gz-sim environment variable the same way. If the IGN variable + // is set, it will have precedence. + { + std::string envConfig; + bool configSet = common::env(kServerConfigPathEnvGzSim, envConfig, true); + if (configSet) + { + return parsePlugins(kServerConfigPathEnvGzSim, envConfig); + } } std::string configFilename; @@ -989,7 +1005,7 @@ ignition::gazebo::loadPluginInfo(bool _isPlayback) { ignerr << "Failed to create directory [" << defaultConfigDir << "]." << std::endl; - return ret; + return {}; } if (!common::exists(installedConfig)) @@ -998,14 +1014,14 @@ ignition::gazebo::loadPluginInfo(bool _isPlayback) << "] to default config [" << defaultConfig << "]." << "(file " << installedConfig << " doesn't exist)" << std::endl; - return ret; + return {}; } else if (!common::copyFile(installedConfig, defaultConfig)) { ignerr << "Failed to copy installed config [" << installedConfig << "] to default config [" << defaultConfig << "]." << std::endl; - return ret; + return {}; } else { @@ -1015,7 +1031,7 @@ ignition::gazebo::loadPluginInfo(bool _isPlayback) } } - ret = gz::sim::parsePluginsFromFile(defaultConfig); + const auto ret = gz::sim::parsePluginsFromFile(defaultConfig); if (ret.empty()) { diff --git a/src/ServerConfig_TEST.cc b/src/ServerConfig_TEST.cc index 28fba235ed..e423f8ce5d 100644 --- a/src/ServerConfig_TEST.cc +++ b/src/ServerConfig_TEST.cc @@ -198,14 +198,8 @@ TEST(LoadPluginInfo, FromEmptyEnv) EXPECT_TRUE(common::unsetenv(kServerConfigPathEnv)); } -////////////////////////////////////////////////// -TEST(LoadPluginInfo, FromValidEnv) +void testFromValidEnvPlugins() { - auto validPath = common::joinPaths(PROJECT_SOURCE_PATH, - "test", "worlds", "server_valid2.config"); - - ASSERT_TRUE(common::setenv(kServerConfigPathEnv, validPath)); - auto plugins = loadPluginInfo(); ASSERT_EQ(2u, plugins.size()); @@ -226,9 +220,36 @@ TEST(LoadPluginInfo, FromValidEnv) EXPECT_EQ("gz::sim::TestModelSystem", plugin->Name()); EXPECT_EQ("gz::sim::TestModelSystem", plugin->Plugin().Name()); +} +////////////////////////////////////////////////// +TEST(LoadPluginInfo, FromValidEnv) +{ + auto validPath = common::joinPaths(PROJECT_SOURCE_PATH, + "test", "worlds", "server_valid2.config"); + + ASSERT_TRUE(common::setenv(kServerConfigPathEnv, validPath)); + + SCOPED_TRACE("FromValidEnv"); + testFromValidEnvPlugins(); + EXPECT_TRUE(common::unsetenv(kServerConfigPathEnv)); } +////////////////////////////////////////////////// +TEST(LoadPluginInfo, FromValidEnvGzSimCompatibility) +{ + auto validPath = common::joinPaths(PROJECT_SOURCE_PATH, + "test", "worlds", "server_valid2.config"); + + ASSERT_TRUE(common::unsetenv(kServerConfigPathEnv)); + ASSERT_TRUE(common::setenv(kServerConfigPathEnvGzSim, validPath)); + + SCOPED_TRACE("FromValidEnvGzSimCompatibility"); + testFromValidEnvPlugins(); + + EXPECT_TRUE(common::unsetenv(kServerConfigPathEnvGzSim)); +} + ////////////////////////////////////////////////// TEST(ServerConfig, GenerateRecordPlugin) { diff --git a/src/ServerPrivate.hh b/src/ServerPrivate.hh index dafb801e77..a41640262f 100644 --- a/src/ServerPrivate.hh +++ b/src/ServerPrivate.hh @@ -122,9 +122,9 @@ namespace ignition /// string and return value of false will be used if the resource could /// not be found. /// - /// Fuel will be checked and then the GZ_GAZEBO_RESOURCE_PATH environment - /// variable paths. This service will not check for files relative to - /// working directory of the Gazebo server. + /// Fuel will be checked and then paths from IGN_GAZEBO_RESOURCE_PATH and + /// GZ_SIM_RESOURCE_PATH environment variables. This service will not + /// check for files relative to working directory of the Gazebo server. /// /// \param[in] _req Request filled with a resource URI to resolve. /// Example values could be: diff --git a/src/Server_TEST.cc b/src/Server_TEST.cc index 62b17a614f..40f3d996ec 100644 --- a/src/Server_TEST.cc +++ b/src/Server_TEST.cc @@ -40,6 +40,7 @@ #include "plugins/MockSystem.hh" #include "../test/helpers/Relay.hh" #include "../test/helpers/EnvTestFixture.hh" +#include "../test/helpers/Util.hh" using namespace gz; using namespace gz::sim; @@ -263,14 +264,10 @@ TEST_P(ServerFixture, IGN_UTILS_TEST_DISABLED_ON_WIN32(ServerConfigRealPlugin)) msgs::StringMsg rep; bool result{false}; bool executed{false}; - int sleep{0}; - int maxSleep{30}; - while (!executed && sleep < maxSleep) - { - igndbg << "Requesting /test/service" << std::endl; - executed = node.Request("/test/service", 100, rep, result); - sleep++; - } + const std::string service = "/test/service"; + ASSERT_TRUE(test::waitForService(node, service)); + igndbg << "Requesting " << service << std::endl; + executed = node.Request(service, 1000, rep, result); EXPECT_TRUE(executed); EXPECT_TRUE(result); EXPECT_EQ("TestModelSystem", rep.data()); @@ -315,14 +312,10 @@ TEST_P(ServerFixture, msgs::StringMsg rep; bool result{false}; bool executed{false}; - int sleep{0}; - int maxSleep{30}; - while (!executed && sleep < maxSleep) - { - igndbg << "Requesting /test/service/sensor" << std::endl; - executed = node.Request("/test/service/sensor", 100, rep, result); - sleep++; - } + const std::string service ="/test/service/sensor"; + ASSERT_TRUE(test::waitForService(node, service)); + igndbg << "Requesting " << service << std::endl; + executed = node.Request(service, 1000, rep, result); EXPECT_TRUE(executed); EXPECT_TRUE(result); EXPECT_EQ("TestSensorSystem", rep.data()); @@ -754,16 +747,12 @@ TEST_P(ServerFixture, ServerControlStop) msgs::Boolean res; bool result{false}; bool executed{false}; - int sleep{0}; - int maxSleep{30}; + const std::string service = "/server_control"; + ASSERT_TRUE(test::waitForService(node, service)); // first, call with stop = false; the server should keep running - while (!executed && sleep < maxSleep) - { - igndbg << "Requesting /server_control" << std::endl; - executed = node.Request("/server_control", req, 100, res, result); - sleep++; - } + igndbg << "Requesting " << service << std::endl; + executed = node.Request(service, req, 1000, res, result); EXPECT_TRUE(executed); EXPECT_TRUE(result); EXPECT_FALSE(res.data()); @@ -776,8 +765,8 @@ TEST_P(ServerFixture, ServerControlStop) // now call with stop = true; the server should stop req.set_stop(true); - igndbg << "Requesting /server_control" << std::endl; - executed = node.Request("/server_control", req, 100, res, result); + igndbg << "Requesting " << service << std::endl; + executed = node.Request(service, req, 1000, res, result); EXPECT_TRUE(executed); EXPECT_TRUE(result); @@ -912,13 +901,12 @@ TEST_P(ServerFixture, Seed) } ///////////////////////////////////////////////// -TEST_P(ServerFixture, IGN_UTILS_TEST_DISABLED_ON_WIN32(ResourcePath)) +void testResourcePaths(const std::string &_envVariable) { - common::setenv("IGN_GAZEBO_RESOURCE_PATH", + common::setenv(_envVariable, (common::joinPaths(PROJECT_SOURCE_PATH, "test", "worlds:") + common::joinPaths(PROJECT_SOURCE_PATH, "test", "worlds", "models")).c_str()); - ServerConfig serverConfig; serverConfig.SetSdfFile("resource_paths.sdf"); gz::sim::Server server(serverConfig); @@ -998,16 +986,32 @@ TEST_P(ServerFixture, IGN_UTILS_TEST_DISABLED_ON_WIN32(ResourcePath)) EXPECT_TRUE(server.HasEntity("scheme_resource_uri")); EXPECT_TRUE(server.HasEntity("the_link")); EXPECT_TRUE(server.HasEntity("the_visual")); + common::unsetenv(_envVariable); } ///////////////////////////////////////////////// -TEST_P(ServerFixture, GetResourcePaths) +TEST_P(ServerFixture, IGN_UTILS_TEST_DISABLED_ON_WIN32(ResourcePath)) { - common::setenv("IGN_GAZEBO_RESOURCE_PATH", + SCOPED_TRACE("ResourcePaths"); + testResourcePaths("IGN_GAZEBO_RESOURCE_PATH"); +} + +///////////////////////////////////////////////// +TEST_P(ServerFixture, + IGN_UTILS_TEST_DISABLED_ON_WIN32(ResourcePathGzSimCompatibility)) +{ + common::unsetenv("IGN_GAZEBO_RESOURCE_PATH"); + SCOPED_TRACE("ResourcePathGzSimCompatibility"); + testResourcePaths("GZ_SIM_RESOURCE_PATH"); +} + +///////////////////////////////////////////////// +void testGetResourcePaths(const std::string &_envVariable) +{ + common::setenv(_envVariable, std::string("/tmp/some/path") + common::SystemPaths::Delimiter() + std::string("/home/user/another_path")); - ServerConfig serverConfig; gz::sim::Server server(serverConfig); @@ -1017,19 +1021,31 @@ TEST_P(ServerFixture, GetResourcePaths) msgs::StringMsg_V res; bool result{false}; bool executed{false}; - int sleep{0}; - int maxSleep{30}; - while (!executed && sleep < maxSleep) - { - igndbg << "Requesting /gazebo/resource_paths/get" << std::endl; - executed = node.Request("/gazebo/resource_paths/get", 100, res, result); - sleep++; - } + const std::string service = "/gazebo/resource_paths/get"; + ASSERT_TRUE(test::waitForService(node, service)); + igndbg << "Requesting " << service << std::endl; + executed = node.Request(service, 1000, res, result); EXPECT_TRUE(executed); EXPECT_TRUE(result); EXPECT_EQ(2, res.data_size()); EXPECT_EQ("/tmp/some/path", res.data(0)); EXPECT_EQ("/home/user/another_path", res.data(1)); + common::unsetenv(_envVariable); +} + +///////////////////////////////////////////////// +TEST_P(ServerFixture, GetResourcePaths) +{ + SCOPED_TRACE("GetResourcePaths"); + testGetResourcePaths("IGN_GAZEBO_RESOURCE_PATH"); +} + +///////////////////////////////////////////////// +TEST_P(ServerFixture, GetResourcePathsGzSimCompatibility) +{ + common::unsetenv("IGN_GAZEBO_RESOURCE_PATH"); + SCOPED_TRACE("GetResourcePathsGzSimCompatibility"); + testGetResourcePaths("GZ_SIM_RESOURCE_PATH"); } ///////////////////////////////////////////////// @@ -1071,7 +1087,9 @@ TEST_P(ServerFixture, AddResourcePaths) common::SystemPaths::Delimiter() + std::string("/tmp/even_more")); req.add_data("/tmp/some/path"); - bool executed = node.Request("/gazebo/resource_paths/add", req); + const std::string service = "/gazebo/resource_paths/add"; + ASSERT_TRUE(test::waitForService(node, service)); + bool executed = node.Request(service, req); EXPECT_TRUE(executed); int sleep{0}; @@ -1125,17 +1143,12 @@ TEST_P(ServerFixture, ResolveResourcePaths) msgs::StringMsg req, res; bool result{false}; bool executed{false}; - int sleep{0}; - int maxSleep{30}; req.set_data(_uri); - while (!executed && sleep < maxSleep) - { - igndbg << "Requesting /gazebo/resource_paths/resolve" << std::endl; - executed = node.Request("/gazebo/resource_paths/resolve", req, 100, - res, result); - sleep++; - } + const std::string service ="/gazebo/resource_paths/resolve"; + ASSERT_TRUE(test::waitForService(node, service)); + igndbg << "Requesting " << service << std::endl; + executed = node.Request(service, req, 1000, res, result); EXPECT_TRUE(executed); EXPECT_EQ(_found, result); EXPECT_EQ(_expected, res.data()) << "Expected[" << _expected diff --git a/src/SystemLoader.cc b/src/SystemLoader.cc index 763c656fd2..fff885dbaa 100644 --- a/src/SystemLoader.cc +++ b/src/SystemLoader.cc @@ -46,6 +46,14 @@ class ignition::gazebo::SystemLoaderPrivate gz::common::SystemPaths systemPaths; systemPaths.SetPluginPathEnv(pluginPathEnv); + // Also add GZ_SYSTEM_SIM_PLUGIN_PATH for compatibility with Garden and + // later. + for (const auto &path : + common::SystemPaths::PathsFromEnv(this->pluginPathEnvGzSim)) + { + systemPaths.AddPluginPaths(path); + } + for (const auto &path : this->systemPluginPaths) systemPaths.AddPluginPaths(path); @@ -62,6 +70,14 @@ class ignition::gazebo::SystemLoaderPrivate public: bool InstantiateSystemPlugin(const sdf::Plugin &_sdfPlugin, ignition::plugin::PluginPtr &_gzPlugin) { + const std::string gzSimPrefix{"gz-sim"}; + auto filename = _sdfPlugin.Filename(); + auto pos = filename.find(gzSimPrefix); + if (pos != std::string::npos) + { + filename.replace(pos, gzSimPrefix.size(), "ignition-gazebo"); + } + std::list paths = this->PluginPaths(); common::SystemPaths systemPaths; for (const auto &p : paths) @@ -69,7 +85,7 @@ class ignition::gazebo::SystemLoaderPrivate systemPaths.AddPluginPaths(p); } - auto pathToLib = systemPaths.FindSharedLibrary(_sdfPlugin.Filename()); + auto pathToLib = systemPaths.FindSharedLibrary(filename); if (pathToLib.empty()) { // We assume gz::sim corresponds to the levels feature @@ -125,8 +141,11 @@ class ignition::gazebo::SystemLoaderPrivate return true; } - // Default plugin search path environment variable + // Default plugin search path environment variable. Prefer + // GZ_SYSTEM_SIM_PLUGIN_PATH for compatibility with future versions of Gazebo. public: std::string pluginPathEnv{"IGN_GAZEBO_SYSTEM_PLUGIN_PATH"}; + // Default plugin search path environment variable + public: std::string pluginPathEnvGzSim{"GZ_SIM_SYSTEM_PLUGIN_PATH"}; /// \brief Plugin loader instace public: gz::plugin::Loader loader; diff --git a/src/SystemLoader_TEST.cc b/src/SystemLoader_TEST.cc index 8658176e0a..05871a843e 100644 --- a/src/SystemLoader_TEST.cc +++ b/src/SystemLoader_TEST.cc @@ -30,29 +30,46 @@ using namespace gz; using namespace sim; +#ifdef _WIN32 + constexpr const char *kPluginDir = "bin"; +#else + constexpr const char *kPluginDir = "lib"; +#endif ///////////////////////////////////////////////// TEST(SystemLoader, Constructor) { - gz::sim::SystemLoader sm; - // Add test plugin to path (referenced in config) auto testBuildPath = common::joinPaths( - std::string(PROJECT_BINARY_PATH), "lib"); - sm.AddSystemPluginPath(testBuildPath); + std::string(PROJECT_BINARY_PATH), kPluginDir); sdf::Root root; root.LoadSdfString(std::string("" - "" - "" + "") + + "" + "" + "" + "" + "" + "" ""); - + ASSERT_NE(root.WorldByIndex(0), nullptr); auto worldElem = root.WorldByIndex(0)->Element(); if (worldElem->HasElement("plugin")) { sdf::ElementPtr pluginElem = worldElem->GetElement("plugin"); while (pluginElem) { + gz::sim::SystemLoader sm; + sm.AddSystemPluginPath(testBuildPath); sdf::Plugin plugin; plugin.Load(pluginElem); auto system = sm.LoadPlugin(plugin); @@ -61,6 +78,48 @@ TEST(SystemLoader, Constructor) } } } +///////////////////////////////////////////////// +TEST(SystemLoader, FromPluginPathEnv) +{ + sdf::Root root; + root.LoadSdfString(R"( + + + + + )"); + + ASSERT_NE(root.WorldCount(), 0u); + auto world = root.WorldByIndex(0); + ASSERT_TRUE(world != nullptr); + ASSERT_FALSE(world->Plugins().empty()); + auto plugin = world->Plugins()[0]; + + { + gz::sim::SystemLoader sm; + auto system = sm.LoadPlugin(plugin); + EXPECT_FALSE(system.has_value()); + } + + const auto libPath = common::joinPaths(PROJECT_BINARY_PATH, kPluginDir); + + { + common::setenv("IGN_GAZEBO_SYSTEM_PLUGIN_PATH", libPath.c_str()); + + gz::sim::SystemLoader sm; + auto system = sm.LoadPlugin(plugin); + EXPECT_TRUE(system.has_value()); + EXPECT_TRUE(common::unsetenv("IGN_GAZEBO_SYSTEM_PLUGIN_PATH")); + } + { + common::setenv("GZ_SIM_SYSTEM_PLUGIN_PATH", libPath.c_str()); + + gz::sim::SystemLoader sm; + auto system = sm.LoadPlugin(plugin); + EXPECT_TRUE(system.has_value()); + EXPECT_TRUE(common::unsetenv("GZ_SIM_SYSTEM_PLUGIN_PATH")); + } +} TEST(SystemLoader, EmptyNames) { diff --git a/src/Util.cc b/src/Util.cc index e3d198a9cc..ec958353e8 100644 --- a/src/Util.cc +++ b/src/Util.cc @@ -401,15 +401,35 @@ std::string asFullPath(const std::string &_uri, const std::string &_filePath) return common::joinPaths(path, uri); } +namespace +{ ////////////////////////////////////////////////// -std::vector resourcePaths() +/// \brief Helper function to extract paths form an environment variable +/// refactored from `resourcePaths` below. +/// common::SystemPaths::PathsFromEnv is available, but it's behavior is +/// slightly different from this in that it adds trailing `/` to the end of a +/// path if it doesn't have it already. +std::vector extractPathsFromEnv(const std::string &_envVar) { - std::vector gzPaths; - char *gzPathCStr = std::getenv(kResourcePathEnv.c_str()); - if (gzPathCStr && *gzPathCStr != '\0') + std::vector pathsFromEnv; + char *pathFromEnvCStr = std::getenv(_envVar.c_str()); + if (pathFromEnvCStr && *pathFromEnvCStr != '\0') { - gzPaths = common::Split(gzPathCStr, common::SystemPaths::Delimiter()); + pathsFromEnv = + common::Split(pathFromEnvCStr, common::SystemPaths::Delimiter()); } + return pathsFromEnv; +} +} // namespace + +////////////////////////////////////////////////// +std::vector resourcePaths() +{ + auto gzPaths = extractPathsFromEnv(kResourcePathEnv); + const auto gzSimResourcePaths = extractPathsFromEnv(kResourcePathEnvGzSim); + + gzPaths.insert(gzPaths.end(), gzSimResourcePaths.begin(), + gzSimResourcePaths.end()); gzPaths.erase(std::remove_if(gzPaths.begin(), gzPaths.end(), [](const std::string &_path) @@ -441,35 +461,31 @@ void addResourcePaths(const std::vector &_paths) } // Gazebo resource paths - std::vector gzPaths; - char *gzPathCStr = std::getenv(kResourcePathEnv.c_str()); - if (gzPathCStr && *gzPathCStr != '\0') - { - gzPaths = common::Split(gzPathCStr, common::SystemPaths::Delimiter()); - } + auto gzPaths = extractPathsFromEnv(kResourcePathEnv); - // Add new paths to gzPaths - for (const auto &path : _paths) + auto addUniquePaths = [](std::vector &_container, + const std::vector _pathsToAdd) { - if (std::find(gzPaths.begin(), gzPaths.end(), path) == gzPaths.end()) + for (const auto &path : _pathsToAdd) { - gzPaths.push_back(path); + if (std::find(_container.begin(), _container.end(), path) == + _container.end()) + { + _container.push_back(path); + } } - } + }; + // Add new paths to gzPaths + addUniquePaths(gzPaths, _paths); // Append Gz paths to SDF / Ign paths - for (const auto &path : gzPaths) - { - if (std::find(sdfPaths.begin(), sdfPaths.end(), path) == sdfPaths.end()) - { - sdfPaths.push_back(path); - } + addUniquePaths(sdfPaths, gzPaths); + addUniquePaths(ignPaths, gzPaths); - if (std::find(ignPaths.begin(), ignPaths.end(), path) == ignPaths.end()) - { - ignPaths.push_back(path); - } - } + // Also append paths from GZ_SIM_RESOURCE_PATH + const auto gzSimResourcePaths = extractPathsFromEnv(kResourcePathEnvGzSim); + addUniquePaths(sdfPaths, gzSimResourcePaths); + addUniquePaths(ignPaths, gzSimResourcePaths); // Update the vars std::string sdfPathsStr; @@ -727,6 +743,11 @@ std::string resolveSdfWorldFile(const std::string &_sdfFile, // Worlds from environment variable systemPaths.SetFilePathEnv(kResourcePathEnv); + // Also add paths from GZ_SIM_RESOURCE_PATH + for (const auto &path : extractPathsFromEnv(kResourcePathEnvGzSim)) + { + systemPaths.AddFilePaths(path); + } // Worlds installed with ign-gazebo systemPaths.AddFilePaths(IGN_GAZEBO_WORLD_INSTALL_DIR); diff --git a/src/cmd/cmdgazebo.rb.in b/src/cmd/cmdgazebo.rb.in index b2ae8b9566..07d36a7bb2 100755 --- a/src/cmd/cmdgazebo.rb.in +++ b/src/cmd/cmdgazebo.rb.in @@ -160,9 +160,14 @@ COMMANDS = { 'gazebo' => "Environment variables: \n"\ " IGN_GAZEBO_RESOURCE_PATH Colon separated paths used to locate \n"\ " resources such as worlds and models. \n\n"\ + " GZ_SIM_RESOURCE_PATH Colon separated paths used to locate \n"\ + " resources such as worlds and models. \n\n"\ " IGN_GAZEBO_SYSTEM_PLUGIN_PATH Colon separated paths used to \n"\ " locate system plugins. \n\n"\ + " GZ_SIM_SYSTEM_PLUGIN_PATH Colon separated paths used to \n"\ + " locate system plugins. \n\n"\ " IGN_GAZEBO_SERVER_CONFIG_PATH Path to server configuration file. \n\n"\ + " GZ_SIM_SERVER_CONFIG_PATH Path to server configuration file. \n\n"\ " IGN_GUI_PLUGIN_PATH Colon separated paths used to locate GUI \n"\ " plugins. \n\n"\ " GZ_GUI_RESOURCE_PATH Colon separated paths used to locate GUI \n"\ @@ -412,7 +417,7 @@ has properly set the DYLD_LIBRARY_PATH environment variables." # If not, then first check the IGN_GAZEBO_RESOURCE_PATH environment # variable, then the configuration path from the launch library. else - resourcePathEnv = ENV['IGN_GAZEBO_RESOURCE_PATH'] + resourcePathEnv = "#{ENV['IGN_GAZEBO_RESOURCE_PATH']}:#{ENV['GZ_SIM_RESOURCE_PATH']}" if !resourcePathEnv.nil? resourcePaths = resourcePathEnv.split(':') for resourcePath in resourcePaths diff --git a/src/gz_TEST.cc b/src/gz_TEST.cc index 78aecdb101..d04b414876 100644 --- a/src/gz_TEST.cc +++ b/src/gz_TEST.cc @@ -159,12 +159,18 @@ TEST(CmdLine, IGN_UTILS_TEST_DISABLED_ON_WIN32(ResourcePath)) // Correct path auto path = std::string("IGN_GAZEBO_RESOURCE_PATH=") + - PROJECT_SOURCE_PATH + "/test/worlds "; + gz::common::joinPaths(PROJECT_SOURCE_PATH, "test", "worlds "); output = customExecStr(path + cmd); EXPECT_EQ(output.find("Unable to find file plugins.sdf"), std::string::npos) << output; + path = std::string("GZ_SIM_RESOURCE_PATH=") + + gz::common::joinPaths(PROJECT_SOURCE_PATH, "test", "worlds "); + output = customExecStr(path + cmd); + EXPECT_EQ(output.find("Unable to find file plugins.sdf"), std::string::npos) + << output; + // Several paths path = std::string("IGN_GAZEBO_RESOURCE_PATH=banana:") + PROJECT_SOURCE_PATH + "/test/worlds:orange "; @@ -172,6 +178,25 @@ TEST(CmdLine, IGN_UTILS_TEST_DISABLED_ON_WIN32(ResourcePath)) output = customExecStr(path + cmd); EXPECT_EQ(output.find("Unable to find file plugins.sdf"), std::string::npos) << output; + + // Test nested models + // Use a direct path to the input file. Using a file name that has to be + // resolved interacts with how resource environment variables are processed + // and so will have different behavior than when a direct path is provided. + cmd = kIgnCommand + " -s -r -v 4 --iterations 1 " + + gz::common::joinPaths(PROJECT_SOURCE_PATH, "test", "worlds", + "include_nested_models.sdf"); + output = customExecStr(cmd); + EXPECT_NE(output.find("Unable to find"), std::string::npos) << output; + + std::string pathValue = + gz::common::joinPaths(PROJECT_SOURCE_PATH, "test", "worlds", "models"); + + output = customExecStr("IGN_GAZEBO_RESOURCE_PATH=" + pathValue + " " + cmd); + EXPECT_EQ(output.find("Unable to find"), std::string::npos) << output; + + output = customExecStr("GZ_SIM_RESOURCE_PATH=" + pathValue + " " + cmd); + EXPECT_EQ(output.find("Unable to find"), std::string::npos) << output; } ////////////////////////////////////////////////// diff --git a/src/rendering/RenderUtil.cc b/src/rendering/RenderUtil.cc index 2f92ef3c50..cf050e5716 100644 --- a/src/rendering/RenderUtil.cc +++ b/src/rendering/RenderUtil.cc @@ -2555,6 +2555,9 @@ void RenderUtil::InitRenderEnginePluginPaths() common::SystemPaths pluginPath; pluginPath.SetPluginPathEnv(kRenderPluginPathEnv); rendering::setPluginPaths(pluginPath.PluginPaths()); + + rendering::setPluginPaths( + common::SystemPaths::PathsFromEnv(kRenderPluginPathEnvGzSim)); } ///////////////////////////////////////////////// diff --git a/src/systems/particle_emitter/ParticleEmitter.cc b/src/systems/particle_emitter/ParticleEmitter.cc index fae477f3f0..7aa6bbcc82 100644 --- a/src/systems/particle_emitter/ParticleEmitter.cc +++ b/src/systems/particle_emitter/ParticleEmitter.cc @@ -250,9 +250,8 @@ void ParticleEmitter::Configure(const Entity &_entity, auto colorRangeImagePath = _sdf->Get("color_range_image"); auto path = asFullPath(colorRangeImagePath, modelPath.value()); - common::SystemPaths systemPaths; - systemPaths.SetFilePathEnv(kResourcePathEnv); - auto absolutePath = systemPaths.FindFile(path); + const std::string absolutePath = + common::SystemPaths::LocateLocalFile(path, gazebo::resourcePaths()); this->dataPtr->emitter.mutable_color_range_image()->set_data( absolutePath); diff --git a/test/helpers/Util.hh b/test/helpers/Util.hh new file mode 100644 index 0000000000..d4e5b78df2 --- /dev/null +++ b/test/helpers/Util.hh @@ -0,0 +1,49 @@ +/* + * 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 +#include + +#include + +namespace ignition::gazebo::test +{ +/// \brief Wait until a service becomes available. +/// See https://github.com/gazebosim/gz-transport/issues/468 for why this might +/// be necessary before make a service request. +/// \param[in] _node Transport Node to use +/// \param[in] _service Name of service to wait for +/// \param[in] _timeoutS Time out in seconds +bool waitForService(const transport::Node &_node, const std::string &_service, + int _timeoutS = 5) +{ + int curSleep = 0; + while (curSleep < _timeoutS) + { + std::vector publishers; + if (_node.ServiceInfo(_service, publishers)) + { + return true; + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + ++curSleep; + } + return false; +} +} // namespace gz::sim::test diff --git a/tutorials/migration_sdf.md b/tutorials/migration_sdf.md index ee3a4b39e1..2c6483dfe1 100644 --- a/tutorials/migration_sdf.md +++ b/tutorials/migration_sdf.md @@ -146,7 +146,7 @@ Each simulator uses a different environment variable: * `GAZEBO_MODEL_PATH` for models * `GAZEBO_RESOURCE_PATH` for worlds and some rendering resources * Ignition Gazebo: - * `IGN_GAZEBO_RESOURCE_PATH` for worlds, models and other resources + * `IGN_GAZEBO_RESOURCE_PATH` or `GZ_SIM_RESOURCE_PATH` for worlds, models and other resources For example, if you have the file structure above, you can set the environment variable to `/home/username/models`: @@ -236,7 +236,7 @@ where that plugin is located. The variables are different for each simulator: * Gazebo classic: * `GAZEBO_PLUGIN_PATH` for all plugin types. * Ignition Gazebo: - * `IGN_GAZEBO_SYSTEM_PLUGIN_PATH` for Ignition Gazebo systems (world, model, + * `IGN_GAZEBO_SYSTEM_PLUGIN_PATH` or `GZ_SIM_SYSTEM_PLUGIN_PATH` for Ignition Gazebo systems (world, model, sensor and visual plugins). * `IGN_GUI_PLUGIN_PATH` for GUI plugins. diff --git a/tutorials/resources.md b/tutorials/resources.md index c7e03d3439..936082874d 100644 --- a/tutorials/resources.md +++ b/tutorials/resources.md @@ -38,7 +38,7 @@ System plugins may be loaded through: Ignition will look for system plugins on the following paths, in order: -1. All paths on the `IGN_GAZEBO_SYSTEM_PLUGIN_PATH` environment variable +1. All paths on the `IGN_GAZEBO_SYSTEM_PLUGIN_PATH` or `GZ_SIM_SYSTEM_PLUGIN_PATH` environment variables 2. `$HOME/.ignition/gazebo/plugins` 3. [Systems that are installed with Ignition Gazebo](https://gazebosim.org/api/gazebo/6/namespace gz_1_1gazebo_1_1systems.html) @@ -116,7 +116,7 @@ Top-level entities such as models, lights and actors may be loaded through: Ignition will look for URIs (path / URL) in the following, in order: -1. All paths on the `IGN_GAZEBO_RESOURCE_PATH`\* environment variable (if +1. All paths on the `IGN_GAZEBO_RESOURCE_PATH` and `GZ_SIM_RESOURCE_PATH`\* environment variables (if path is URI, scheme is stripped) 2. Current running path / absolute path 3. [Ignition Fuel](https://app.gazebosim.org/fuel/models) @@ -138,7 +138,7 @@ Mesh files may be loaded through: Ignition will look for URIs (path / URL) in the following, in order: 1. Current running path / absolute path -2. All paths on the `IGN_GAZEBO_RESOURCE_PATH`\* environment variable (if path +2. All paths on the `IGN_GAZEBO_RESOURCE_PATH` and `GZ_SIM_RESOURCE_PATH`\* environment variables (if path is URI, scheme is stripped) \* The `IGN_FILE_PATH` environment variable also works in some scenarios, but diff --git a/tutorials/server_config.md b/tutorials/server_config.md index 70bf7f1426..b6d5e6fe01 100644 --- a/tutorials/server_config.md +++ b/tutorials/server_config.md @@ -13,13 +13,13 @@ a simulation. There are a few places where the plugins can be defined: 1. `` elements inside an SDF file. -2. File path defined by the `IGN_GAZEBO_SERVER_CONFIG_PATH` environment variable. +2. File path defined by the `IGN_GAZEBO_SERVER_CONFIG_PATH` or `GZ_SIM_SERVER_CONFIG_PATH` environment variables. 3. The default configuration file at `$HOME/.ignition/gazebo/<#>/server.config` \*, where `<#>` is Gazebo's major version. Each of the items above takes precedence over the ones below it. For example, if a the SDF file has any `` elements, then the -`IGN_GAZEBO_SERVER_CONFIG_PATH` variable is ignored. And the default configuration +`IGN_GAZEBO_SERVER_CONFIG_PATH` and `GZ_SIM_SERVER_CONFIG_PATH` variables are ignored. And the default configuration file is only loaded if no plugins are passed through the SDF file or the environment variable.