For large projects, is single CMakeLists.txt is preferred or multiple CMakeLists.txt via add_subdirectories

Hello All,

Currently in my project we are maintaining a single CMakeLists.txt where in we are keeping lots of targets for all our layers. Our project is setup as a layered architecture where in whole projects is divided into some 30 layers (which will increase more in future). For each layers we have separate repo. We have an internal tool, which runs on all these repos and based on rule defined for projects, it creates targets for each layer and write it into a single cmakelists.txt.

So my current CMakeLists.txt looks like this

Sri Ganeshji : Sri Balaji : Sri Pitreshwarji : Sri Durgaji : Sri Venkateshwara

This is a GENERATED file.

cmake_minimum_required(VERSION 3.22.0)

project(TallyWorld DESCRIPTION “” LANGUAGES NONE)

Base files

include (Base/TWInitialize)

############################################################
tw_setup_package_library (TWDevSupport
“” # BaseTarget
“” # BaseLayer
“$ENV{TWToolsHome}/TWDevSupport” #SourcePath
“” # PreCompiledHeader
)

############################################################
tw_create_library (TWUniversal
“” # BaseTarget
OFF # IsTest
“0.0.0-Development.0” # Version
“UniversalPlatform” # Category
“TWDevSupport” # BaseLayer
“$ENV{TWWorkspaceHome}/TallyWorld/UniversalPlatform/TWUniversal.Source” # SourcePath
“TWDevSupportLib.hpp” # PreCompiledHeader
“” # ToolKit
)
tw_add_library_private_header (TWUniversal
“Includes/InternalHPP”
)
tw_add_library_public_header (TWUniversal
“Includes/HPP”
)
tw_add_library_sources (TWUniversal
“Includes/HPP/TWBasePUID.hpp”
“Includes/HPP/TWDAR.hpp”
“Includes/HPP/TWDITPacket.hpp”
“Includes/HPP/TWDITPayload.hpp”
“Includes/HPP/TWHashCRC.hpp”
“Includes/HPP/TWOTP.hpp”
“Includes/HPP/TWPUIDHeader.hpp”
“Includes/HPP/TWTrustChainMgmt.hpp”
“Includes/HPP/TWUnivConsts.hpp”
“Includes/HPP/TWUniversalGenFiles.hpp”
“Includes/HPP/TWUnivStruct.hpp”
“Includes/HXX/TWHashCRC.hxx”
“Includes/InternalHPP/TWInternalCipher.hpp”
“Includes/InternalHPP/TWInternalCRC.hpp”
“Includes/InternalHPP/TWInternalDITPacket.hpp”
“Includes/InternalHPP/TWInternalDITPayload.hpp”
“Includes/InternalHPP/TWInternalHash.hpp”
“Includes/InternalHPP/TWInternalPRNG.hpp”
“Includes/InternalHPP/TWInternalPUID.hpp”
“Includes/InternalHPP/TWInternalTrustChainMgmt.hpp”
“Includes/InternalHPP/TWInternalUniversal.hpp”
“Includes/InternalHPP/TWInternalUniversalConstants.hpp”
“Includes/InternalHPP/TWInternalUniversalMain.hpp”
“Includes/InternalHPP/TWInternalUtils.hpp”
“Includes/InternalHPP/TWPrivateCipherAES.hpp”
“Includes/InternalHPP/TWPrivateCipherBlowfish.hpp”
“Includes/InternalHPP/TWPrivateCipherDES.hpp”
“Includes/InternalHPP/TWPrivateCipherIDEA.hpp”
“Includes/InternalHPP/TWPrivateCipherRC5.hpp”
“Includes/InternalHPP/TWPrivateCipherTEA.hpp”
“Includes/InternalHPP/TWPrivateCipherThreefish.hpp”
“Includes/InternalHPP/TWPrivateCipherTwofish.hpp”
“Includes/InternalHPP/TWPrivateHash.hpp”
“Includes/InternalHPP/TWPrivatePRNGBBS.hpp”
“Includes/InternalHPP/TWPrivatePRNGRC4.hpp”
“Includes/TWUniversal.dox”
“TWDIT/TWInternalPUID.cpp”
“TWDITPacket/TWDITPacket.cpp”
“TWDITPacket/TWInternalDITPacket.cpp”
“TWDITPayload/TWDITPayload.cpp”
“TWDITPayload/TWInternalDITPayload.cpp”
“TWHashCRC/TWHashCRC.cpp”
“TWInternalCipherGenerator/TWInternalBBS.cpp”
“TWInternalCipherGenerator/TWInternalRC4.cpp”
“TWInternalCRC/TWInternalCRC.cpp”
“TWInternalEncryption/TWInternalAES/TWInternalAES.cpp”
“TWInternalEncryption/TWInternalBlowfish/TWInternalBlowfish.cpp”
“TWInternalEncryption/TWInternalDES/TWInternalDES.cpp”
“TWInternalEncryption/TWInternalIDEA/TWInternalIDEA.cpp”
“TWInternalEncryption/TWInternalRC5/TWInternalRC5.cpp”
“TWInternalEncryption/TWInternalTEA/TWInternalTEA.cpp”
“TWInternalEncryption/TWInternalThreefish/TWInternalThreefish.cpp”
“TWInternalEncryption/TWInternalTwofish/TWInternalTwofish.cpp”
“TWInternalHash/TWInternalHash.cpp”
“TWInternalHash/TWPrivateHash.cpp”
“TWInternalUtils/TWInternalUtils.cpp”
“TWOTP/TWOTP.cpp”
“TWPUIDHeader/TWPUIDHeader.cpp”
“TWTrustChainMgmt/TWInternalTrustChainMgmt.cpp”
“TWTrustChainMgmt/TWTrustChainMgmt.cpp”
“TWUniversal/TWInternalUniversal.cpp”
“TWUniversalLib.hpp”
)
tw_setup_library (TWUniversal)
############################################################
tw_create_library (TWCompatibility
“” # BaseTarget
OFF # IsTest
“0.0.0-Development.0” # Version
“CompatibilityPlatform” # Category
“TWUniversal” # BaseLayer
“$ENV{TWWorkspaceHome}/TallyWorld/CompatibilityPlatform/TWCompatibility.Source” # SourcePath
“TWUniversalLib.hpp” # PreCompiledHeader
“” # ToolKit
)
tw_add_library_private_header (TWCompatibility
“Includes/InternalHPP”
)
tw_add_library_public_header (TWCompatibility
“Includes/HPP”
)
tw_add_library_sources (TWCompatibility
“Includes/HPP/TWCompatibilityConstants.hpp”
“Includes/HPP/TWCompatibilityLifecycle.hpp”
“Includes/HPP/TWHash.hpp”
“Includes/HPP/TWHuffman.hpp”
“Includes/HPP/TWLZStyleCompression.hpp”
“Includes/InternalHPP/TWInternalHash.hpp”
“Includes/InternalHPP/TWInternalHuffman.hpp”
“Includes/InternalHPP/TWInternalHuffmanCode.hpp”
“Includes/InternalHPP/TWInternalLZ4Compressor.hpp”
“Includes/InternalHPP/TWInternalUtils.hpp”
“Includes/InternalHPP/TWPrivateHash.hpp”
“TWCompatibilityLifecycle/TWCompatibilityLifecycle.cpp”
“TWCompression/TWHuffman/TWHuffman.cpp”
“TWCompression/TWLZStyleCompression/TWInternalLZ4.cpp”
“TWCompression/TWLZStyleCompression/TWLZStyleCompression.cpp”
“TWHash/TWHash.cpp”
“TWInternalHash/TWInternalHash.cpp”
“TWInternalHash/TWPrivateHash.cpp”
“TWInternalUtils/TWInternalUtils.cpp”
“TWCompatibilityLib.hpp”
)
tw_setup_library (TWCompatibility)
############################################################
tw_create_library (TWCoreBasic
“” # BaseTarget
OFF # IsTest
“0.0.0-Development.0” # Version
“FoundationalPlatform” # Category
“TWCompatibility” # BaseLayer
“$ENV{TWWorkspaceHome}/TallyWorld/FoundationalPlatform/TWCoreBasic.Source” # SourcePath
“TWCompatibilityLib.hpp” # PreCompiledHeader
“” # ToolKit
)
tw_add_library_private_header (TWCoreBasic
“Includes/InternalOSHPP”
)
tw_add_library_public_header (TWCoreBasic
“Includes/OSHPP”
“Includes/HPP”
)
tw_add_library_sources (TWCoreBasic
“Includes/HPP/TArray.hpp”
“Includes/HPP/TPString.hpp”
“Includes/HPP/TWAtomics.hpp”
“Includes/HPP/TWCoreBasicGenFiles.hpp”
“Includes/HPP/TWCoreBasicLifecycle.hpp”
“Includes/HPP/TWLargeInteger.hpp”
“Includes/HPP/TWMathUtils.hpp”
“Includes/HPP/TWNonIntrinsic128.hpp”
“Includes/HPP/TWNonIntrinsicInt128.hpp”
“Includes/HPP/TWNonIntrinsicUInt128.hpp”
“Includes/HPP/TWPlatformFunctionFrame.hpp”
“Includes/HPP/TWPlatformFunctionWrappers.hpp”
“Includes/HPP/TWStatus.hpp”
“Includes/HPP/TWTraits.hpp”
“Includes/HPP/TWTypes.hpp”
“Includes/HPP/TWUIDMgr.hpp”
“Includes/HXX/TArray.hxx”
“Includes/HXX/TPString.hxx”
“Includes/HXX/TWAtomics.hxx”
“Includes/HXX/TWLargeInteger.hxx”
“Includes/HXX/TWMathUtils.hxx”
“Includes/HXX/TWNonIntrinsicInt128.hxx”
“Includes/HXX/TWNonIntrinsicUInt128.hxx”
“Includes/HXX/TWPlatformFunctionWrappers.hxx”
“Includes/HXX/TWStatus.hxx”
“Includes/HXX/TWTraits.hxx”
“Includes/HXX/TWUIDMgr.hxx”
“Includes/InternalOSHPP/TWOSInternalJNIUtilsHelper.hpp”
“Includes/InternalOSHXX/TWOSInternalJNIUtilsHelper.hxx”
“Includes/OSHPP/TWKotlinInteropInfoHardcoded.hpp”
“Includes/OSHPP/TWOSJNIUtils.hpp”
“Includes/OSHPP/TWOSSystemIncludes.hpp”
“Includes/OSHPP/TWOSSystemIncludesKernelAndroid.hpp”
“Includes/OSHPP/TWOSSystemIncludesKernelApple.hpp”
“Includes/OSHPP/TWOSSystemIncludesKernelApplePrimaryOS.hpp”
“Includes/OSHPP/TWOSSystemIncludesKernelIOS.hpp”
“Includes/OSHPP/TWOSSystemIncludesKernelLinux.hpp”
“Includes/OSHPP/TWOSSystemIncludesKernelLinuxPrimaryOS.hpp”
“Includes/OSHPP/TWOSSystemIncludesKernelTVOS.hpp”
“Includes/OSHPP/TWOSSystemIncludesKernelUniKernel.hpp”
“Includes/OSHPP/TWOSSystemIncludesKernelWatchOS.hpp”
“Includes/OSHPP/TWOSSystemIncludesKernelWindows.hpp”
“Includes/OSHXX/TWOSJNIUtils.hxx”
“TWCoreBasicGenFiles/TWCoreBasicGenFiles.cpp”
“TWCoreBasicLifecycle/TWCoreBasicLifecycle.cpp”
“TWNonIntrinsic128/TWNonIntrinsicInt128/TWNonIntrinsicInt128.cpp”
“TWNonIntrinsic128/TWNonIntrinsicUInt128/TWNonIntrinsicUInt128.cpp”
“TWOSPlatformFunctionFrame/TWOSPlatformFunctionWrappers.cpp”
“TWStatus/TWStatus.cpp”
“TWUIDMgr/TWUIDMgr.cpp”
“TWCoreBasicLib.hpp”
“../../../TWGen/TWGenUniversalID.kt”
“../../../TWGen/TWGenUniversalID.swift”
)
tw_add_library_os_sources (TWCoreBasic “Android_Phone;Android_Tablet;Android_Wearable;Android_TV”
“TWOSPlatformFunctionFrame/TWKernelAndroid/TWKotlinInteropInfoHardcoded.cpp”
“TWOSPlatformFunctionFrame/TWKernelAndroid/TWOSCustomAnnotations.kt”
“TWOSPlatformFunctionFrame/TWKernelAndroid/TWOSJNIUtils.cpp”
“TWOSPlatformFunctionFrame/TWKernelAndroid/TWOSPlatformFunctionWrappers.kt”
)
tw_add_library_os_sources (TWCoreBasic “MacOS;iOS;iPadOS;tvOS;iOS_Simulator;iPadOS_Simulator;tvOS_Simulator”
“TWOSPlatformFunctionFrame/TWKernelApple/TWOSPlatformFunctionWrappers.swift”
)
tw_setup_library (TWCoreBasic)
############################################################
tw_create_library (TWCoreMain
“” # BaseTarget
OFF # IsTest
“0.0.0-Development.0” # Version
“FoundationalPlatform/TWCoreMain” # Category
“TWCoreBasic” # BaseLayer
“$ENV{TWWorkspaceHome}/TallyWorld/FoundationalPlatform/TWCoreMain.Source” # SourcePath
“TWCoreBasicLib.hpp” # PreCompiledHeader
“” # ToolKit
)
tw_add_library_private_header (TWCoreMain
“Includes/InternalOSHPP”
“Includes/InternalHPP”
)
tw_add_library_public_header (TWCoreMain
“Includes/OSHPP”
“Includes/HPP”
)
tw_add_library_sources (TWCoreMain
“Includes/HPP/TWCoreBasicLibNew.hpp”
“Includes/HPP/TWCoreMainGenFiles.hpp”
“Includes/HPP/TWCoreMainLibNew.hpp”
“Includes/HPP/TWCoreMainLifecycle.hpp”
“Includes/HPP/TWCoreProcessLibNew.hpp”
“Includes/HPP/TWLaunchParameter.hpp”
“Includes/HPP/TWOSDynamicLibLoader.hpp”
“Includes/HPP/TWOSProcessEntry.hpp”
“Includes/HPP/TWProcessLaunchProperties.hpp”
“Includes/InternalHPP/TWOSInternalEventLoopMgrToolKitNonInteractive.hpp”
“Includes/InternalOSHPP/TWOSInternalProcessEntryKernelWindows.hpp”
“Includes/InternalOSHPP/TWOSInternalServiceEntryKernelWindows.hpp”
“Includes/OSHPP/TWCoreMainSwiftBridgingHeader.hpp”
“Includes/OSHPP/TWJNIEntryKernelAndroid.hpp”
“Includes/OSHPP/TWOSInternalProcessEntryHelper.hpp”
“Includes/OSHPP/TWOSInternalProcessEntryHelperKernelApplePrimaryOS.hpp”
“Includes/OSHPP/TWOSServiceModeLaunch.hpp”
“Includes/OSHPP/TWOSSystemIncludesObjCpp.hpp”
“Includes/OSHPP/TWWindowsIncludes.hpp”
“Includes/OSHPP/TWWindowsNativeXAMLIncludes.hpp”
“Includes/OSHPP/TWWinUI3App.hpp”
“Includes/OSHPP/TWWinUI3Includes.hpp”
“TWCoreMainLifecycle/TWCoreMainLifecycle.cpp”
“TWProcessLaunchProperties/TWProcessLaunchProperties.cpp”
“TWCoreMainLib.hpp”
)
tw_add_library_os_sources (TWCoreMain “Android_Phone;Android_Tablet;Android_Wearable;Android_TV”
“TWOSDynamicLibLoader/TWKernelAndroid/TWOSDynamicLibLoaderKernelAndroid.cpp”
“TWUtils/TWKernelAndroid/TWUtilsKernelAndroid.kt”
)
tw_add_library_os_sources (TWCoreMain “MacOS;iOS;iPadOS;tvOS;iOS_Simulator;iPadOS_Simulator;tvOS_Simulator”
“TWOSDynamicLibLoader/TWKernelApple/TWOSDynamicLibLoaderKernelApple.cpp”
)
tw_add_library_os_sources (TWCoreMain “Linux”
“TWOSDynamicLibLoader/TWKernelLinuxPrimaryOS/TWOSDynamicLibLoaderKernelLinuxPrimaryOS.cpp”
)
tw_add_library_os_sources (TWCoreMain “Windows”
“TWOSDynamicLibLoader/TWKernelWindows/TWOSDynamicLibLoaderKernelWindows.cpp”
)
tw_setup_library (TWCoreMain)

