`FETCHCONTENT_TRY_FIND_PACKAGE_MODE` for indvidual package

As far as I know there isn’t a way to overwrite this for an individual package (i.e. like FETCHCONTENT_UPDATES_DISCONNECTED_<uppercaseName> allows individual control). But in the meantime is there a way to hack around it?

ExternalProject has a project-specific UPDATES_DISCONNECTED flag that could be populated using some mechanism. I don’t know if there’s a hacky way to plumb anything through right now though. Maybe just specify it along with the source arguments?

@craig.scott

Workaround 1:

Since FetchContent operates on a “first to define, wins” approach, you can call FetchContent_Declare() with whatever details you want early on. If this is something you want to do as a user, that can be in a file you include via PROJECT_TOP_LEVEL_INCLUDES, for example. That may mean you need to find what details to use for the other arguments in that call though, depending on how you want to define it.

Workaround 2:

Another approach is to define your own dependency provider. You could intercept FetchContent_MakeAvailable() calls and make it try find_package() yourself. This has the advantage that you get to see the FetchContent_Declare() arguments that were selected and then choose to act, rather than having to find them and lift them out to an earlier call to FetchContent_Declare() (the approach of workaround 1). One down side is you would have to check for FIND_PACKAGE_ARGS and form your call to find_package() using those, if present (not crazy hard). You also will end up seeing all calls to FetchContent_MakeAvailable() for all dependencies that go via that route, not just the one dependency that you really want to affect, so your logic would have to account for that.

So the whole idea behind having a FETCHCONTENT_TRY_FIND_PACKAGE_MODE variable for an individual package is to give the user the option to choose whether one particular package should take precedence and either be find_package or FetchContent depending on the setting of that value. For that I think workaround 2 makes the most sense. I will have to check how to implement that, how to enforce it and how to forward the variables.

After a quick research on dependency provider manual, I have found a dealbreaker:

The whole idea is to be able to overwrite the preference of that value, e.g. if a package is intended to always be used from the system installed one by the user, but everything else should be downloaded, then it should be FETCHCONTENT_TRY_FIND_PACKAGE_MODE_<package_name_upper> = ALWAYS. But rthis conflicts with the logic of the quoted statement.

The purpose of the FETCHCONTENT_TRY_FIND_PACKAGE_MODE variable is to provide a global switch for overall behavior. It is primarily intended for when you want to use a package manager and force all FetchContent_MakeAvailable() calls to try find_package() with your package manager first, even if the FetchContent_Declare() call didn’t include FIND_PACKAGE_ARGS in its arguments.

You are looking for a finer-grained control than that. You are in effect looking for a way to selectively override some of the arguments to whichever FetchContent_Declare() call ends up being used for a particular dependency. We have some precedent for that with the variables FETCHCONTENT_SOURCE_DIR_<uppercaseName> and FETCHCONTENT_UPDATE_DISCONNECTED_<uppercaseName>. The former is often used, the latter not so much. I’d like to avoid the added complexity of introducing a FETCHCONTENT_TRY_FIND_PACKAGE_MODE_<uppercaseName> if we can though.

For that use case, I would recommend Workaround 1 in my earlier comment. You are in effect overriding the whole call, wanting to say “always get this dependency from the system”. The FIND_PACKAGE_ARGS part of the arguments isn’t quite that, it is only “try to…”, not “must get from…”. Your use case is actually “ignore all the arguments and force finding this using find_package()”. That’s clearer if you override the whole call, which is precisely what Workaround 1 does.

Let’s consider the following package situations, and an extreme case where we want all to be setup:

  • User wants PkgA to always use system installed version, error if not found
  • User wants PkgB to use the git submodule source, ignore system installed
  • User wants PkgC to use the downloaded source, ignore system installed
  • User wants PkgD to try the system installed version and if not possible default to git submodule source
  • User wants PkgE to try the system installed version and if not possible default to downloaded source source

The project author designs all PkgA-E in a consistent manner, let’s say with FetchContent and FIND_PACKAGE_ARGS

I have thought about hacking something with FETCHCONTENT_SOURCE_DIR_<uppercaseName>, but couldn’t address these situations

Case A

  • FETCHCONTENT_TRY_FIND_PACKAGE_MODE = ALWAYS/OPT_IN
  • Fails for PkgC because system will take precedence
  • Maybe works for PkgA using FETCHCONTENT_UPDATE_DISCONNECTED_<uppercaseName>?

Case B

  • FETCHCONTENT_TRY_FIND_PACKAGE_MODE = NEVER
  • Fails for PkgA, PkgD, PkgE fail

Workaround 1:

So if I understand correctly that part is to override the other definitions e.g. overriding find_package using FetchContent_Declare(OVERRIDE_FIND_PACKAGE) and overriding FetchContent with FetchContent_Declare(FIND_PACKAGE_ARGS). At first glance that could appear to work, but the issue is considering the cases above, there will still be failures like in CaseA.

We could make a project specific flag to switch between different FetchContentDeclare definitions for each dependency, but is that really the best we could do? It makes the CMakeLists.txt file hard to follow and would make it hard to integrate with other projects.

Ok that is a very specific set of requirements. All of that logic could be implemented in a dependency provider where you intercept both find_package() and FetchContent_MakeAvailable() calls. You would add your desired behavior for each of the special cases and simply let all others go through to their default built-in handling.

Note that dependency providers can choose to ignore FETCHCONTENT_TRY_FIND_PACKAGE_MODE, so you are not constrained by the setting of that variable. Dependency providers are essentially free to ignore just about everything about a dependency if they so wish, so you should be able to get whatever behavior you’re aiming for.

Note that dependency providers can choose to ignore FETCHCONTENT_TRY_FIND_PACKAGE_MODE, so you are not constrained by the setting of that variable.

How does this one work? What I am concerned is that when the user sets FETCHCONTENT_TRY_FIND_PACKAGE_MODE = NEVER then cmake does not pass FIND_PACKAGE_ARGS as indicated by the documentation so I wouldn’t be able to cover all situations in that case.

In your dependency provider, you control the find_package() arguments. Only PkgD and PkgE in your example would be needing these. You should be able to decide what find_package() arguments you want to use in this case, you are being very specific about what you want to get from where. You should be able to specify the precise details you want to use to look in your system for those two. While I acknowledge that the project might be providing arguments that you might otherwise want to use, you should have little trouble working out what those arguments would have been and updating your dependency provider accordingly. Remember, a dependency provider is defined by the user with full knowledge of what the project is and would request, so you can take that knowledge into however you implement your provider.

1 Like