blob: e74edc2d30e8d9e239e153f5a888d334a4d72541 [file] [log] [blame]
#-------------------------------------------------------------------------------
# Copyright (c) 2020-2021, Arm Limited and Contributors. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
#-------------------------------------------------------------------------------
#[===[.rst:
NonoPB integration for cmake
----------------------------
This module will:
- download nanopb if not available locally
- build the runtime static library and the generator
- import the static library to the build
- define a function to provide access to the generator
Note: the python module created by the generator build will be installed under
Python_SITELIB ("Third-party platform independent installation directory.")
This means the build may alter the state of your system. Please use virtualenv.
Note: see requirements.txt for dependnecies which need to be installed before
running this module.
#]===]
#### Get the dependency
set(NANOPB_URL "https://github.com/nanopb/nanopb.git" CACHE STRING "nanopb repository URL")
set(NANOPB_REFSPEC "nanopb-0.4.2" CACHE STRING "nanopb git refspec")
set(NANOPB_INSTALL_PATH "${CMAKE_CURRENT_BINARY_DIR}/nanopb_install" CACHE PATH "nanopb installation directory")
set(NANOPB_PACKAGE_PATH "${NANOPB_INSTALL_PATH}/libnanopb/cmake" CACHE PATH "nanopb CMake package directory")
include(FetchContent)
# Checking git
find_program(GIT_COMMAND "git")
if (NOT GIT_COMMAND)
message(FATAL_ERROR "Please install git")
endif()
# Fetching nanopb
FetchContent_Declare(
nanopb
GIT_REPOSITORY ${NANOPB_URL}
GIT_TAG ${NANOPB_REFSPEC}
GIT_SHALLOW TRUE
#See the .patch file for details on why it is needed.
PATCH_COMMAND git stash
COMMAND git apply ${CMAKE_CURRENT_LIST_DIR}/fix-pyhon-name.patch
)
# FetchContent_GetProperties exports nanopb_SOURCE_DIR and nanopb_BINARY_DIR variables
FetchContent_GetProperties(nanopb)
if(NOT nanopb_POPULATED)
message(STATUS "Fetching nanopb")
FetchContent_Populate(nanopb)
endif()
#### Build the runtime and the generator.
# Pass extra include paths to nanopb build uning CFLAGS if needed
if (NOT "${NANOPB_EXTERNAL_INCLUDE_PATHS}" STREQUAL "")
string(REPLACE ";" "-I " NANOPB_EXTERNAL_INCLUDE_PATHS "${NANOPB_EXTERNAL_INCLUDE_PATHS}")
set(_SAVED_CFLAGS $ENV{CFLAGS})
set(ENV{CFLAGS} "$ENV{CFLAGS} -I ${NANOPB_EXTERNAL_INCLUDE_PATHS}")
endif()
if( NOT CMAKE_CROSSCOMPILING)
execute_process(COMMAND
${CMAKE_COMMAND}
-DBUILD_SHARED_LIBS=Off
-DBUILD_STATIC_LIBS=On
-Dnanopb_BUILD_RUNTIME=On
-Dnanopb_BUILD_GENERATOR=On
-Dnanopb_PROTOC_PATH=${nanopb_SOURCE_DIR}/generator/protoc
-Dnanopb_MSVC_STATIC_RUNTIME=Off
-DCMAKE_INSTALL_PREFIX=${NANOPB_INSTALL_PATH}
-DCMAKE_TOOLCHAIN_FILE=${TS_EXTERNAL_LIB_TOOLCHAIN_FILE}
-GUnix\ Makefiles
${nanopb_SOURCE_DIR}
WORKING_DIRECTORY
${nanopb_BINARY_DIR}
RESULT_VARIABLE _exec_error
)
else()
execute_process(COMMAND
${CMAKE_COMMAND}
-DBUILD_SHARED_LIBS=Off
-DBUILD_STATIC_LIBS=On
-Dnanopb_BUILD_RUNTIME=On
-Dnanopb_BUILD_GENERATOR=On
-Dnanopb_PROTOC_PATH=${nanopb_SOURCE_DIR}/generator/protoc
-Dnanopb_MSVC_STATIC_RUNTIME=Off
-DCMAKE_INSTALL_PREFIX=${NANOPB_INSTALL_PATH}
-DCMAKE_TOOLCHAIN_FILE=${TS_EXTERNAL_LIB_TOOLCHAIN_FILE}
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY
-GUnix\ Makefiles
${nanopb_SOURCE_DIR}
WORKING_DIRECTORY
${nanopb_BINARY_DIR}
RESULT_VARIABLE _exec_error
)
endif()
if (NOT "${NANOPB_EXTERNAL_INCLUDE_PATHS}" STREQUAL "")
set($ENV{CFLAGS} ${_SAVED_CFLAGS})
unset(_SAVED_CFLAGS)
unset(NANOPB_EXTERNAL_INCLUDE_PATHS)
endif()
if (_exec_error)
message(FATAL_ERROR "Configuration step of nanopb runtime failed with ${_exec_error}.")
endif()
execute_process(COMMAND
${CMAKE_COMMAND} --build ${nanopb_BINARY_DIR} -- install -j8
RESULT_VARIABLE _exec_error
)
if (_exec_error)
message(FATAL_ERROR "Build step of nanopb runtime failed with ${_exec_error}.")
endif()
#### Include Nanopb runtime in the build.
find_package(Nanopb
PATHS "${NANOPB_INSTALL_PATH}"
NO_DEFAULT_PATH
)
#### Build access to the protobuf compiler
#TODO: verify protoc dependencies: python3-protobuf
find_package(Python3 COMPONENTS Interpreter)
if (NOT Python3_Interpreter_FOUND)
message(FATAL_ERROR "Failed to find python3 interpreter.")
endif()
find_file(NANOPB_GENERATOR_PATH
NAMES nanopb_generator.py
PATHS ${nanopb_SOURCE_DIR}/generator
DOC "nanopb protobuf compiler"
NO_DEFAULT_PATH
)
if (NOT NANOPB_GENERATOR_PATH)
message(FATAL_ERROR "Nanopb generator was not found!")
endif()
#[===[.rst:
.. cmake:command:: protobuf_generate
.. code-block:: cmake
protobuf_generate(SRC file.proto
TGT foo
NAMESPACE bar
BASE_DIR "proto/definitions")
Run the ``nanopb_generator`` to compile a protobuf definition file into C source.
Generated source file will be added to the source list of ``TGT``. Protobuf
compilation will take part before TGT+NAMESPACE is built.
Protobuf file names added to the same TGT must not collide.
Inputs:
``SRC``
Path to of the protobuf file to process. Either absoluto or relative to the
callers location.
``TGT``
Name of target to compile generated source files.
``NAMESPACE``
Namespace to put generated files under. Specifies include path and allows
separating colliding protobuf files.
``BASE_DIR``
Base directory. Generated files are located reletive to this base.
#]===]
function(protobuf_generate)
set(_options )
set(_oneValueArgs SRC TGT NAMESPACE BASE_DIR)
set(_multiValueArgs )
cmake_parse_arguments(PARAMS "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN})
#Verify mandatory parameters
if (NOT DEFINED PARAMS_SRC)
message(FATAL_ERROR "nanopb_generate(): mandatory parameter SRC missing.")
endif()
if (NOT DEFINED PARAMS_TGT)
message(FATAL_ERROR "nanopb_generate(): mandatory parameter TGT missing.")
endif()
if (NOT DEFINED PARAMS_NAMESPACE)
message(FATAL_ERROR "nanopb_generate(): mandatory parameter NAMESPACE missing.")
endif()
if (NOT DEFINED PARAMS_BASE_DIR)
message(FATAL_ERROR "nanopb_generate(): mandatory parameter BASE_DIR missing.")
endif()
#If SRC is not abolute make it relative to the callers location.
if (NOT IS_ABSOLUTE ${PARAMS_SRC})
set(PARAMS_SRC "${CMAKE_CURRENT_LIST_DIR}/${PARAMS_SRC}")
endif()
#Calculate the output directory
set(_OUT_DIR_BASE ${CMAKE_BINARY_DIR}/src/${PARAMS_NAMESPACE})
#Calculate output file names
get_filename_component(_BASENAME ${PARAMS_SRC} NAME_WE)
#Get relative path or SRC to BASE_DIR
file(RELATIVE_PATH _SRC_REL ${PARAMS_BASE_DIR} ${PARAMS_SRC})
get_filename_component(_OUT_DIR_REL ${_SRC_REL} DIRECTORY )
#Calculate output file paths
set(_OUT_C "${_OUT_DIR_BASE}/${_OUT_DIR_REL}/${_BASENAME}.pb.c")
set(_OUT_H "${_OUT_DIR_BASE}/${_OUT_DIR_REL}/${_BASENAME}.pb.h")
#some helper variables for the purpose of readability
set(_nanopb_target "nanopb_generate_${PARAMS_TGT}_${PARAMS_NAMESPACE}")
set(_nanopb_fake_file "nanopb_generate_ff_${PARAMS_TGT}")
if (NOT TARGET "${_nanopb_target}")
#Tell cmake the dependency (source) file is fake.
set_source_files_properties("${_nanopb_fake_file}" PROPERTIES SYMBOLIC "true")
#Create a custom target which depends on a "fake" file.
add_custom_target("${_nanopb_target}"
DEPENDS "${_nanopb_fake_file}")
#Add a cutom command to the target to create output directory.
add_custom_command(OUTPUT "${_nanopb_fake_file}"
COMMAND ${CMAKE_COMMAND} -E make_directory ${_OUT_DIR_BASE}
COMMENT "Generating source from protobuf definitions for target ${PARAMS_TGT}")
#Ensure protobuf build happens before test target.
add_dependencies(${PARAMS_TGT} ${_nanopb_target})
#Add include path to protobuf output.
target_include_directories(${PARAMS_TGT} PRIVATE ${_OUT_DIR_BASE})
endif()
#Append a protobuf generator command to the nanopb_generate target.
add_custom_command(OUTPUT "${_OUT_C}" "${_OUT_H}"
COMMAND ${Python3_EXECUTABLE} ${NANOPB_GENERATOR_PATH}
-I ${PARAMS_BASE_DIR}
-D ${_OUT_DIR_BASE}
${_SRC_REL}
DEPENDS "${PARAMS_SRC}")
#Add generated file to the target
set_property(SOURCE "${_OUT_C}" PROPERTY GENERATED TRUE)
target_sources(${PARAMS_TGT} PRIVATE "${_OUT_C}")
endfunction()
#[===[.rst:
.. cmake:command:: protobuf_generate_all
.. code-block:: cmake
protobuf_generate_all(TGT foo
NAMESPACE bar
BASE_DIR "proto/definitions")
Generates C code from all .proto files listed in the target
property PROTOBUF_FILES.
Inputs:
``TGT``
Name of target to compile generated source files.
``NAMESPACE``
Namespace to put generated files under. Specifies include path and allows
separating colliding protobuf files.
``BASE_DIR``
Base directory. Generated files are located reletive to this base.
#]===]
function(protobuf_generate_all)
set(_options )
set(_oneValueArgs TGT NAMESPACE BASE_DIR)
set(_multiValueArgs )
cmake_parse_arguments(PARAMS "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN})
#Verify mandatory parameters
if (NOT DEFINED PARAMS_TGT)
message(FATAL_ERROR "nanopb_generate_all(): mandatory parameter TGT missing.")
endif()
if (NOT DEFINED PARAMS_NAMESPACE)
message(FATAL_ERROR "nanopb_generate_all(): mandatory parameter NAMESPACE missing.")
endif()
if (NOT DEFINED PARAMS_BASE_DIR)
message(FATAL_ERROR "nanopb_generate_all(): mandatory parameter BASE_DIR missing.")
endif()
get_property(_protolist TARGET ${PARAMS_TGT} PROPERTY PROTOBUF_FILES)
#Build of each .proto file
foreach(_file IN LISTS _protolist)
protobuf_generate(
TGT ${PARAMS_TGT}
SRC "${_file}"
NAMESPACE ${PARAMS_NAMESPACE}
BASE_DIR ${PARAMS_BASE_DIR})
endforeach()
endfunction()