Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Add ROSEnv generator integration for ROS2 builds #17110

Merged
merged 18 commits into from
Oct 29, 2024
3 changes: 2 additions & 1 deletion conan/internal/api/install/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"SConsDeps": "conan.tools.scons",
"QbsDeps": "conan.tools.qbs",
"QbsProfile": "conan.tools.qbs",
"CPSDeps": "conan.tools.cps"
"CPSDeps": "conan.tools.cps",
"ROSEnv": "conan.tools.ros"
}


Expand Down
1 change: 1 addition & 0 deletions conan/tools/ros/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from conan.tools.ros.rosenv import ROSEnv
49 changes: 49 additions & 0 deletions conan/tools/ros/rosenv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import os
from conan.api.output import Color
from conan.tools.env import VirtualBuildEnv, Environment
from conan.tools.files import save


class ROSEnv:
"""
Generator to serve as integration for Robot Operating System 2 development workspaces.
It generates a conanrosenv.sh file that when sources sets variables so the Conan
dependencies are found by CMake and the run environment is also set.

IMPORTANT: This generator should be used together with CMakeDeps and CMakeToolchain generators.
"""

def __init__(self, conanfile):
self._conanfile = conanfile
self._variables = {}
danimtb marked this conversation as resolved.
Show resolved Hide resolved
self.variables = {}
self._virtualbuildenv = VirtualBuildEnv(self._conanfile, auto_generate=True)
self._virtualbuildenv.basename = "conanrosenv"
self._rosenv_wrapper = "conanrosenv.sh"

def generate(self):
output_folder = self._conanfile.generators_folder
self._variables["CMAKE_TOOLCHAIN_FILE"] = os.path.join(output_folder, "conan_toolchain.cmake")
build_type = self._conanfile.settings.get_safe("build_type")
if build_type:
self._variables["CMAKE_BUILD_TYPE"] = build_type
self.variables.update(self._variables)

# Add ROS required variables to VirtualBuildEnv
rosbuildenv = Environment()
for k, v in self.variables.items():
rosbuildenv.define(k, v)
self._virtualbuildenv._buildenv = rosbuildenv
danimtb marked this conversation as resolved.
Show resolved Hide resolved
self._virtualbuildenv.generate()

# Generate conanrosenv.sh script wrapper that calls conanbuild.sh and conanrun.sh
# TODO: Windows .bat/.ps1 files still not supported for the wrapper
conanbuild_path = os.path.join(self._conanfile.generators_folder, "conanbuild.sh")
conanrun_path = os.path.join(self._conanfile.generators_folder, "conanrun.sh")
rosenv_wrapper_content = [f". \"{conanbuild_path}\"", f". \"{conanrun_path}\""]
conanrosenv_path = os.path.join(self._conanfile.generators_folder, self._rosenv_wrapper)
save(self._conanfile, conanrosenv_path, "\n".join(rosenv_wrapper_content))
danimtb marked this conversation as resolved.
Show resolved Hide resolved

msg = f"Generated ROSEnv Conan file: conanrosenv.sh\n" + \
f"Use 'source {conanrosenv_path}' to set the ROSEnv Conan before 'colcon build'"
self._conanfile.output.info(msg, fg=Color.CYAN)
Empty file.
74 changes: 74 additions & 0 deletions test/integration/tools/ros/test_rosenv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import os
import textwrap
import platform

import pytest

from conan.test.assets.genconanfile import GenConanfile
from conan.test.utils.tools import TestClient


@pytest.mark.skipif(platform.system() == "Windows", reason="Uses UNIX commands")
def test_rosenv():
"""
Test that the ROSEnv generator generates the environment files and that the environment variables are correctly set
"""
client = TestClient()
conanfile3 = textwrap.dedent('''
[requires]
[generators]
CMakeDeps
CMakeToolchain
ROSEnv
''')
client.save({
"conanfile3.txt": conanfile3
})

client.run("install conanfile3.txt --output-folder install/conan")
assert "Generated ROSEnv Conan file: conanrosenv.sh" in client.out
conanrosenv_path = os.path.join(client.current_folder, "install", "conan", "conanrosenv.sh")
assert os.path.exists(conanrosenv_path)
client.run_command(f". \"{conanrosenv_path}\" && env")
toolchain_path = os.path.join(client.current_folder, "install", "conan", "conan_toolchain.cmake")
assert f"CMAKE_TOOLCHAIN_FILE={toolchain_path}" in client.out
assert "CMAKE_BUILD_TYPE=Release" in client.out


@pytest.mark.skipif(platform.system() == "Windows", reason="Uses UNIX commands")
def test_rosenv_shared_libraries():
"""
Test that the library paths env vars are set up correctly so that the executables built with
colcon can found the shared libraries of conan packages
"""
client = TestClient()
c1 = GenConanfile("lib1", "1.0").with_shared_option(False).with_package_file("lib/lib1", "lib-content")
c2 = GenConanfile("lib2", "1.0").with_shared_option(False).with_requirement("lib1/1.0").with_package_file("lib/lib2", "lib-content")
c3 = textwrap.dedent('''
[requires]
lib2/1.0
[generators]
CMakeDeps
CMakeToolchain
ROSEnv
''')
client.save({
"conanfile1.py": c1,
"conanfile2.py": c2,
"conanfile3.txt": c3
})

client.run("create conanfile1.py -o *:shared=True")
client.run("create conanfile2.py -o *:shared=True")
client.run("install conanfile3.txt -o *:shared=True --output-folder install/conan")
conanrosenv_path = os.path.join(client.current_folder, "install", "conan", "conanrosenv.sh")
client.run_command(f". \"{conanrosenv_path}\" && env")
environment_content = client.out
client.run(
"cache path lib1/1.0#58723f478a96866dcbd9456d8eefd7c4:1744785cb24e3bdca70e27041dc5abd20476f947")
lib1_lib_path = os.path.join(client.out.strip(), "lib")
assert lib1_lib_path in environment_content
client.run(
"cache path lib2/1.0#4b7a6063ba107d770458ce10385beb52:5c3c2e56259489f7ffbc8e494921eda4b747ef21")
lib2_lib_path = os.path.join(client.out.strip(), "lib")
assert lib2_lib_path in environment_content