10

For a project, I am using Android gradle scripts with CMake, gradle plugin is version 3:0:0, CMake version 3.6. Both gradle and CMake files are pretty simple and uninteresting (just defining the files used - I can still copy-paste them as required).

I have the following project structure; basically a codebase producing a few tens of .so files (the native part for the Android packages that get packaged into an apk, thereby called 'Executables'), which all depend on the same shared library code (static libraries, thereby called 'Libraries'). The Library code is still (relatively) volatile, so I wish the Executables to have project-level dependencies on them, so that whenever the Executables are built, the Libraries are rebuilt on-demand every time their code is changed. The structure looks like:

+ LibProjects/
---Bin/ (Originally empty)
---Lib1/CMakeLists.txt (+sources files, same level as the CMakeLists.txt)
...
---Lib10/CMakeLists.txt (same)
+ Executables/
---Executable1/CMakeLists.txt (source files here)
--------------/AndroidFiles/build.gradle (and other android project files)(points to the CMakeLists.txt)
...
---Executable40/CMakeLists.txt

The Libraries' CMakeLists redirect their output into the Bin folder using

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY {CMAKE_CURRENT_SOURCE_DIR}/../Bin/${ANDROID_ABI}/${LibraryName})

The executable's projects add dependencies on the libraries "as normal"

add_subdirectory(${PROJECTS_ROOT}/LibProjects/${LibraryName} ${PROJECTS_ROOT}/Framework/Bin/Android/${ANDROID_ABI}/${LibraryName})...

Everything almost works, in the sense that I can get sensible executables and the Executables trigger builds of the libraries.

The problem is that when building the executables sequentially, each one does NOT reuse the library project outputs of the other ones: When I build Executable1, it will build all libraries (normal) and then it will build itself. Afterwards, when I build Executable2, it will NOT reuse the libraries that were already built for Executable1, and so on - this effectively increases my build time by a factor of ~10.

I can find the output of the build of each library inside the /Bin folder as expected, but they are not reused across executables - there are no CMake "project files" (is this the correct term) in the bin folder, all of them get generated inside the executable build directory.

The problem I am trying to resolve is the build times stemming from the fact that each library gets rebuilt for each executable.

At the moment the solutions I am considering is to somehow instruct CMake to use the Bin folder (or another folder) as a working folder for each library in its own folder instead of with the executable, hoping that the gradle android plugin will be smart enough to then spot that neither the cmakefiles nor the object files need to be regenerated, and avoid the rebuild.

The restriction that I have is that I cannot restructure the codebase itself, and that each Executable must be buildable separately of the others - there is absolutely no possibility of a top-level CMake - each Executable should be able to be triggered on its own.

Gerasimos R
  • 2,056
  • 1
  • 12
  • 22
  • AndroidFiles you have only under Executable1? Or you have 40 of these? – Alex Cohn Dec 18 '17 at 19:55
  • Looks like a dependency issue, can you check if your targets are explicitly listing the dependencies for every target? Also, if you can post `CMakeLists.txt` for the library and the executable. – AmeyaVS Dec 19 '17 at 05:06
  • AndroidFiles I have 40 of. I can post the CMakeLists but they won't tell you anything - the dependencies are literally what I posted. It is not a "dependency issue" in the sense that the behaviour is *expected* it is just not what I want - see Oliv's answer below. – Gerasimos R Dec 22 '17 at 11:04

3 Answers3

2

CMake can guess if the build is up-to-date by reading informations froms the current build directory.

When you run CMake manualy in Executables/<x> directory, cmake retrieve information from the build directory associated to Executable/<x> directory. It then check if the timestamp of the built file correspond to the last build performed in this build directory. If not, it rebuild. What happen is that: Lib1 library file is built after you build Executable1, then you run cmake in Executalbe2, it compares the timestamp of Lib1 target file, see that this file was not produced by this instance of the cmake build and then rebuild the lib. And so on.

So you have two options:

1- Either you build the library and install their target files in the bindirectory (using install cmake command and make install bash command for exemple). Then in the Executalbe<x>/CMakeLists you use find_library command instead of add_subdirectory.

