Skip to content

Commit

Permalink
Merge branch 'public-version-hpp' of ssh://github.com/stuqdog/cpp-sdk…
Browse files Browse the repository at this point in the history
… into public-version-hpp
  • Loading branch information
stuqdog committed Sep 20, 2024
2 parents fbb0c75 + 17c8ebc commit 95a1a2f
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 22 deletions.
123 changes: 120 additions & 3 deletions BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,124 @@ an installation tree under `build/install`.

If the build did not succeed, please see the next section.

## Building with `conan`

As of version 0.0.11 there is experimental support for building with
the `conan` C++ package manager. This section will go into more detail
without assuming any familiarity with `conan`, but please consult the
[`conan` docs](https://docs.conan.io/2/introduction.html) for more info.

To use `conan`, you'll need to have `python` and `pip` on your system if
you don't already. For example,

- Debian: `apt-get install python3 python3-pip`
- MacOS with Homebrew: `brew install python`

and then, optionally in a venv if you prefer,

```shell
pip install conan
conan profile detect
```

The second line detects a default [`conan`
profile](https://docs.conan.io/2/reference/config_files/profiles.html)
encapsulating your build environment, which you can modify as needed.

Next note that there are two possible approaches here, which complement
or supersede some of the other sections in this document. Namely, it is
possible to

1. use `conan` to build and package the SDK; OR
2. use `conan` to obtain the [software
prerequisites](#software-prerequisites) for the SDK.

Option 1. makes more sense for [building against the
SDK](#building-against-the-sdk) as you would in module development.
There we use `conan` to get the SDK's dependencies, and then to build,
install, and package the SDK, which can then be consumed by declaring it
as a `conan` dependency.

Option 2. makes more sense for doing locally development *on* the SDK
while using `conan` to get dependencies instead of your system package
manager. Note that Option 1 implies a superset of Option 2.

In either case, `conan` will use the [`conanfile.py`](/conanfile.py).
Note that we build with `offline_proto_generation=True` by default.

### Option 1. Creating and consuming the SDK `conan` package.

Here we use `conan` to package and install the SDK so that we can build
against it by declaring it as a dependency in a
`conanfile.txt` or a `conanfile.py`.

To do this, call `conan create .` from the project root. This will build
and test the `viam-cpp-sdk` recipe, adding it into your local cache. See
the [conan docs](https://docs.conan.io/2/reference/commands/create.html)
for more info on `conan create`, as we will omit any details about using
profiles, options, or settings to customize the build.

Once this is done, the `viam-cpp-sdk` package is ready to be consumed.
The example projects show a [minimal
`conanfile.txt`](src/viam/examples/project/cmake/conanfile.txt). With
this `conanfile.txt` in the same directory as your project's
`CMakeLists.txt`, you can then do, for example,

```shell
conan install . --output-folder=build-conan --build=missing
cmake . --preset=conan-release
cmake --build --preset=conan-release -j 8
```

Note that this can be done with the same `CMakeLists.txt` from the
[example project](src/viam/examples/project/cmake/CMakeLists.txt): it is
agnostic of the use of `conan` to package the SDK as opposed to the SDK
having been built and installed manually.

It is also possible to build using a `conanfile.py` rather than a
`conanfile.txt`, see again the [conan
docs](https://docs.conan.io/2/tutorial/consuming_packages/the_flexibility_of_conanfile_py.html#consuming-packages-flexibility-of-conanfile-py),
or look at the [`test_package/conanfile.py`](test_package/conanfile.py)
which is the test package recipe.

### Option 2. Using `conan` to manage the SDK dependencies

Here we use `conan` to grab the SDK dependencies, setting ourselves up
for local SDK development.

From the root of this repo, you can do
``` shell
conan install . --output-folder=build-conan --build=missing
```
to install dependencies with `conan`. And then
``` shell
cmake . --preset conan-release
cmake --build --preset=conan-release -j
```
The `conan install` sets up some
[cmake-presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html)
which are used to resolve `--preset conan-release` above. Among other
things, this tells CMake about the `conan_toolchain.cmake` generated by
`conan`, and runs CMake with certain environment variables set.

If you do not want to use cmake-presets, you can do
``` shell
cd build-conan
source conanbuild.sh
cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake
```
and later call `source deactivate_conanbuild.sh`. The `conanbuild.sh`
script does the same environment variable setting as the cmake-presets,
but it may be preferable if you would rather invoke your build system
directly.

PLEASE NOTE: Whether by cmake-presets or `conanbuild.sh`, you MUST, one
way or another, make the `PATH` entries set by `conan` available to
CMake, because `buf` requires that `protoc` is available on `PATH`. If
you do not do this then `buf generate` will fail outright, or, if you
have a different version of `protoc` available in your `PATH`, it will
silently fail and later cause compilation failures due to protobuf
version mismatches.

## Options to Configure or Customize the Build

Expand Down Expand Up @@ -250,9 +368,8 @@ The Viam C++ SDK installs support files for CMake's `find_package`
subsystem.

``` cmake
find_package(viam-cpp-sdk CONFIG REQUIRED COMPONENTS Lib)
target_link_libraries(mytarget viam-cpp-sdk::viamcpp)
find_package(viam-cpp-sdk CONFIG REQUIRED COMPONENTS viamsdk)
target_link_libraries(mytarget viam-cpp-sdk::viamsdk)
```

Note that you will need to ensure that `CMAKE_PREFIX_PATH` is set
Expand Down
59 changes: 47 additions & 12 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,18 @@ option(VIAMCPPSDK_SANITIZED_BUILD "Build with address and UB sanitizers (less pe
#
option(VIAMCPPSDK_CLANG_TIDY "Run the clang-tidy linter" OFF)

# The following options are only defined if this project is not being included as a subproject
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
include(CMakeDependentOption)
option(VIAMCPPSDK_BUILD_EXAMPLES "Build the example executables (requires building tests too)" ON)

# Note that the complex module example requires the testing library and adds its tests to the unit
# test suite. Thus you can only disable tests if examples are also disabled.
# The call below says don't give the user the option to disable tests unless examples are already
# disabled, and default to building the tests in either case.
cmake_dependent_option(VIAMCPPSDK_BUILD_TESTS "Build the unit test suite" ON "NOT VIAMCPPSDK_BUILD_EXAMPLES" ON)
endif()


# Enforce known toolchains and toolchain minima unless asked not to.
if (VIAMCPPSDK_ENFORCE_COMPILER_MINIMA)
Expand Down Expand Up @@ -186,6 +198,11 @@ set(CMAKE_MODULE_PATH
${CMAKE_MODULE_PATH}
${PROJECT_SOURCE_DIR}/src/viam/cmake
)
if (VIAMCPPSDK_BUILD_TESTS OR VIAMCPPSDK_BUILD_EXAMPLES)
# We maybe could include this unconditionally, but technically only tests or examples need it
include(viamcppsdk_utils)
endif()



# Configure how the SDK will manage runtime paths. This is important
Expand Down Expand Up @@ -336,11 +353,14 @@ find_package(Boost ${VIAMCPPSDK_BOOST_VERSION_MINIMUM} REQUIRED COMPONENTS heade
# another try via pkgconfig.

find_package(gRPC ${VIAMCPPSDK_GRPC_VERSION_MINIMUM} CONFIG)
if (gRPC_FOUND)

# Protobuf has an idiosyncratic system for checking version compatibility, so we specify
# no version here and check it manually below.
find_package(Protobuf CONFIG)

if (gRPC_FOUND AND Protobuf_FOUND)
get_target_property(VIAMCPPSDK_GRPC_CPP_PLUGIN gRPC::grpc_cpp_plugin LOCATION)
set(VIAMCPPSDK_PROTOBUF_VERSION ${Protobuf_VERSION})
set(VIAMCPPSDK_PROTOBUF_VERSION_MAJOR ${Protobuf_VERSION_MAJOR})
set(VIAMCPPSDK_PROTOBUF_VERSION_MINOR ${Protobuf_VERSION_MINOR})

# We need a good value for VIAMCPPSDK_PROTOBUF_VERSION in order to
# interpolate it into our `pkgconfig` requirements. Make sure we
Expand All @@ -358,7 +378,7 @@ if (gRPC_FOUND)
set(VIAMCPPSDK_GRPCXX_VERSION ${gRPC_VERSION})
set(VIAMCPPSDK_GRPCXX_LIBRARIES gRPC::grpc++)
set(VIAMCPPSDK_GRPCXX_REFLECTION_LIBRARIES gRPC::grpc++_reflection)
else()
elseif((NOT gRPC_FOUND) AND (NOT Protobuf_FOUND))
message(WARNING "Failed to find gRPC with `find_package`; falling back to `pkg_check_modules`")

include(FindPkgConfig)
Expand All @@ -368,8 +388,6 @@ else()
pkg_check_modules(GRPCXX REQUIRED grpc++>=${VIAMCPPSDK_GRPC_VERSION_MINIMUM})

set(VIAMCPPSDK_PROTOBUF_VERSION ${PROTOBUF_VERSION})
string(REGEX REPLACE "^([0-9]+)\.([0-9]+)\.([0-9]+)$" "\\1" VIAMCPPSDK_PROTOBUF_VERSION_MAJOR ${VIAMCPPSDK_PROTOBUF_VERSION})
string(REGEX REPLACE "^([0-9]+)\.([0-9]+)\.([0-9]+)$" "\\2" VIAMCPPSDK_PROTOBUF_VERSION_MINOR ${VIAMCPPSDK_PROTOBUF_VERSION})
set(VIAMCPPSDK_PROTOBUF_INCLUDE_DIRS ${PROTOBUF_INCLUDE_DIRS})
set(VIAMCPPSDK_PROTOBUF_LIBRARY_DIRS ${PROTOBUF_LIBRARY_DIRS})
set(VIAMCPPSDK_PROTOBUF_LIBRARIES ${PROTOBUF_LIBRARIES})
Expand All @@ -396,10 +414,25 @@ else()
set(VIAMCPPSDK_GRPCXX_REFLECTION_LIBRARIES ${VIAMCPPSDK_GRPCXX_LIBRARIES})
set(VIAMCPPSDK_GRPCXX_REFLECTION_LIB ${VIAMCPPSDK_GRPCXX_ROOT}_reflection)
list(PREPEND VIAMCPPSDK_GRPCXX_REFLECTION_LIBRARIES ${VIAMCPPSDK_GRPCXX_REFLECTION_LIB})
else()
message(FATAL_ERROR "Unexpected combination of find_package and pkg-config protobuf and gRPC")
endif()

# Do this here unconditionally in case find_package didn't set the MAJOR/MINOR variables
string(REGEX REPLACE "^([0-9]+)\.([0-9]+)\.([0-9]+)$" "\\1" VIAMCPPSDK_PROTOBUF_VERSION_MAJOR ${VIAMCPPSDK_PROTOBUF_VERSION})
string(REGEX REPLACE "^([0-9]+)\.([0-9]+)\.([0-9]+)$" "\\2" VIAMCPPSDK_PROTOBUF_VERSION_MINOR ${VIAMCPPSDK_PROTOBUF_VERSION})
string(REGEX REPLACE "^([0-9]+)\.([0-9]+)\.([0-9]+)$" "\\3" VIAMCPPSDK_PROTOBUF_VERSION_PATCH ${VIAMCPPSDK_PROTOBUF_VERSION})

# The check below expects a version that looks like 20-something.x.y, so if
# we got a major version that doesn't look like that, synthesize one from the version we did get.
if (VIAMCPPSDK_PROTOBUF_VERSION_MAJOR GREATER 20)
set(VIAMCPPSDK_PROTOC_VERSION "${VIAMCPPSDK_PROTOBUF_VERSION_MAJOR}.${VIAMCPPSDK_PROTOBUF_VERSION_MINOR}")
else()
set(VIAMCPPSDK_PROTOC_VERSION "${VIAMCPPSDK_PROTOBUF_VERSION_MINOR}.${VIAMCPPSDK_PROTOBUF_VERSION_PATCH}")
endif()

# These are the minimum versions of gRPC and protobuf for which remote plugins with buf are supported.
if ((VIAMCPPSDK_GRPCXX_VERSION VERSION_GREATER 1.51.1) AND (VIAMCPPSDK_PROTOBUF_VERSION VERSION_GREATER 21.7.0))
if ((VIAMCPPSDK_GRPCXX_VERSION VERSION_GREATER 1.51.1) AND (VIAMCPPSDK_PROTOC_VERSION VERSION_GREATER 21.7.0))
set(VIAMCPPSDK_BUF_REMOTE_PLUGIN_SUPPORTED 1)
endif()

Expand Down Expand Up @@ -441,8 +474,10 @@ add_custom_target(
DEPENDS all
)

add_custom_target(
install-examples
COMMAND ${CMAKE_COMMAND} -DCOMPONENT=viam-cpp-sdk_examples -P ${CMAKE_BINARY_DIR}/cmake_install.cmake
DEPENDS all
)
if (VIAMCPPSDK_BUILD_EXAMPLES)
add_custom_target(
install-examples
COMMAND ${CMAKE_COMMAND} -DCOMPONENT=viam-cpp-sdk_examples -P ${CMAKE_BINARY_DIR}/cmake_install.cmake
DEPENDS all
)
endif()
109 changes: 109 additions & 0 deletions conanfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from conan import ConanFile
from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout
from conan.tools.files import load
import re

class ViamCppSdkRecipe(ConanFile):
name = "viam-cpp-sdk"

license = "Apache-2.0"
url = "https://github.com/viamrobotics/viam-cpp-sdk/"

package_type = "library"

settings = "os", "compiler", "build_type", "arch"

options = {
"offline_proto_generation": [True, False],
"shared": [True, False]
}

default_options = {
"offline_proto_generation": True,
"shared": True
}

exports_sources = "CMakeLists.txt", "LICENSE", "src/*"

def set_version(self):
content = load(self, "CMakeLists.txt")
self.version = re.search("set\(CMAKE_PROJECT_VERSION (.+)\)", content).group(1).strip()

def configure(self):
# If we're building static then build the world as static, otherwise
# stuff will probably break.
# If you want your shared build to also build the world as shared, you
# can invoke conan with -o "&:shared=False" -o "*:shared=False",
# possibly with --build=missing or --build=cascade as desired,
# but this is probably not necessary.
if not self.options.shared:
self.options["*"].shared = False

def requirements(self):
self.requires('boost/[>=1.74.0]', transitive_headers=True)

# The SDK supports older grpc and protobuf, but these are the oldest
# maintained conan packages.
self.requires('grpc/[>=1.48.4]', transitive_headers=True)
self.requires('protobuf/[>=3.17.1]', transitive_headers=True)

self.requires('xtensor/[>=0.24.3]', transitive_headers=True)
self.requires('abseil/[>=20230125.3]')

def build_requirements(self):
if self.options.offline_proto_generation:
self.tool_requires('grpc/[>=1.48.4]')
self.tool_requires('protobuf/[>=3.17.1]')

def layout(self):
cmake_layout(self)

def generate(self):
tc = CMakeToolchain(self)

tc.cache_variables["VIAMCPPSDK_OFFLINE_PROTO_GENERATION"] = self.options.offline_proto_generation
tc.cache_variables["VIAMCPPSDK_USE_DYNAMIC_PROTOS"] = True

tc.cache_variables["VIAMCPPSDK_BUILD_TESTS"] = False
tc.cache_variables["VIAMCPPSDK_BUILD_EXAMPLES"] = False

tc.generate()

CMakeDeps(self).generate()

def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()

def package(self):
CMake(self).install()

def package_info(self):
self.cpp_info.components["viam_rust_utils"].libs = ["viam_rust_utils"]

for component in ["viamsdk", "viamapi"]:
self.cpp_info.components[component].libs = [component]
self.cpp_info.components[component].set_property("cmake_target_name", "viam-cpp-sdk::{}".format(component))
self.cpp_info.components[component].set_property("pkg_config_name", "viam-cpp-sdk-lib{}".format(component))
self.cpp_info.components[component].requires = ["grpc::grpc++", "protobuf::libprotobuf"]
if self.settings.os in ["Linux", "FreeBSD"]:
self.cpp_info.components[component].system_libs = ["pthread"]

if self.settings.os in ["Linux", "FreeBSD"]:
self.cpp_info.components["viamsdk"].system_libs.extend(["dl", "rt"])

self.cpp_info.components["viamapi"].includedirs.append("include/viam/api")

self.cpp_info.components["viamsdk"].requires.extend([
"viamapi",
"boost::headers",
"boost::log",
"xtensor::xtensor",

"viam_rust_utils",
"abseil::absl_strings",
"grpc::grpc++_reflection"
])

self.cpp_info.components["viamsdk"].frameworks = ["Security"]
5 changes: 4 additions & 1 deletion src/viam/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@

add_subdirectory(api)
add_subdirectory(sdk)
add_subdirectory(examples)

if (VIAMCPPSDK_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()

# Generate CMake configs to enable importing this project
# into others via `find_package`.
Expand Down
2 changes: 1 addition & 1 deletion src/viam/api/config/buf.gen.remote.plugin.yaml.in
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ managed:
plugins:
- plugin: buf.build/grpc/cpp:v@VIAMCPPSDK_GRPCXX_VERSION@
out: gen/viam/api
- plugin: buf.build/protocolbuffers/cpp:v@VIAMCPPSDK_PROTOBUF_VERSION_MAJOR@.@VIAMCPPSDK_PROTOBUF_VERSION_MINOR@
- plugin: buf.build/protocolbuffers/cpp:v@VIAMCPPSDK_PROTOC_VERSION@
out: gen/viam/api
8 changes: 6 additions & 2 deletions src/viam/examples/project/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ tree and read the build instructions, then install the SDK.

- For an example project showing how to consume an installed
`viam-cpp-sdk` with CMake's `find_package`, see
`cmake/CMakeLists.txt`.
[`cmake/CMakeLists.txt`](cmake/CMakeLists.txt). Note that
this sample `CMakeLists.txt` can be used with no modifications
to build against the SDK as installed directly from CMake,
or to build against the `viam-cpp-sdk` `conan` package. For
instructions building with `conan`, see [here](/BUILDING.md#creating-and-consuming-the-sdk-conan-package).

- For an example project showing how to consume an installed
`viam-cpp-sdk` via its `pkg-config` setup, see
`pkg-config/Makefile`.
[`pkg-config/Makefile`](pkg-config/Makefile).
6 changes: 6 additions & 0 deletions src/viam/examples/project/cmake/conanfile.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[requires]
viam-cpp-sdk/[>=0.0.11]

[generators]
CMakeDeps
CMakeToolchain
Loading

0 comments on commit 95a1a2f

Please sign in to comment.