Skip to content

Conversation

AshFungor
Copy link
Collaborator

@AshFungor AshFungor commented Aug 8, 2025

Hey, @riebl!

I think we should reconsider our project CMake layout. I can mention the following issues from my personal experience:

  • Dependency management is quite chaotic and spread across all project, which makes it hard to reuse previously declared packages and extern projects (individual components may declare same find_package for protobuf, for example);
  • Options are also spread across all project and some configure-time decisions are performed via checking target existence (veins & inet), which may confuse people at first;
  • Some of our components do not have CMakeLists.txt at all, and instead are managed by CMakeLists.txt of a parent directory, that comes in conflict with CMake philosophy of delegating work to children nodes.

I propose:

  • Simple and expressive top-level CMakeLists file with find_packages, options and other project-wide configuration. Also, primary libs are declared at that scope;
  • External components are managed by /extern/CMakeLists, and each component may be turned off;
  • Artery::Core aggregates sources from directories for now;
  • Components manage no external projects or deps, just declare and patch existing targets.

@AshFungor AshFungor requested a review from riebl August 8, 2025 20:56
Copy link
Owner

@riebl riebl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All in all, I am in favour of this PR. I have major doubts, however, if separating "dependency finding" from "dependency consuming" is a good idea.

Edit: Up to now, I would say it is okay to have multiple "features" calling the same "find_package". But I agree, there is room for improvement.

@@ -11,14 +11,8 @@ target_include_directories(ots PUBLIC $<TARGET_PROPERTY:core,INCLUDE_DIRECTORIES
set_property(TARGET ots PROPERTY NED_FOLDERS ${CMAKE_CURRENT_SOURCE_DIR})
set_property(TARGET ots PROPERTY OMNETPP_LIBRARY ON)

find_package(PkgConfig MODULE REQUIRED)
pkg_check_modules(ZEROMQ REQUIRED libzmq)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not convinced by this (and similar changes): Before this PR, the "find_package" part is co-located with the "consuming" part. By moving "find_package" away, we have only the usage of ZEROMQ_* variables left but it is not clear where they are defined.

add_subdirectory(networking)
add_subdirectory(application)

target_sources(core PRIVATE
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this part handling sources of each subdirectory separately, nice!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I wish we had src/artery/core directory and a number of static libs for each core component. I'm sort of terrified by the idea of patching numerous includes, but maybe :)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, that separating each component in its own library would be a clean approach. I have considered this in the past but found no clear benefit.


# force cmake to generate message first, in case actual args_TARGET
# sources use results from custom_command (include message headers)
add_custom_target(dummy_${msg_name}_target DEPENDS "${msg_output_header}")
Copy link
Collaborator Author

@AshFungor AshFungor Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, how it managed to work before? :)
There was no guarantee that cmake decides to execute custom_command and generate message source & header file before compiling any other source included to args_TARGET that uses it? I think that OUTPUTS dependency that is created by custom_command exists for target file only, which should imply that cmake may start building other args_target sources before it bumps into non-existent codegen file and invokes our custom_command.

I might be horribly wrong though. How it even happened that refactoring caused this...

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh? Why is a dummy target introduced? The generated files are added as sources to the given TARGET and thus the generator command is pulled in by the respective target.
I had no trouble so far adding not-yet-existing sources to a target as long as a matching generation rule exists.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed dummy target so you can observe what happens in CI :)

On my machine, with ninja, I have the following rule to generate a file:

#############################################
# Assume dependencies for generated source file.

build /workspaces/artery/build/Debug/opp_messages/artery/utility/AsioData_m.cc: CUSTOM_COMMAND || cmake_object_order_depends_target_core
  restat = 1

Then, here comes object file:

build CMakeFiles/core.dir/opp_messages/artery/utility/AsioData_m.cc.o: CXX_COMPILER__core_unscanned_Debug /workspaces/artery/build/Debug/opp_messages/artery/utility/AsioData_m.cc || cmake_object_order_depends_target_core
  CONFIG = Debug
  DEFINES = ...
  DEP_FILE = CMakeFiles/core.dir/opp_messages/artery/utility/AsioData_m.cc.o.d
  FLAGS = -g -std=c++17 -fPIC
  INCLUDES = ...
  OBJECT_DIR = CMakeFiles/core.dir
  OBJECT_FILE_DIR = CMakeFiles/core.dir/opp_messages/artery/utility
  TARGET_COMPILE_PDB = CMakeFiles/core.dir/
  TARGET_PDB = libartery_core.pdb