2- Or you create a super project which has the following structure:

+  supper_project
---CMakeLists.txt #add_subdirectory(LibProjects/lib<x>)... add_subdirectory(Executables/Executalbe<x>)...
  + LibProjects/
  ---Bin/ (Originally empty)
  ---Lib1/CMakeLists.txt (+sources files, same level as the CMakeLists.txt)
  ...
  ---Lib10/CMakeLists.txt (same)
  + Executables/
  ---Executable1/CMakeLists.txt (source files here)
  --------------/AndroidFiles/build.gradle (and other android project files)
  (not any more:points to the CMakeLists.txt)
  ...
  ---Executable40/CMakeLists.txt
Oliv
  • 17,610
  • 1
  • 29
  • 72
  • 1
    Unfortunately this answer is "sorry - there is no answer" - option 1 is one of the things I want to avoid (two-step building), while option 2 is not an option - the build is mix-and-match, not monolithing. All of which is fair enough. I will wait a while to see if anyone comes up with anything, otherwise I will award this one. – Gerasimos R Dec 22 '17 at 11:06
  • Clarification - I'm disappointed in reality, not the answer :) – Gerasimos R Dec 22 '17 at 11:08
  • @Gerasimos What do you mean by mix-and-match. You realy should clarify your idea and your question, my answer is essentialy a speculation about what you are doing. I suppose that you are performing a build in each executable directory by executing for each directory the cmake command. But your last comment confuse me. I have certainly not grasped your intention and what you are actualy doing. – Oliv Dec 22 '17 at 13:01
  • I think you have grasped what I am trying to do just fine - sorry to confuse. By "mix and match" I mean that each "executable" project must be buildable separately, so there is absolutely no possibility of an "ubercmake" file building everything. That's all I meant. – Gerasimos R Jan 02 '18 at 16:20
  • What's wrong with '2-step' building? Note that each executable can trigger build & install of all libraries that it needs. – Alex Cohn Jan 02 '18 at 20:16
  • The problem with 2-step building is manyfold: the libraries have very volatile code (are expected to be modified/extended even by the end user), but please explain - if each executable can trigger build and install of libraries, based on their own code, and if that only happens when the library code is changed, then that would be the actual answer. – Gerasimos R Jan 03 '18 at 11:04
  • Additionally, this would make the solution sloppy (but it is not unsurmountable); Android build system makes it unwieldy to generate standalone libraries: gradle scripts are really only designed to generate .apk, not libraries, so while they CAN be hacked into generating the static libraries, it does look weird and rather sloppy. This is the solution I am currently developing, but I am not thrilled by it. – Gerasimos R Jan 03 '18 at 16:53
1

I managed to work around this problem - but in the end it was by working around rather than with CMake.

I removed the CMakeFile-level dependencies (add_subdirectory) and only left the libraries at the linking level (target_link_libraries Executable [the library files])

Afterwards, I created gradle scripts for each library and added dependencies to these scripts in each application gradle script, so that the building of the libraries gets triggered by gradle dependencies instead of CMake dependencies. It's slower than it would be if gradle could be avoided, but much faster than rebuilding every time, and the overhead is at least constant (a few seconds per project).

Gerasimos R
  • 2,056
  • 1
  • 12
  • 22
0

I think problem lies in the way you have defined your dependencies.

For each executable you are creating separate targets using add_subdirectory. e.g. for executable 1 you have add_subdirectory(${PROJECTS_ROOT}/LibProjects/${Library1}) and for executable 2 also you have add_subdirectory(${PROJECTS_ROOT}/LibProjects/${Library1}), so cmake will create two separate targets for same library1 in each of the executable's subdirectory and thus it will create separate timestamp and cache files. That is why it looks that it is building the same library for multiple times, but in fact for cmake they are different targets.

To fix this you can include all libraries in top level CMakeLists.txt using add_subdirectory and them in each executable's CMakeLists.txt add the dependency using add_dependencies command.

Vikash Kesarwani
  • 850
  • 5
  • 14