0
$\begingroup$

Rosanswers logo

The catkin build command builds each package in a workspace’s source space in isolation in order to prevent build-time cross-talk.

I have a large workspace of ROS1 packages that builds with catkin_make. Sometimes other people with the same workspace will build and find that their build fails with some missing dependency in a CMakeLists.txt somewhere. I understand that catkin_make does non-isolated builds - so whether or not a dependency is available to a package is dependent on the order that the packages happen to built so lots of CMakeLists.txt ommissions are masked.

I have tried building my workspace with catkin build and found that it does expose a lot of build errors but it doesn't give me consistent builds. If I run it several times in succession, more and more of the packages will be successfully built. If I run catkin build && catkin clean several times, different numbers of packages will be successfully built.

If packages are being built "in isolation" I would expect the same thing to happen each time I run catkin build since the build order should be irrelevant? Clearly I am mistaken in this understanding so my question is:

What does packages being built "in isolation" mean? How is catkin build different to catkin_make ?

Is there some way to build my packages that is determinstic/repeatable? So that I can have confidence that if it builds on one machine, it'll build on another?

even if I run catkin build --force-cmake && catkin clean I get inconsistent results.


Originally posted by LukeAI on ROS Answers with karma: 131 on 2021-05-14

Post score: 0


Original comments

Comment by gvdhoorn on 2021-05-14:
Please refrain from asking multiple questions in a single post.

$\endgroup$

1 Answer 1

0
$\begingroup$

Rosanswers logo

I'm only going to answer this part of your question:

Is there some way to build my packages that is determinstic/repeatable?

tl;dr: if you don't state your dependencies correctly, Catkin is free to start building pkgs, which may in fact depend on each other, in parallel, leading to compiler errors and ultimately failed and aborted builds.

As the exact point in time where builds get aborted is (almost) stochastic (due to scheduling, CPU and IO performance, etc), and dependents typically will not need everything from dependencies, partial builds of dependencies may result in successful builds of (some) dependents.

This leads to successive builds appearing to make progress, as more-and-more dependents will find their dependencies where they expect them, leading to those builds succeeding, leading to their dependents being able to start their build. Etc, etc.

With enough iterations, the whole graph of dependencies gets built, until finally all dependencies are built and there are no more failures.

As to the question: always running catkin_make -j1 could help make things fail at almost the same place, however it's no guarantee, as other factors may still cause slight ordering differences in operations.


Long version: the Catkin side to this is already largely deterministic I believe. It's the other parts which may not be.

Catkin performs two main tasks before it asks CMake to build everything:

  1. discover all packages in your src space and construct a graph where the vertices are the packages and the edges encode dependencies (ie: if pkg A depends on B, there will be an edge between the vertices representing A and B expressing that dependency)
  2. run a topological sort over that graph, to determine the order in which everything should be built which satisfies those dependencies

Your packages state their dependencies using build_depends in their manifests, which Catkin parses and then uses to build the graph. After the sort, it asks CMake to start the build by building the packages which:

  1. have no dependencies (rare),
  2. for which all dependencies are already built,
  3. depend only on packages present in an underlay (or its underlay, ad infinitum)
  4. do not depend on each other

for all of those, the order in which they are built does not really matter, as pkgs in category 1 don't depend on anything, in category 2 and 3 have all their dependencies ready and in category 4 cannot fail when built at the same time as they don't need anything from each other.

After the first layer of packages has been built, Catkin moves up to the next, which will be packages which depend on (some, or all) of the packages it just built. This process then repeats, until the "top" package(s) is (are) built.

Note: only the build_depend entries in your package.xml are taken into account, not the setup.py or CMakeLists.txt (actually, there's also build_export_depend et al., but I'll ignore those here, they don't significantly change the explanation).

Now back to your question:

Sometimes other people with the same workspace will build and find that their build fails with some missing dependency in a CMakeLists.txt somewhere. [..] If I run it several times in succession, more and more of the packages will be successfully built.

As I wrote: only package.xml entries are used to create the graph. So it's entirely possible for a pkg A which depends on pkg B, but which does not state a <build_depend>B</build_depend> to end up on the same 'layer' of the graph and thus get built at the same time. Obviously this fails, as A does need B (Catkin can not really be blamed in such cases I believe: according to the information you provided, A and B fall into (at least) category 4 above).

However, it's possible A can be partly built, perhaps because it only needs some parts of B, and this is where the non-determinism comes in: depending on how your operating system schedules the cmake, make and gcc processes, those "some parts of B" may or may not have already completed building.

If they were finished by the time A tries to use them: you'll never know there was a problem with the build_depends of A. If they were not finished, A's build will fail, probably with some sort of #include error, complaining No such file of directory: "B/some_include.h" and Bs build is also aborted (together with any other jobs which may be running for other pkgs).

A good example of this situation is packages which provide messages, services and/or actions. The headers are generated during the build, and it's imperative message generation has completed before other packages try to #include those files, or GCC (or whichever compiler you're using) will not be able to find the files and abort.

If I run catkin build && catkin clean several times, different numbers of packages will be successfully built.

So where does the non-determinism come in, and why does restarting the build (sometimes) help?

Each time you restart your build, B's build processes will get a little further before they are aborted due to A's build failing. At some point, enough of B will have finished for A's build to no longer fail (fi: the message headers #included by A may have actually been generated by B's build). But how much of B succeeds is slightly non-deterministic, due to the stochasticity which results from OS process & thread scheduling I mentioned above (it not only depends on available jobs in the build, but on all other processes on your machine fi).

As to why different numbers of packages may succeed: a topological sort does not always have a unique solution, and in addition, again due to scheduling, there is a non-zero chance packages with the same or similar dependency sets will be scheduled "at the same time", but really only start building at slightly different times. Together with the semi-randomness of tasks succeeding in dependencies (fi: B's tasks getting scheduled and/or aborted at slightly different times), this leads to a situation where packages on the same layer in the dependency graph not always progressing in the exact same way in their build. This can appear to be random, but should be explainable if total insight into scheduling of tasks, progress of the build, file system state, etc would be available.

Disabling parallelism during the build (both process and thread) will significantly reduce these effects and likely cause failures in the same or similar spots. With catkin_make, this can be done with catkin_make -j1. catkin_tools supports similar flags.


Originally posted by gvdhoorn with karma: 86574 on 2021-05-14

This answer was ACCEPTED on the original site

Post score: 2


Original comments

Comment by gvdhoorn on 2021-05-14:
Refer to #q217475 for why we have separate (or duplicated) places to store dependency information btw.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.