Finally, core target:

#############################################
# Link the shared library libartery_core.so

build libartery_core.so: CXX_SHARED_LIBRARY_LINKER__core_Debug ... CMakeFiles/core.dir/src/artery/utility/AsioScheduler.cc.o CMakeFiles/core.dir/src/artery/utility/AsioTask.cc.o CMakeFiles/core.dir/src/artery/utility/Channel.cc.o CMakeFiles/core.dir/src/artery/utility/Identity.cc.o CMakeFiles/core.dir/src/artery/utility/IdentityRegistry.cc.o CMakeFiles/core.dir/src/artery/utility/FilterRules.cc.o CMakeFiles/core.dir/src/artery/utility/Geometry.cc.o CMakeFiles/core.dir/opp_messages/artery/utility/AsioData_m.cc.o ...
  CONFIG = Debug
  DEP_FILE = CMakeFiles/core.dir/link.d
  LANGUAGE_COMPILE_FLAGS = -g
  LINK_FLAGS = -Wl,--dependency-file=CMakeFiles/core.dir/link.d
  LINK_LIBRARIES = ...
  LINK_PATH = -L/omnetpp/lib
  OBJECT_DIR = CMakeFiles/core.dir
  POST_BUILD = :
  PRE_LINK = :
  SONAME = libartery_core.so
  SONAME_FLAG = -Wl,-soname,
  TARGET_COMPILE_PDB = CMakeFiles/core.dir/
  TARGET_FILE = libartery_core.so
  TARGET_PDB = libartery_core.pdb

You can see that AsioData_m.cc.o comes in the same line as Identity.cc.o (for example), which means that ninja might decide to build Identity.cc.o before AsioData_m.cc.o, when headers for AsioData_m.cc.o have not been generated yet.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same build configuration on master yields these:

#############################################
# Custom command for opp_messages/artery/utility/AsioData_m.cc

build opp_messages/artery/utility/AsioData_m.cc opp_messages/artery/utility/AsioData_m.h | ${cmake_ninja_workdir}opp_messages/artery/utility/AsioData_m.cc ${cmake_ninja_workdir}opp_messages/artery/utility/AsioData_m.h: CUSTOM_COMMAND /workspaces/artery/src/artery/utility/AsioData.msg /omnetpp/bin/opp_msgc || extern/libINET.so extern/libveins.so extern/vanetza/lib/libvanetza_access.so extern/vanetza/lib/libvanetza_asn1.so extern/vanetza/lib/libvanetza_asn1_its.so ... /* more shared libs */
  COMMAND = cd /workspaces/artery/build/Debug/opp_messages/artery/utility && /omnetpp/bin/opp_msgc -s _m.cc -h /workspaces/artery/src/artery/utility/AsioData.msg
  DESC = Generating artery/utility/AsioData
  restat = 1

Also, buildorder rule exists:

build cmake_object_order_depends_target_core: phony || cmake_object_order_depends_target_INET cmake_object_order_depends_target_access ... /* more targets */ ... cmake_object_order_depends_target_gnss cmake_object_order_depends_target_net cmake_object_order_depends_target_security cmake_object_order_depends_target_traci cmake_object_order_depends_target_veins opp_messages/artery/utility/AsioData_m.cc opp_messages/artery/utility/AsioData_m.h

Copy link
Collaborator Author

@AshFungor AshFungor Aug 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, mystery resolved :)
If I move

generate_opp_message(AsioData.msg TARGET core)

To a different directory other than the one where original TARGET (Artery::core) is defined, it does not generate the command.

So, @riebl, what do you think? Should we force that dummy target to allow such cases, or just declare sub-core targets to actually make cmake generate a rule?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Come to think of it, we should probably adopt the idea of codegen target. This will be useful for linter in CI, and generally feels like a better approach (during development, you may invoke that target to re-generate or generate new auto-generated files)

