Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CMakeToolchain: proper support of find_*() & include() commands #10186

Merged

Conversation

SpaceIm
Copy link
Contributor

@SpaceIm SpaceIm commented Dec 16, 2021

Changelog: Fix: Improved CMakeToolchain robustness regarding find_file, find_path and find_program commands allowing better cross-build scenarios and better differentiation of the right context where to get, for example, executables (build vs host).
Docs: conan-io/docs#2383

  • In find_program(), avoid to find executables of host context before executables of build context.

  • support all cross-build scenario (hopefully).

  • support include() of CMake modules coming from build context.

  • remove CMakeToolchain.find_builddirs (doesn't make sense anymore).

  • Refer to the issue that supports this Pull Request.

  • If the issue has missing info, explain the purpose/use case/pain/need that covers this Pull Request.

  • I've read the Contributing guide.

  • I've followed the PEP8 style guides for Python code.

  • I've opened another PR in the Conan docs repo to the develop branch, documenting this one.

Note: By default this PR will skip the slower tests and will use a limited set of python versions. Check here how to increase the testing level by writing some tags in the current PR body text.

closes #9427
closes #9735
closes #10167

- avoid to find host executables in find_program()
- support cross-build scenario
- support include() of CMake modules coming from build context
@CLAassistant
Copy link

CLAassistant commented Dec 16, 2021

CLA assistant check
All committers have signed the CLA.

@SpaceIm
Copy link
Contributor Author

SpaceIm commented Dec 16, 2021

Two comments regarding existing code I didn't change:

  • I don't understand what is the purpose of android part, it seems to specific and dangerous since it manipulates CMAKE_FIND_ROOT_PATH which is the top precedence in all paths checked by find_*() commands
    => ok, I've removed this part, it's the usual issue due to CMAKE_FIND_ROOT_PATH_MODE_* set to ONLY.
  • CMAKE_FIND_PACKAGE_PREFER_CONFIG can be removed (another PR?) now that CMakeDeps can generate Find files (cmake_find_mode property).

@SpaceIm
Copy link
Contributor Author

SpaceIm commented Dec 16, 2021

tests fail because:

  • they want to be able to find some config files in the root package folder, which is something I don't want find_package() can do, because adding root package folder to CMAKE_PREFIX_PATH has too many side effects.
    Actually, there should be 2 more properties in cpp_info of recipes: cmake_module_path (folders of packaged CMake module and Find files) & cmake_prefix_path (folders of packaged pkgconfig & CMake Config files), empty by default, and CMAKE_MODULE_PATH & CMAKE_PREFIX_PATH should be populated with these paths respectively (+ CMAKE_CURRENT_LIST_DIR). Indeed builddirs is too generic and semantically unrelated to CMAKE_MODULE_PATH & CMAKE_PREFIX_PATH.
    Anyway, here I expect folders of packaged CMake module and config files to be added explicitly in builddirs.
  • I have removed find_builddir logic, but not related test. I think this switch won't make sense after this PR.

Maybe I should have added the tests I want to succeed in the first place (same recipe in requirements & build requirements with libs & tools packaged, discovery of module files from build requirements).

Copy link
Member

@memsharded memsharded left a comment

Choose a reason for hiding this comment

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

I have my concerns about the extension of this to "dependencies.build". Things liks libs, headers and frameworks probably shouldn't be added by default. And the executables and shared libs don't need to be added to the path, because they will already be in the PATH and LD_LIBRARY_PATH, because they are put there by the VirtualBuildEnv (which is a bit more general solution, as it works for every build system)

conan/tools/cmake/toolchain.py Show resolved Hide resolved
@memsharded memsharded requested a review from lasote December 17, 2021 00:08
@memsharded memsharded added this to the 1.44 milestone Dec 17, 2021
conan/tools/cmake/toolchain.py Show resolved Hide resolved
@@ -425,34 +439,64 @@ def context(self):
if prefer_config is not None and prefer_config.lower() in ("false", "0", "off"):
find_package_prefer_config = "OFF"

os_ = self._conanfile.settings.get_safe("os")
android_prefix = "${CMAKE_CURRENT_LIST_DIR}" if os_ == "Android" else None
Copy link
Member

Choose a reason for hiding this comment

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

This was necessary at some point, to have the Android build finding the xxx-config.cmake by Conan. Maybe it is no longer needed, but it is difficult to know, because we are not running yet Android build tests in our CI

Copy link
Contributor Author

@SpaceIm SpaceIm Dec 17, 2021

Choose a reason for hiding this comment

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

Yes, it's the same issue than cross-build to iOS/tvOS/watchOS, but 2 different solutions have been implemented. Here it's fixed by forcing CMAKE_FIND_ROOT_PATH_MODE_PACKAGE to "BOTH" (same fix than iOS/tvOS/watchOS).

Of course, the best solution would be to not have to manipulate CMAKE_FIND_ROOT_PATH_MODE_* but other solutions raise other issues (conflicts between host & build context), and I think this one is harmless compare to other solutions.

@SpaceIm SpaceIm force-pushed the feature/cmake-toolchain-robust-find-commands branch from f2a242d to 66a93d8 Compare December 17, 2021 08:56
CMAKE_MODULE_PATH has no side effects in find_program()/find_library()/find_file()/file_path(), so it's fine.
What should be avoided is to add root package folder to CMAKE_PREFIX_PATH, otherwise executables of host context would have precedence over executables of build context in find_program() calls.
@SpaceIm SpaceIm force-pushed the feature/cmake-toolchain-robust-find-commands branch from 66a93d8 to 75812f4 Compare December 17, 2021 08:57
@SpaceIm
Copy link
Contributor Author

SpaceIm commented Dec 17, 2021

TODO:

  • add test to check that executables of build context are found before executables of host context in find_program() (native & cross build, recipe in build requirement only & recipe in requirements and build requirements).
  • add test to check that CMake modules of build context are accessible with include() (native & cross build).

@SpaceIm SpaceIm force-pushed the feature/cmake-toolchain-robust-find-commands branch from 132f23f to e34655a Compare December 19, 2021 01:23
Copy link
Contributor

@lasote lasote left a comment

Choose a reason for hiding this comment

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

I understand these issues (and agree with them) and I see a lot of value in this PR to understand the issues we currently have. But still not convinced about the way to resolve them. Should we use more properties to declare specific things like config file paths? and maybe more things?

Should we do this in Conan 1.X even if maybe the better fix (not either sure) is to change the cppinfo model or defaults?

Also, I think this PR does some "tricks" like changing the ROOT_PATH_MODE variables and filling CMAKE_FIND_ROOT_PATH, probably there is not single answer for all cases, I'm afraid it might depend on every project/package/toolchain, in some cases, I would need to find some things in my packages, sometimes I don't, sometimes from build, sometimes from host...All these questions should be answered by the user, the challenge is how to do it understandable and maintainable.

conan/tools/cmake/toolchain.py Outdated Show resolved Hide resolved
conan/tools/cmake/toolchain.py Outdated Show resolved Hide resolved
conan/tools/cmake/toolchain.py Outdated Show resolved Hide resolved
endif()

if(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE STREQUAL "ONLY")
set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} {{ generators_folder }} {{ host_build_paths_noroot }})
Copy link
Contributor

Choose a reason for hiding this comment

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

is this trick necessary? why not set BOTH? I'm sure there is an explanation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, the toolchain could force CMAKE_FIND_ROOT_PATH_MODE_PACKAGE to BOTH. But I would like to avoid to override this variable if set by user toolchain.

Copy link
Contributor

@lasote lasote Dec 22, 2021

Choose a reason for hiding this comment

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

I'm wondering if the user toolchain is setting CMAKE_FIND_ROOT_PATH_MODE_PACKAGE STREQUAL "ONLY", wouldn't be mostly the same modifying the CMAKE_FIND_ROOT_PATH?. Why is that ok but not overriding the CMAKE_FIND_ROOT_PATH_MODE_PACKAGE variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure to understand the questions. Would you like to set CMAKE_FIND_ROOT_PATH with generator folder and non-root builddirs of requires, regardless of CMAKE_FIND_ROOT_PATH_MODE_PACKAGE value?

Copy link
Contributor

Choose a reason for hiding this comment

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

No, just saying that you are protecting the user set value from CMAKE_FIND_ROOT_PATH_MODE_PACKAGE because I would like to avoid to override this variable if set by user toolchain. but then you are modifying the CMAKE_FIND_ROOT_PATH, so I'm wondering if it is not kind of "cheating".

Copy link
Contributor

Choose a reason for hiding this comment

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

In other words, why is so important to keep the user value of CMAKE_FIND_ROOT_PATH_MODE_PACKAGE (and others)?

Copy link
Contributor Author

@SpaceIm SpaceIm Dec 28, 2021

Choose a reason for hiding this comment

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

Ok, so when CMAKE_FIND_ROOT_PATH_MODE_* is ONLY, you have no other choice than manipulating CMAKE_FIND_ROOT_PATH because all other variables are ignored. CMAKE_FIND_ROOT_PATH is like CMAKE_PREFIX_PATH but with higher precedence, so no it's not cheating.

