Third party package managers like vcpkg, conan, etc. are dedicated to providing dependencies. They typically have “recipes” for how to provide each dependency and they effectively allow a project to say “give me this” and leave it up to the package manager to take care of the details of how that’s done. That’s a gross over-simplification, but it’s close enough for the purpose of discussion here. Package managers are generally well-suited to publicly available dependencies, but some do support private dependencies as well. Users are typically responsible for making the package manager available on their system in some way.
FetchContent provides a way for a project to say “give me this, and if not provided by other means, use this method to get it”. With CMake 3.23 and earlier, it was more like “give me this and use this method to get it”, but CMake 3.24 added support for integration with
find_package() and dependency providers, so it now gives you the ability to try a package manager first, and if that doesn’t supply it, fall back to the download-and-build-from-source details.
CMake itself doesn’t get involved in defining “recipes” for dependencies for either method. For package managers, the package managers provide the “how”. For FetchContent, the project provides the “how” (with CMake 3.24 or later, it’s more like the “how, if nothing else provides it”).
FetchContent is often a good fit within organisations where they want to bring together multiple internal projects under active development into one build. Developers don’t have to install an extra tool and it has direct support for working across multiple repositories at once. With FetchContent, a dependency is only added to the build if something asks for it, which means you can use CMake cache variables to turn features on and off, and the build will not ask for a dependency that isn’t used.
Package managers take an additional step to set up, which some people don’t like, but others don’t mind. They usually require you to adapt your workflow in some way to accommodate how they expect the build to be done. These adjustments can range from quite minor to rather disruptive. Package managers can be a good fit for bringing in mature dependencies, especially popular ones that many projects use. The maintainers of these tools often put in a lot of work to fix and patch dependencies to make them more reliable to use, which can save you a lot of work (if they get it right!). People often underestimate the value these tools provide by curating and managing how the dependencies are built. A common weakness of package managers is that they usually want you to define your dependencies up front, so you can end up pulling in dependencies you may not actually need.
My general advice would be to use FetchContent for internal dependencies if they are being actively developed. If package managers don’t present any major issues for your situation, prefer to use those to provide public dependencies. If you are inside an organisation and you have some internal projects that are relatively mature, consider whether a package manager might still be appropriate for making those available to others within the company. New features available from CMake 3.24 should help with transitioning projects from FetchContent-provided to package manager-provided once a project matures, should that be desired.
The following discussion may also be relevant (I intend to follow-up there with further comments soon): https://discourse.cmake.org/t/fetchcontent-in-dependency-management