Here all functions prefixed with tw_ are our own cmake functions written in CMakeFiles and these CMakeFiles are getting included from include (Base/TWInitialize) line CMakeLists.txt.

Here TWDevSupport, TWCoreBasic , TWCoreMains are our layers.. I have just takes 3 layers here but CMakeLists.txt holds data for all 30 layers.

Would it be advisable to create a single CMakeLists.txt for each layer and include them in main CMakeLists.txt. Some like below

Sri Ganeshji : Sri Balaji : Sri Pitreshwarji : Sri Durgaji : Sri Venkateshwara

# This is a GENERATED file.

cmake_minimum_required(VERSION 3.28.0)

project(TallyWorld DESCRIPTION “” LANGUAGES NONE)

# Base files

include (Base/TWInitialize)

add_subdirectory(TWDevSupport) → a CMakeLists.txt would be available (generated by internal tool) under TWDevSupport which will have all associated targets for TWDevSupport

add_subdirectory(TWUniversal) → a CMakeLists.txt would be available (generated by internal tool) under TWUniversal which will have all associated targets for TWUniversal

add_subdirectory(TWCoreBasic) → a CMakeLists.txt would be available (generated by internal tool) under TWCoreBasic which will have all associated targets for TWCoreBasic

add_subdirectory(TWCoreMain) → a CMakeLists.txt would be available (generated by internal tool) under TWCoreMain which will have all associated targets for TWCoreMain