https://cmake.org/cmake/help/latest/policy/CMP0171.html

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I would like to support Debian 12 as a baseline for the moment, which ships with CMake 3.25, i.e. without built-in codegen support.
Since the utility sub-directory contains non-optional sources, we may move their handling back to src/artery/CMakeLists.txt to avoid the issue. A follow-up PR may then add a codegen target.

@AshFungor
Copy link
Collaborator Author

AshFungor commented Aug 8, 2025

But I agree, there is room for improvement.

Can you elaborate on this one, please?)
I think it might be a good idea to have our standalone components more visible in project structure. I mean, there probably should be a clear way of understanding if certain piece of project may configure dependencies or not, like:

  • src/artery/some-component - ok, directory is correct, there might be find_package there
  • src/artery/some-dir/some-component - no, not ok. Component discovery should be moved upwards

What do you think?

Or we may leave this one out, since there are no guidelines or best practices about it :)

@AshFungor AshFungor self-assigned this Aug 9, 2025
@riebl
Copy link
Owner

riebl commented Aug 9, 2025

Can you elaborate on this one, please?

Well, the built-in features of CMake are rather limited when it comes to managing projects with many optional features and thus variable dependencies. I have seen some projects adding their own dependency management on top. For Artery, I can imagine a lightweight variant.

Let me briefly sketch a possible approach:

  1. Declare all dependencies in the root directory with a dedicated macro, e.g. declare_artery_dependency. The macro will look-up if the dependency is available quietly using find_package(... QUIET). If the dependency is found, it creates a target to be used by the consuming components.
  2. The components invoke require_artery_dependency which will abort the CMake configuration step if the dependency is not available. We may also conditionally add subdirectories depending on the available dependencies, e.g. add_artery_subdirectory(dir DEPENDENCIES foo). We may even combine it with feature flags like WITH_TESTBED and bailing out with a useful message: add_artery_subdirectory(testbed DEPENDENCIES protobuf SWITCH WITH_TESTBED).

@AshFungor AshFungor requested a review from riebl August 13, 2025 11:23
@AshFungor
Copy link
Collaborator Author

Hey! I think it's implemented now. I did not create declare_artery_dependency, though - CMake has no standard interface to interact with packages, so declaring a package tends to introduce different code each time.

# Primary Artery targets #
##########################

# TODO: temporarily targets are moved into src/artery
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why temporarily? What is still "to-do" here?

Copy link
Collaborator Author

@AshFungor AshFungor Aug 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved because of that generate_opp_message issue, but I wish to move them back once codegen target appears and there won't be any requirements for main targets' placement.

@AshFungor
Copy link
Collaborator Author

We should probably also make Find* configs for some extern packages - this will help integrating them with our new approach to handle deps.

@AshFungor AshFungor requested a review from riebl August 15, 2025 15:36
@AshFungor
Copy link
Collaborator Author

Hey, @riebl! Any updates on this one?)

@AshFungor
Copy link
Collaborator Author

I also added all features to CI, current runs will fail since package rebuild is required.

@AshFungor AshFungor requested a review from riebl August 21, 2025 09:19
@riebl
Copy link
Owner

riebl commented Aug 24, 2025

Looks all good now, but I don't want do override the PR checks. Would you mind adding the required Dockerfile adjustments to another preparatory PR?

@AshFungor AshFungor force-pushed the different-cmake-layout branch from cb38424 to d8d4d46 Compare August 25, 2025 14:08
@AshFungor AshFungor requested a review from riebl August 25, 2025 14:47
@AshFungor
Copy link
Collaborator Author

After small adjustments to package config - CI should pass alright now. :)

@AshFungor
Copy link
Collaborator Author

Hey, @riebl!

Something odd happens to protobuf-dependent features (testbed and transfusion). I'll investigate that later alongside implementing global codegen target.

This PR has grown quite big :) Think we can merge it now.

@riebl riebl merged commit 0e910ee into riebl:master Aug 26, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants