conan-io / conan

Conan - The open-source C and C++ package manager
https://conan.io
MIT License
8.14k stars 970 forks source link

[bug] conan2 chooses incompatible version of catch2 dependency #14582

Open sakra opened 1 year ago

sakra commented 1 year ago

Environment details

Steps to reproduce

In my conanfile.txt I am declaring catch2 as a dependency:

catch2/[~3]

Upon running conan2 with the following command line:

conan.exe install --update --settings=build_type=Release -vverbose --build=missing --output-folder=. path/to/source

conan resolves the catch2 dependency in the following way:

======== Input profiles ========
Profile host:
[settings]
arch=x86_64
build_type=Release
compiler=msvc
compiler.cppstd=20
compiler.runtime=dynamic
compiler.runtime_type=Release
compiler.version=193
os=Windows
[conf]
tools.cmake.cmaketoolchain:generator=Ninja

Profile build:
[settings]
arch=x86_64
build_type=Release
compiler=msvc
compiler.cppstd=20
compiler.runtime=dynamic
compiler.runtime_type=Release
compiler.version=193
os=Windows

======== Computing necessary packages ========
...
catch2/3.4.0: Checking 3 compatible configurations:
catch2/3.4.0: '256d8e24411ffc8da15f42452ef405d7117905dc': compiler.cppstd=14
catch2/3.4.0: Current package revision is newer than the remote one
catch2/3.4.0: Main binary package 'b85eb13063d5472984e2a54461fae73d82585560' missing. Using compatible package '256d8e24411ffc8da15f42452ef405d7117905dc'
...

Conan2 selects the compatible package 256d8e24411ffc8da15f42452ef405d7117905dc which is not compatible at all. Since it is compiled for cppstd=14, it misses all the extensions that Catch2 adds when it is compiled for C++17 or newer. This make the build fail upon linking.

I tried to force conan2 to build catch2 from source by adding --build=[catch2*] to the command line. That however seems to be completely ignored by conan2.

Logs

No response

adnan-ali1 commented 1 year ago

There is a compatibility.py file which is adding these extra compatibilities which in your case are making it select the non-compatible package. You can check the file in the conan home directory and see the compatibility method documentation on conan 2.

You can try returning empty from the comptaibility method in that file and see how that works for you

sakra commented 1 year ago

@adnan-ali1 There is no compatibility.py in my .conana2 directory. Actually I have set up conan2 on a new Windows 11 machine and it uses default settings. Maybe its a problem in the catch2 recipe.

memsharded commented 1 year ago

Hi @sakra

The compatibility.py file will be automatically created the first time it is needed if it doesn't exist. So after a conan install invocation or similar the file should be there. If catch2/3.4.0: Checking 3 compatible configurations: is in the output, the file has been generated.

Thanks very much for the insights about catch2 not being compatible over different cppstd values. Building it from sources as you tried with the compiler.cppstd=17 setting should work, maybe it is the syntax? It shouldn't use brackets but something like --build="catch*" with quotes. Can you please try that?

As @adnan-ali1 suggested (thanks!) it is also possible to edit the compatibility.py, for example you could add a exception for packages that matches catch2, and avoid the fallback to older cppstd14.

Finally, we need to investigate this a bit further. It seems that we want packages to be able to inhibit themselves from certain possible compatibility.py behavior, but this doesn't seem possible at this moment. We might want to consider this a new feature.

sakra commented 1 year ago

Hi @memsharded

Thanks for your reply and clarifying the situation regarding compatibility.py. The file has indeed bin generated in the directory .conan2/extensions/plugins/compatibility.

Regarding forcing a manual build. I have tried the following combinations:

--build="catch2" does not force a build. --build="catch2*" does indeed force a build. --build="[catch2*]" does not force a build. Looking at the documentation, I do not see why this one does not work.

Regarding the cppstd compatibility:

I think that the default assumption that a dependency that has been compiled with an older standard as the one used by the current profile is compatible is too optimistic for C++. IMHO that default choice should be that compiler.cppstd must match exactly. Some packages may opt-out of this behavior.

memsharded commented 1 year ago

--build="catch2*" does indeed force a build.

The match is a fnmatch over the whole catch2/version reference. This is why catch2* works but not the others. If you provide the full catch2/version it should work too

I think that the default assumption that a dependency that has been compiled with an older standard as the one used by the current profile is compatible is too optimistic for C++. IMHO that default choice should be that compiler.cppstd must match exactly. Some packages may opt-out of this behavior.

We took this decission based on the ConanCenter behavior. Conan 1.X assumed cppstd compatibility implicitly, and it has been working quite good for years, for around 1500 different packages, without reports similar to this one. While we knew that it is possible that some packages can be using some variability based on the different standard, it doesn't seem the norm, so we decided to go with a default compatibility.py that assumes that. It seems to work so far because:

sakra commented 1 year ago

--build="catch2*" does indeed force a build.

The match is a fnmatch over the whole catch2/version reference. This is why catch2* works but not the others. If you provide the full catch2/version it should work too

I tried the following variants:

--build="catch2/3.4.0" forces a build --build="[catch2/*]" does not force a build --build="[catch2/3.4.0]" does not force a build

We took this decission based on the ConanCenter behavior. Conan 1.X assumed cppstd compatibility implicitly, and it has been working quite good for years, for around 1500 different packages, without reports similar to this one. While we knew that it is possible that some packages can be using some variability based on the different standard, it doesn't seem the norm, so we decided to go with a default compatibility.py that assumes that. It seems to work so far because:

* The usual thing is that teams using Conan in prod make sure they build their own binaries with the right settings that they want

* Deactivating the default `compatibility.py` to force exact matching is trivial

* It is a reasonable default to build binaries for ConanCenter. Multiplying the number of packages to build x5 is simply unfeasible. And forcing exact `cppstd` matching is bad, as it forces the majority of users to build from source most of the packages, taking too long.

I see. How would I have to change compatibility.py in order to force exact matching for catch2 only? Currently it looks like this:

def compatibility(conanfile):
    configs = cppstd_compat(conanfile)
    # TODO: Append more configurations for your custom compatibility rules
    return configs
memsharded commented 1 year ago

--build="[catch2/*]" does not force a build

Yes, the brackets are taken literally, don't use brackets at all. Maybe have you seen that somewhere in the docs? If that is the case, we should fix the docs, but the brackets should never be used in the --build patterns. The --build=[pattern] in the conan install -h cli help is not literal, probably a legacy when the --build didn't really require a value (defaulted to *)

I see. How would I have to change compatibility.py in order to force exact matching for catch2 only?

Something like:

def compatibility(conanfile):
    if conanfile.name == "catch2":
           return
    configs = cppstd_compat(conanfile)
    # TODO: Append more configurations for your custom compatibility rules
    return configs
sakra commented 1 year ago

--build="[catch2/*]" does not force a build

Yes, the brackets are taken literally, don't use brackets at all. Maybe have you seen that somewhere in the docs? If that is the case, we should fix the docs, but the brackets should never be used in the --build patterns. The --build=[pattern] in the conan install -h cli help is not literal, probably a legacy when the --build didn't really require a value (defaulted to *)

My bad. I interpreted the brackets as literally. I thing adding examples to the install documentation would be helpful here.

I see. How would I have to change compatibility.py in order to force exact matching for catch2 only?

Something like:

def compatibility(conanfile):
    if conanfile.name == "catch2":
           return
    configs = cppstd_compat(conanfile)
    # TODO: Append more configurations for your custom compatibility rules
    return configs

Thanks. That works.

memsharded commented 1 year ago

We re-opened this, as we want to discuss the above:

It seems that we want packages to be able to inhibit themselves from certain possible compatibility.py behavior, but this doesn't seem possible at this moment. We might want to consider this a new feature.