Why is it important to honor CMAKE_FIND_ROOT_PATH_MODE_* of users? Principle of least surprise.
But as I said before, if ONLY is overridden by BOTH, it should not break consumers (other proposal in #10186 (comment)), because system paths have the lowest priority in search paths of find_*() commands, and BOTH still look into CMAKE_FIND_ROOT_PATH first if the user has injected some stuff not managed by conan.

Copy link
Contributor

Choose a reason for hiding this comment

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

Then I would simplify and just override to BOTH.

Ok, so when CMAKE_FIND_ROOT_PATH_MODE_* is ONLY, you have no other choice than manipulating CMAKE_FIND_ROOT_PATH because all other variables are ignored. CMAKE_FIND_ROOT_PATH is like CMAKE_PREFIX_PATH but with higher precedence, so no it's not cheating.

Yes, I got that, but:

Why is it important to honor CMAKE_FIND_ROOT_PATH_MODE_* of users? Principle of least surprise.

Not sure that changing CMAKE_FIND_ROOT_PATH is least surprise either, so I vote for simplifying and just overriding to BOTH when cross_build.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

simplified in 1e89fe9

CMAKE_FIND_ROOT_PATH_MODE_* of user honored unless it's ONLY.

if(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE STREQUAL "ONLY")
set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} {{ generators_folder }} {{ host_build_paths_noroot }})
endif()
if(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM STREQUAL "NEVER")
Copy link
Contributor

Choose a reason for hiding this comment

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

Same question for all "ifs". Sounds very complex :(

Copy link
Contributor Author

@SpaceIm SpaceIm Dec 23, 2021

Choose a reason for hiding this comment

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

Yes it's complex, I'll try to explain the all logic when I have time, it's not obvious. I try to address #10186 (comment).
If you want something simple, I can fallback to something like this:

    template = textwrap.dedent("""
        ...

        {% if cross_building %}
        if(NOT DEFINED CMAKE_FIND_ROOT_PATH_MODE_PACKAGE OR CMAKE_FIND_ROOT_PATH_MODE_PACKAGE STREQUAL "ONLY")
            set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE "BOTH")
        endif()
        if(NOT DEFINED CMAKE_FIND_ROOT_PATH_MODE_PROGRAM OR CMAKE_FIND_ROOT_PATH_MODE_PROGRAM STREQUAL "ONLY")
            set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM "BOTH")
        endif()
        if(NOT DEFINED CMAKE_FIND_ROOT_PATH_MODE_LIBRARY OR CMAKE_FIND_ROOT_PATH_MODE_LIBRARY STREQUAL "ONLY")
            set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY "BOTH")
        endif()
        {% if is_apple %}
        if(NOT DEFINED CMAKE_FIND_ROOT_PATH_MODE_FRAMEWORK OR CMAKE_FIND_ROOT_PATH_MODE_FRAMEWORK STREQUAL "ONLY")
            set(CMAKE_FIND_ROOT_PATH_MODE_FRAMEWORK "BOTH")
        endif()
        {% endif %}
        if(NOT DEFINED CMAKE_FIND_ROOT_PATH_MODE_INCLUDE OR CMAKE_FIND_ROOT_PATH_MODE_INCLUDE STREQUAL "ONLY")
            set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE "BOTH")
        endif()
        {% endif %}
    """)

conan/tools/cmake/toolchain.py Outdated Show resolved Hide resolved
@SpaceIm
Copy link
Contributor Author

SpaceIm commented Jan 14, 2022

some conflicts to solve

@memsharded
Copy link
Member

I am contributing a fix to the broken test in 6bb0be8.

The test was failing because we added this new test to run CMake with -werror -warn, and this was detecting a bunch of warnings because of usage of uniniatilized variables. I am changing those lines to list(PREPEND ...) to avoid that warning, and I also took a chance and splitted the CMAKE_MODULE_PATH and CMAKE_PREFIX_PATH in multiple lines, adding a comment to each one. I think it will be useful for readers trying to understand what the toolchain is doing and why is adding some folders to those variables (the explanation in each line comment might be poor, it would be great to add the full explanation: "adding to CMAKE_PREFIX_PATH so we can locate the ....config.cmake inside, etc, etc")

@memsharded
Copy link
Member

I had to push a few changes more, after merging latest "develop" state in 5d19b4f.

The most important thing that changed is that now the cppinfo directories are absolute, everything was almost good, except the comparison of builddirs == "", that was broken. Now it seems to be fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants