16

I'm using the FindProtobuf module in a project where the protocol buffer files are in a sub-directory. I want the CMakeLists.txt file in that sub-directory to invoke protoc to generate the CPP Files. My project folder structure is like this:

cammy/CMakeLists.txt   # Root CMakeLists.txt
cammy/protofiles/test.proto # protofile
cammy/protofiles/CMakeLists.txt

I have the include(FindProtobuf), the find_package invocation and the call to PROTOBUF_GENERATE_CPP in the CMakeLists.txt file in protobuf folder.

The executable build step is in the Root CMakeLists.txt file and I add the generated files to the target executable in this file ie.

add_executable( ${EXEC} ${SRCS} ${PROTO_SRC} ${PROTO_HEADER} )
target_link_libraries( ${EXEC} ${PROTOBUF_LIBRARIES} )

are both defined in the root CMakeLists.txt

When I run cmake, it does not run protoc to generate the Source files even though I expilicitly tie generated sources to the executable thereby creating a dependency.

When I move all the contents of CMakeLists.txt in the protofiles folder into the root CMakeLists.txt, the proto files are compiled.

Can anyone help me with this? I want all the protocol buffer building stuff to go in the CMakeLists.txt file created in the protofiles folder.

I also noticed that variables generated in the inner CMakeLists.txt ( like PROTO_SRC ) are defined in the inner file when printed ( ie I get the correct generated CPP filename ) but when I print the same variable in the root file.. it is empty. Its almost as though I need to "export" (if there were a way in cmake ) the variables out to the root folder.

Any help would be much appreciated.

Thanks Kartik

Kartik Aiyer
  • 586
  • 1
  • 6
  • 20

1 Answers1

27

I think [FindProtobuf][0] isn't really meant to be used this way. From its docs:

NOTE: The PROTOBUF_GENERATE_CPP macro & add_executable() or add_library() calls only work properly within the same directory.

You're trying to use the PROTOBUF_GENERATE_CPP macro in a subdirectory, and although the CMake docs don't really make it clear, a subdirectory introduces a new scope for variables. This means that any variables set or modified in the subdir scope don't affect similarly-named variables in the parent scope. Hence the reason for PROTO_SRC being available in your protofiles dir, but not in the parent.

The way to pass variables up a scope is to use [set(... PARENT_SCOPE)][1], so in protofiles/CMakeLists.txt you could do:

PROTOBUF_GENERATE_CPP(PROTO_SRC PROTO_HEADER test.proto)

set(PROTOBUF_INCLUDE_DIRS ${PROTOBUF_INCLUDE_DIRS} PARENT_SCOPE)
set(PROTOBUF_LIBRARIES ${PROTOBUF_LIBRARIES} PARENT_SCOPE)
set(PROTO_SRC ${PROTO_SRC} PARENT_SCOPE)
set(PROTO_HEADER ${PROTO_HEADER} PARENT_SCOPE)

However, this still doesn't get us all the way!

CMake doesn't actually invoke the protoc compiler to generate the .pb.h and .pb.cc files - it uses [add_custom_command][2] to do this. The custom command specifies the .pb.h and .pb.cc files as outputs, and the custom command is only invoked (i.e. protoc executed) if a subsequent target which depends on these files is built.

So, at configure time (when CMake executes) these files don't exist. This is a problem if you try to add them as sources to an add_library or add_executable command - CMake needs to be told that these files don't exist when it runs, but that they will exist at build time.

The way to do that is to set the [GENERATED][3] property to TRUE for these files. The PROTOBUF_GENERATE_CPP macro does that automatically, but as with the variables, the property isn't populated up into the parent scope. So in your top-level CMakeLists.txt, you also need to add:

set_source_files_properties(${PROTO_SRC} ${PROTO_HEADER} PROPERTIES
                            GENERATED TRUE)

As you can see, using PROTOBUF_GENERATE_CPP in a different dir to the corresponding add_library/add_executable commands is a bit fragile. If you can avoid doing it, you probably should.

[0]: https://cmake.org/cmake/help/latest/module/FindProtobuf.html "CMake latest documentation for "FindProtobuf" module" [1]: https://cmake.org/cmake/help/latest/command/set.html "CMake latest documentation for "set" command" [2]: https://cmake.org/cmake/help/latest/command/add_custom_command.html "CMake latest documentation for "add_custom_command"" [3]: https://cmake.org/cmake/help/latest/prop_sf/GENERATED.html "CMake latest documentation for "GENERATED" source file property"

starball
  • 20,030
  • 7
  • 43
  • 238
Fraser
  • 74,704
  • 20
  • 238
  • 215
  • Thanks for clearing things up. To sum it up, am I correct in saying that the reason why the nested proto compiling doesn't work is because the variables generated are not in the scope of the rook? Also, is it possible to mark the GENERATED flag when we set the parent scope variables from the local ones instead of having to do it in the root file ? Finally, would an alternate approach be to build the protobuf stuff as a library and just link it to the root executable ? – Kartik Aiyer Oct 13 '13 at 19:14
  • @KartikAiyer Not sure what "not in the scope of the rook" means, but if it's "not in the scope of the top-level CMakeLists.txt", then yes. For Q2, no - as far as I know there's no way to set properties on files which are defined in the parent scope. For Q3, yes - I believe that would be a good approach. – Fraser Oct 13 '13 at 21:49
  • @Fraser thank you for your answer, I have a similar question, I adjust the project and still not work , is it possible that give me more suggestion ? ref : http://stackoverflow.com/questions/29720410/no-member-found-when-use-cmake-construct-proto , sample project: https://github.com/yuikns/cmake-proto – Lumen Apr 20 '15 at 02:26
  • Worth noting that this also happens when the protobuf generated files are needed in a subdir: for example, I have all my `test_*.cpp` files in a `tests` directory, alongside their `CMakeLists.txt` to build the test binary and I was seeing a similar issue: adding that single line you suggested (`set_source_file_properties(...)`) solved the problem. Minor nit is that the var names should be `PROTO_SRCS` and `PROTO_HDRS` - but otherwise, awesome, man, thanks! – Marco Massenzio Apr 10 '17 at 06:08
  • Oh, and BTW, I don't seem to need to add the `add_custom_command` to my `CMakeLists` file: the protos just get generated whenever I run the `cmake --build .` (or `make all`) command. – Marco Massenzio Apr 10 '17 at 06:10