I know that in these kinds of projects a top-level CMakeLists is used, but I am trying to have these 2 components be built separately.
If you want to build them truly separately, you'll have to go through some boilerplate. I assume you're in a situation where this is worth the pain: maybe you have to use two different toolchains. Otherwise, creating a build at the top level with just two add_subdirectory calls for lib and then app with the same following lists files will work (and you can even remove the packaging stuff).
Basically, in CMake you should always work with targets. Never link directly to a library file unless there isn't any other way. When you use targets, as I do below, it will even set up the include paths for you correctly. This is because CMake propagates the PUBLIC/INTERFACE properties (like those set by target_include_directories) to linkees.
CMake cannot magically determine include paths from a fully compiled static library it didn't even know it built. That is the crux of the issue here. You need to use targets.
Anyway, the boilerplate is just to declare which targets you want to export and then actually go ahead and export them. The GNUInstallDirs module sets up sane defaults for any system. This particular feature requires CMake 3.14+, but I put 3.19 because that's what I tested it on (you should never declare an untested minimum version in CMake, or anywhere else really).
Here's the CMakeLists.txt for the library:
cmake_minimum_required(VERSION 3.19)
project(test_lib)
add_library(test_lib lib.cpp)
add_library(test_lib::test_lib ALIAS test_lib)
target_include_directories(test_lib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
## Packaging rules to generate CMake exports
include(GNUInstallDirs)
install(TARGETS test_lib
EXPORT test_lib_targets
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
export(EXPORT test_lib_targets
NAMESPACE test_lib::
FILE "${CMAKE_INSTALL_LIBDIR}/cmake/test_lib/test_lib-config.cmake")
In the app's CMakeLists.txt:
cmake_minimum_required(VERSION 3.19)
project(test_app)
find_package(test_lib REQUIRED)
add_executable(test_app app.cpp)
target_link_libraries(test_app PRIVATE test_lib::test_lib)
At the command line, you would build them separately like so:
$ cmake -S lib -B lib/build -DCMAKE_BUILD_TYPE=Release
$ cmake --build lib/build
$ cmake -S app -B app/build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/path/to/lib/build
$ cmake --build app/build
If you want to go further and support installing your CMake targets scripts (rather than just exporting them from the build directory), look at the install(EXPORT) command: https://cmake.org/cmake/help/latest/command/install.html#export