0
$\begingroup$

Background

  • Upstream pkg (called rclcpp_sample) provides a library-a.
  • Downstream pkg (called rclcpp_child_sample) provides an executable that depends on library-a.
  • Both are ament-packages (i.e. written within ament framework), to be built with colcon.

To avoid a persistent linker error (very similar to robotics.stackexchange.com#23995 but I wouldn't say the same), the solution I came up with was to target_link_libraries the target that the upstream pkg exports.

target_link_libraries(${app_derived} rclcpp_sample::rclcpp_sample)

Questions

  • Q1. Why ament_target_dependencies can't link to a resource in another ament package?

    Common error I got was:
    # colcon build --packages-select rclcpp_sample rclcpp_child_sample
    Starting >>> rclcpp_sample
    Finished <<< rclcpp_sample [1.46s]                    
    Starting >>> rclcpp_child_sample
    --- stderr: rclcpp_child_sample                              
    /usr/bin/ld: CMakeFiles/app_derived.dir/src/exec.cpp.o: in function `void __gnu_cxx::new_allocator<rclcpp_sample::DerivedModule>::construct<rclcpp_sample::DerivedModule, char const (&) [9]>(rclcpp_sample::DerivedModule*, char const (&) [9])':
    exec.cpp:(.text._ZN9__gnu_cxx13new_allocatorIN13rclcpp_sample13DerivedModuleEE9constructIS2_JRA9_KcEEEvPT_DpOT0_[_ZN9__gnu_cxx13new_allocatorIN13rclcpp_sample13DerivedModuleEE9constructIS2_JRA9_KcEEEvPT_DpOT0_]+0x79): undefined reference to `rclcpp_sample::DerivedModule::DerivedModule(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)'
    collect2: error: ld returned 1 exit status
    make[2]: *** [CMakeFiles/app_derived.dir/build.make:132: app_derived] Error 1
    make[1]: *** [CMakeFiles/Makefile2:78: CMakeFiles/app_derived.dir/all] Error 2
    make: *** [Makefile:141: all] Error 2
    ---
    Failed   <<< rclcpp_child_sample [2.67s, exited with code 2]
    
  • Q2. Why the exported macro is duplicating the element as rclcpp_sample::rclcpp_sample? rclcpp_sample is the namespace I use but the duplication of it is never defined anywhere in the upstream.

More background

Full code of 2 packages can be seen in prooving_grounds!2 if that's quicker.

Upstream pkg

CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(rclcpp_sample)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# BEGIN: From ROS2 tutorial https://docs.ros.org/en/galactic/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Cpp-Publisher-And-Subscriber.html
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 17)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# END From ROS2 tutorial

set(THIS_PACKAGE_INCLUDE_DEPENDS
  ament_cmake
  rclcpp 
)
foreach(Dependency IN ITEMS ${THIS_PACKAGE_INCLUDE_DEPENDS})
  find_package(${Dependency} REQUIRED)
endforeach()

add_library(${PROJECT_NAME} SHARED
  src/impl.cpp
)
target_include_directories(
  ${PROJECT_NAME}
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>)
#target_link_libraries(${PROJECT_NAME} ${THIS_PACKAGE_INCLUDE_DEPENDS})    
ament_target_dependencies(${PROJECT_NAME} ${THIS_PACKAGE_INCLUDE_DEPENDS})
ament_export_include_directories(include)
ament_export_targets(${PROJECT_NAME}Targets HAS_LIBRARY_TARGET)

install(DIRECTORY include/
  DESTINATION include
)
install(
  TARGETS ${PROJECT_NAME}
  EXPORT ${PROJECT_NAME}Targets
  INCLUDES DESTINATION include
  RUNTIME DESTINATION bin
  LIBRARY DESTINATION lib
  ARCHIVE DESTINATION lib
)

ament_package()
base.hpp (defines the parent of another class)
#ifndef RCLCPP_SAMPLE__BASE_HPP_
#define RCLCPP_SAMPLE__BASE_HPP_