I know this will work fine. What my question is , if when I run my internal tool to add or remove source files and as a result it only modified 1 layer’s CMakeLists.txt. Now CMake would generate/update cache, will it parse all CMakeLists.txt files added via add_subdirectories or it will parse only modified one and generate the cache. All I am looking is how can I improve cache generation time .

PS: I am using Visual Studio IDE integrated CMake for generating the build. I am using CMake Open Folder and thus when there is a change in CMakeLists.txt, VS will trigger CMake automatically to generate the cache.

Please lets us know if split approach would give me some benefits . Would appreciate any help/suggestions/comment on this.

You build script is another piece of software you maintain, so normal software architecture and maintenance rules apply.

While Big Ball of Mud is the most commonly shipped architecture, it’s not one that inspires pride.

Sorry, I did not understand your response :frowning: Could you please elaborate .

Well, as @LegalizeAdulthood mentioned, your cmake files are also software. So you will want to still design your cmake files so that they read easily. You also want to encapsulate away details at each level. You would want to have cmake files that are top level, coarse grained, and fine grained. You will then find it easier to read. Having everything in one file means the reader has to keep navigating the whole file again and again. The cmake files should really mirror the structure of your code. You mention you have layers, then I would expect there to be a cmake for each layer at least. Ultimately you want the cmake to be easy to read and have some logical structure.

Thanks @codefrog . Yes , we are in process to structure as you suggested. My question was apart of readibility (which is great, no doubt in it) will I also get, some performance benefits in cmake cache generating timing.