diff --git a/README.md b/README.md index dcea0f2..87445e4 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Conan create ros-kilted](https://github.com/conan-io/ros-conan/actions/workflows/conan-create-ros-kilted.yml/badge.svg)](https://github.com/conan-io/ros-conan/actions/workflows/conan-create-ros-kilted.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) -Conan recipes for building [ROS 2](https://docs.ros.org/) from source. Pull ROS 2 and all its dependencies with a single `conan install`, no `rosdep`, `apt`, `brew` or `choco` required. Works with plain CMake projects and `colcon` workspaces, on Windows, macOS and Linux. +Conan recipes for building [ROS 2](https://docs.ros.org/) from source. Pull ROS 2 and all its dependencies with a single `conan install`, no `rosdep`, `apt`, `brew` or `choco` required. Works with plain CMake projects, `colcon` workspaces, and [Conan workspaces](https://docs.conan.io/2/tutorial/developing_packages/workspaces.html), on Windows, macOS and Linux. --- @@ -89,6 +89,7 @@ pinned to. The Conan version `YYYY.MM.DD` maps 1:1 to the rosdistro tag | ------------------------------------------------------------- | --------------------------------------------------------------------------- | | [`consumer_cmake`](examples/consumer_cmake/readme.md) | Pure-CMake consumer using `rclcpp` via `CMakeDeps`, no `colcon` needed. | | [`consumer_colcon`](examples/consumer_colcon/readme.md) | `colcon` workspace consuming the ROS runtime from Conan via `ROSEnv`. | +| [`conan_workspace`](examples/conan_workspace/readme.md) | Same `src/` layout as `consumer_colcon`, orchestrated with a Conan workspace (CMake only). | | [`pose_estimation`](examples/pose_estimation/readme.md) | `ros-kilted` + `opencv` + `tensorflow-lite` publishing a skeleton overlay. | | [`consumer_desktop`](examples/consumer_desktop/readme.md) | Installs the `desktop` variant and runs its GUI tooling (`rviz2`, `rqt`). | diff --git a/examples/conan_workspace/ci_test_example.py b/examples/conan_workspace/ci_test_example.py new file mode 100644 index 0000000..bb3f863 --- /dev/null +++ b/examples/conan_workspace/ci_test_example.py @@ -0,0 +1,18 @@ +import platform + +from test.examples_tools import VARIANT_ARGS, run + +run( + f"conan workspace build --profile ../../profiles/ros {VARIANT_ARGS} --build=missing" +) + +if platform.system() == "Windows": + run( + r"call .\src\consumer_node\build\generators\conanrun.bat && " + r".\src\consumer_node\build\Release\consumer_node.exe" + ) +else: + run( + ". ./src/consumer_node/build/Release/generators/conanrun.sh && " + "./src/consumer_node/build/Release/consumer_node" + ) diff --git a/examples/conan_workspace/conanws.yml b/examples/conan_workspace/conanws.yml new file mode 100644 index 0000000..15377d9 --- /dev/null +++ b/examples/conan_workspace/conanws.yml @@ -0,0 +1,6 @@ +name: conan_workspace +packages: + - path: src/dummy_lib + ref: dummy_lib/0.1 + - path: src/consumer_node + ref: consumer_node/0.1 diff --git a/examples/conan_workspace/readme.md b/examples/conan_workspace/readme.md new file mode 100644 index 0000000..ae1694b --- /dev/null +++ b/examples/conan_workspace/readme.md @@ -0,0 +1,105 @@ +# conan_workspace + +Same layout as [`consumer_colcon`](../consumer_colcon/readme.md) — two packages under +`src/` — but orchestrated with a [Conan workspace](https://docs.conan.io/2/tutorial/developing_packages/workspaces.html) +instead of `colcon`. There is no `package.xml`, no `ament_cmake`, and no `colcon`: +each package is a plain CMake project with its own `conanfile.py`. Workspace packages +are resolved as [editables](https://docs.conan.io/2/tutorial/developing_packages/editable_packages.html) +and built in dependency order. + +- `dummy_lib` — a static library with no ROS dependencies, used to demonstrate + cross-package linking inside the workspace. +- `consumer_node` — depends on `dummy_lib` and `ros-kilted` (`rclcpp`), prints a + message and starts a short-lived node. + +[← back to main README](../../README.md) + +## Layout + +```text +conan_workspace/ +├── conanws.yml # workspace root + package inventory +└── src/ + ├── dummy_lib/ + │ ├── conanfile.py # replaces package.xml + │ ├── CMakeLists.txt + │ ├── include/ + │ └── src/ + └── consumer_node/ + ├── conanfile.py + ├── CMakeLists.txt + └── src/ +``` + +Conan walks up from the current directory until it finds `conanws.yml` and/or +`conanws.py`; that folder is the workspace root. Paths in `conanws.yml` are +relative to it. + +This example only needs `conanws.yml`. A `conanws.py` is optional Python +customization (`root_conanfile()` for a monolithic super-build, custom +`add`/`clean`/`build_order`, or `get_ref()` when name/version come from +`python_requires`). Orchestrated `conan workspace build` does not use any of +that. + +This example uses the **orchestrated** workspace flow (`conan workspace build`), +which is the closest analogue to `colcon build`: each package is configured and +built on its own, in topological order, with workspace members consumed as +editables. + +Conan workspaces are experimental and subject to breaking changes — see the +[Conan stability notes](https://docs.conan.io/2/introduction.html#stability). + +## How it is wired + +| Piece | Role | +| ----- | ---- | +| [`conanws.yml`](conanws.yml) | Lists `src/dummy_lib` and `src/consumer_node` as workspace packages. | +| [`src/dummy_lib/conanfile.py`](src/dummy_lib/conanfile.py) | CMake library recipe: `CMakeToolchain` + `CMakeDeps`, `cmake_layout()`. | +| [`src/consumer_node/conanfile.py`](src/consumer_node/conanfile.py) | Requires `dummy_lib/0.1` (editable, from this workspace) and `ros-kilted/2026.06.17`. | +| CMake | Plain `find_package(dummy_lib)` / `find_package(rclcpp)` — configs come from Conan, not from sourcing a ROS `setup` script. | + +`conan workspace build` is `conan build` for every workspace package, in the +right order. External dependencies (`ros-kilted` and its graph) are installed +from the cache/remotes; `dummy_lib` is never looked up as a binary because it +is in the workspace. + +## Prerequisites + +- A C++17 compiler. +- CMake ≥ 3.22 (the [profile](../../profiles/ros) tool-requires `cmake/3.29.3`). +- Conan 2.31+ (For `conan workspace build`, as the feature has received recent improvements). +- A Conan remote that exposes `ros-kilted` — see the + [main README](../../README.md#quick-start). + +## Build & run + +From this directory: + +```bash +conan workspace build --profile ../../profiles/ros --build=missing +``` + +Then activate the consumer's Conan run environment and execute the node: + +**Windows (cmd):** + +```bat +call src\consumer_node\build\generators\conanrun.bat +src\consumer_node\build\Release\consumer_node.exe +``` + +**macOS / Linux (bash/zsh):** + +```bash +. ./src/consumer_node/build/Release/generators/conanrun.sh +./src/consumer_node/build/Release/consumer_node +``` + +Expected output (truncated): + +```text +[dummy_lib] hello from library +[INFO] [...] [workspace_consumer_node]: Conan workspace consumer_node: rclcpp linked and node started. +``` + +The exact CI invocation lives in [`ci_test_example.py`](ci_test_example.py). diff --git a/examples/conan_workspace/src/consumer_node/CMakeLists.txt b/examples/conan_workspace/src/consumer_node/CMakeLists.txt new file mode 100644 index 0000000..6997ce2 --- /dev/null +++ b/examples/conan_workspace/src/consumer_node/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.22) +project(consumer_node CXX) + +find_package(dummy_lib REQUIRED) +find_package(rclcpp REQUIRED) + +add_executable(consumer_node src/main.cpp) +target_link_libraries(consumer_node PRIVATE dummy_lib::dummy_lib rclcpp::rclcpp) diff --git a/examples/conan_workspace/src/consumer_node/conanfile.py b/examples/conan_workspace/src/consumer_node/conanfile.py new file mode 100644 index 0000000..594d2e8 --- /dev/null +++ b/examples/conan_workspace/src/consumer_node/conanfile.py @@ -0,0 +1,26 @@ +from conan import ConanFile +from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout + + +class ConsumerNodeConan(ConanFile): + name = "consumer_node" + version = "0.1" + package_type = "application" + settings = "os", "compiler", "build_type", "arch" + exports_sources = "CMakeLists.txt", "src/*" + + def layout(self): + cmake_layout(self) + + def requirements(self): + self.requires("dummy_lib/0.1") + self.requires("ros-kilted/2026.06.17") + + def generate(self): + CMakeDeps(self).generate() + CMakeToolchain(self).generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() diff --git a/examples/conan_workspace/src/consumer_node/src/main.cpp b/examples/conan_workspace/src/consumer_node/src/main.cpp new file mode 100644 index 0000000..e371b01 --- /dev/null +++ b/examples/conan_workspace/src/consumer_node/src/main.cpp @@ -0,0 +1,15 @@ +#include + +#include +#include + +int main(int argc, char ** argv) +{ + dummy_lib::print_hello(); + + rclcpp::init(argc, argv); + auto node = std::make_shared("workspace_consumer_node"); + RCLCPP_INFO(node->get_logger(), "Conan workspace consumer_node: rclcpp linked and node started."); + rclcpp::shutdown(); + return 0; +} diff --git a/examples/conan_workspace/src/dummy_lib/CMakeLists.txt b/examples/conan_workspace/src/dummy_lib/CMakeLists.txt new file mode 100644 index 0000000..7507673 --- /dev/null +++ b/examples/conan_workspace/src/dummy_lib/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.22) +project(dummy_lib CXX) + +add_library(dummy_lib STATIC src/print.cpp) +target_include_directories(dummy_lib PUBLIC + "$" + "$") + +install(TARGETS dummy_lib + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) +install(DIRECTORY include/ DESTINATION include) diff --git a/examples/conan_workspace/src/dummy_lib/conanfile.py b/examples/conan_workspace/src/dummy_lib/conanfile.py new file mode 100644 index 0000000..81cd7a9 --- /dev/null +++ b/examples/conan_workspace/src/dummy_lib/conanfile.py @@ -0,0 +1,29 @@ +from conan import ConanFile +from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout + + +class DummyLibConan(ConanFile): + name = "dummy_lib" + version = "0.1" + package_type = "static-library" + settings = "os", "compiler", "build_type", "arch" + exports_sources = "CMakeLists.txt", "src/*", "include/*" + + def layout(self): + cmake_layout(self) + + def generate(self): + CMakeDeps(self).generate() + CMakeToolchain(self).generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + + def package_info(self): + self.cpp_info.libs = ["dummy_lib"] diff --git a/examples/conan_workspace/src/dummy_lib/include/dummy_lib/print.hpp b/examples/conan_workspace/src/dummy_lib/include/dummy_lib/print.hpp new file mode 100644 index 0000000..0af6c09 --- /dev/null +++ b/examples/conan_workspace/src/dummy_lib/include/dummy_lib/print.hpp @@ -0,0 +1,12 @@ +#ifndef DUMMY_LIB__PRINT_HPP_ +#define DUMMY_LIB__PRINT_HPP_ + +namespace dummy_lib +{ + +/** Writes a fixed dummy message to stdout (example library API). */ +void print_hello(); + +} // namespace dummy_lib + +#endif // DUMMY_LIB__PRINT_HPP_ diff --git a/examples/conan_workspace/src/dummy_lib/src/print.cpp b/examples/conan_workspace/src/dummy_lib/src/print.cpp new file mode 100644 index 0000000..73d296b --- /dev/null +++ b/examples/conan_workspace/src/dummy_lib/src/print.cpp @@ -0,0 +1,13 @@ +#include + +#include "dummy_lib/print.hpp" + +namespace dummy_lib +{ + +void print_hello() +{ + std::puts("[dummy_lib] hello from library"); +} + +} // namespace dummy_lib