#include <ament_index_cpp/get_package_share_directory.hpp>
#include <unistd.h>
#include <rclcpp/rclcpp.hpp>
#include <string>

namespace rclcpp_sample
{

class BaseModules : public rclcpp::Node
{
public:
  BaseModules(const std::string& nodename);
  virtual ~BaseModules();

  virtual bool func_to_be_overridden();
};
}  // namespace rclcpp_sample

#endif  //RCLCPP_SAMPLE__BASE_HPP_
child1.hpp
#ifndef RCLCPP_SAMPLE__CHILD1_HPP_
#define RCLCPP_SAMPLE__CHILD1_HPP_

#include <string>

#include <rclcpp_sample/base.hpp>

namespace rclcpp_sample
{

class DerivedModule : public BaseModules
{
public:
  explicit DerivedModule(const std::string& nodename);
  virtual ~DerivedModule();

  virtual bool func_to_be_overridden();
};
}  // namespace rclcpp_sample

#endif  // RCLCPP_SAMPLE__CHILD1_HPP_
impl.cpp
#include <rclcpp_sample/base.hpp>
#include <rclcpp_sample/child1.hpp>

using namespace rclcpp_sample;

BaseModules::BaseModules(const std::string& nodename) :
    rclcpp::Node(nodename)
{
  RCLCPP_INFO(this->get_logger(), "Initialization.");
}

BaseModules::~BaseModules()
{
  RCLCPP_INFO(this->get_logger(), "Deconstruction.");
}

bool BaseModules::func_to_be_overridden()
{
  return false;
}

DerivedModule::DerivedModule(const std::string& nodename) :
    BaseModules(nodename)
{
  RCLCPP_INFO(get_logger(), "Construtor of DerivedModule class.");
}

DerivedModule::~DerivedModule()
{
}

bool DerivedModule::func_to_be_overridden()
{
  return true;
}

Downstream pkg

CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(rclcpp_child_sample)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

set(THIS_PACKAGE_INCLUDE_DEPENDS
  ament_cmake
  rclcpp
  rclcpp_sample
)
foreach(dep IN ITEMS ${THIS_PACKAGE_INCLUDE_DEPENDS})
  find_package(${dep} REQUIRED)
endforeach()

set(app_derived app_derived)
add_executable(${app_derived} src/exec.cpp)
target_link_libraries(${app_derived} rclcpp_sample::rclcpp_sample)
ament_target_dependencies(${app_derived} ${THIS_PACKAGE_INCLUDE_DEPENDS})
install(TARGETS ${app_derived} RUNTIME DESTINATION lib/${PROJECT_NAME})

ament_package()
exec.cpp
#include <rclcpp/rclcpp.hpp>
#include <string>

#include <rclcpp_sample/base.hpp>
#include <rclcpp_sample/child1.hpp>

int main(int argc, char *argv[])
{
  rclcpp::init(argc, argv);
  auto app = std::make_shared<rclcpp_sample::DerivedModule>("app_execo");
  rclcpp::executors::MultiThreadedExecutor executor;
  executor.add_node(app);
  std::thread([&executor]() { executor.spin(); }).detach();
  RCLCPP_INFO(rclcpp::get_logger("exec_rclcpp_sample"),
              "func_to_be_overridden: %d", app->func_to_be_overridden());
  rclcpp::shutdown();
}
$\endgroup$

1 Answer 1

1
$\begingroup$

First and foremost, I'm not able to reproduce your linking error on Fedora using either Rolling or Humble. I dropped the target_link_libraries call that you referenced in rclcpp_child_sample, but librclcpp_sample.so still appears on the link line (as expected) and the operation succeeds.

Regarding the namespacing, the convention I've seen when namespacing in CMake is <PROJECT_NAME>::<TARGET_NAME>. This is completely disjoint from any C++ namespacing your libraries might have. In your case, the project name and the single target produced in it share the same name so it may seem redundant in such a simple case, but it does make sense for more complex projects.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.