conan: Incorrect handling of MSVC versions

I am using Conan v1.7.4.

I’ve created a static library Conan package using VS 2017 15.7 and updated my Visual Studio to 15.8. After the update, Conan failed to detect a change in the compiler version and did not perform recompilation of the static library. Instead, it just downloaded the same package that was built with VS 2017 15.7. However, when linking to that package from VS 2017 15.8, I am getting following linker error:

LINK : fatal error C1047: The object or library file 'C:\Users\dodo\.conan\data\LogAndTimer\1.0.1\microblink\stable\package\8b2772668ccbf879d34a70a9d54c78ce70f38a1b\lib\LogAndTimer.lib' was created with an older compiler than other objects; rebuild old objects and libraries
2>LINK : fatal error LNK1257: code generation failed

However, the debug build does work (details how to reproduce are below).

I think the problem is in how Conan handles MSVC versions - instead of using the actual compiler version (CMake detects MSVC 19.14.26431.0 in VS 2017 15.7 and MSVC 19.15.26729.0 in VS 2017 15.8), it uses the version of Visual Studio (VS 2017 is compiler version 15, according to Conan).

I suggest that Conan should use the MSVC compiler version under compiler.version setting and additionally have compiler.vs_version for Visual Studio version. The reason for that is that sometimes libraries built with one version of MSVC are not compatible with later versions of MSVC, especially if you use link time code generation flag (/LTCG link flag and /GL compile flag).

How to reproduce

  • Install Visual Studio 2017, but not the latest update (at the time of writing, this is 15.8.4). For example, use VS 2017 15.7.x or 15.6.x.
  • Create a conan package that contains a single static library. Make sure that Release build type of that package is compiled with /GL compile flag.
  • Create an application that consumes that static library and make sure it works in Release build type (the app should be linked with /LTCG linker flag).
  • Update Visual Studio 2017 to the latest version (15.8.4 at the time of writing).
  • Build your application

Expected behaviour

  • conan install after updating the VS should calculate different hash for the library and should rebuild it, thus making sure application linking will succeed

Actual behaviour

  • Conan does not detect version change in the MSVC compiler and uses the incompatible binary, thus causing linker error described above.

Now, the main question is - could this be fixed without breaking existing conan VS packages, or we need to wait for Conan 2.0 (and disable LTCG until this issue gets fixed).

Also, as a side question, is there a possibility to configure Conan to trigger rebuild of libraries even when a minor/bugfix version of the compiler changes, regardless of the compiler being used (e.g. if we update GCC from 8.1.0 to 8.1.1, we would like to rebuild all our binaries, even though they are link-compatible). This would get us the benefits of always having binaries built with the latest compiler to get the latest bugfixes and performance improvements - this benefit is also mentioned in conan documentation:

What happens if we have a library that we can be built with GCC 4.8 and will preserve the ABI compatibility with GCC 4.9? (This kind of compatibility is easier to achieve for example for pure C libraries).

Although it could be argued that it is worth rebuilding with 4.9 too -to get fixes and performance improvements-. Let’s suppose that we don’t want to create 2 different binaries, but just a single built with GCC 4.8 which also needs to be compatible for GCC 4.9 installations.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 20
  • Comments: 52 (48 by maintainers)

Most upvoted comments

Oops, Microsoft did it again 🤦‍♂️

some notes:

  • probably, using compiler version (_MSC_VER or _MSC_FULL_VER) better aligns with other compilers like GCC, Clang, where we use compiler version as well. it’s better to be consistent. also, in this case compiler.version isn’t misleading and self-descriptive.

  • need to carefully check LTCG and LTO use cases, may users on slack and GitHub reported that even minor version matters for compatibility in these modes

  • some notes from Stephan T. Lavavej (Microsoft STL maintainer):

    Binary compatibility between the 2015, 2017, and 2019 toolsets comes with a couple of restrictions: no LTCG (which is a non-issue here), and the final link must be performed by a toolset that’s as new as (or newer than) all of the object files and libraries being linked. For example, you can’t take VS 2019 16.1 (v142) compiled libraries, and perform a final link with VS 2015 RTM (v140). And in practice, I am aware of at least one build break that can be encountered when attempting this (as we added a vectorized implementation of std::reverse that requires separately compiled support to be dragged in during the final link).

  • AFAIK there are/were ways to install MSVC compiler without Visual Studio, e.g. with DDK (Driver Development Kit), WDK (Windows Driver Kit), Windows SDK, might be some others. I am not sure, if toolsets are available in these cases at all.

Can you please clarify with an example the actual settings.yml that you would use?

I’ve actually described the settings.yml I use in this comment above.

I am proposing the 140, 141, 142, as they are aligned with the default binary compatibility model: binaries are compatible by default by the toolset version, not the full compiler version.

This seems reasonable for the default VS settings, where LTO is disabled. However, this is then not the msvc compiler - it’s visual studio toolset - that should be different from msvc compiler.

Then, for users that want finer control of the binaries, they can use the mscver.

Here is where I would actually treat that as the separate compiler, for simplicity, not as the mscver subsetting. If you choose to use msvc compiler instead of visual studio toolset then you should probably have different binaries. Yes, sometimes they might match, but this is similar to how some GCC vs Clang binaries are compatible.

It is not possible (maybe it is, but seems challenging), to have more than 1 mscver installed at the same time in one machine. So this is not actionable at all, it is just information about the version, but nothing that Conan can use to select one or other.

Yes, it’s possible and we have multiple msvc versions installed on our CI machines. I’ve even described in the comment linked above how you can force conan to select correct msvc version.

Maybe you mean just to change the names and use 140 => 19.0, 141 => 19.1, 142 => 19.2? And then leave the rest as a subsetting? If that is the case, yes, totally good for me.

For msvc compiler, the versions should definitely be in line with what compiler reports (19.25, 19.26, 19.27, … ). The 140, 141, … versions are in line with visual studio toolset and should not be used for msvc compiler.

So, to conclude:

  • the msvc compiler should have versioning in line with reported version of cl.exe and should not be related to version of Visual Studio nor it should require VS installation, imho
  • the visual studio toolset can have the versioning in line with versions of VS toolsets and can be used by people that don’t want fine-grained control over msvc versions
  • the binaries produced by msvc and visual studio should in general have different ID and not be treated as compatible, however methods used for icc may be used to ensure compatibility between msvc and visual studio builds of the same package.

If all that seems too complicated (I’d guess most people will get confused by having both msvc and visual studio “compilers”), and if we just want to have only one msvc compiler, then I would definitely vote for the first option (msvc, not visual studio). The versions should then be in sync with versions reported by cl.exe and for general case (when LTO is not needed), the version should be parsed from major number (i.e. 19.1, 19.2, …) and if someone needs LTO and fine-grained control, they can easily add versions 19.16, 19.17, 19.25, 19.26, …) to their settings.yml.

This is not without precedent: the default settings.yml, shipped by conan, already has such versioning scheme for GCC: there is version 9, but also versions 9.1, 9.2 and 9.3. Version 9 is OK for general case, but if you use LTO, you need to use 9.1, 9.2 and 9.3.

We use the same trick also for Apple Clang - without LTO all compilers shipped with Xcode 11 produce link-compatible binaries, but if you enable LTO, there are actually two different versions of clang in Xcode 11 series: 11.0.0 and 11.0.3, which are not LTO-link compatible.

So, why not doing the same with MSVC as well?

We will look at this before 2.0 to see what can we introduce in 1.X. We understand this is important.

If anything, it could make sense to introduce a new compiler in the setting, to replace the old one, but without replacing.

I like the idea - introduce new compiler msvc in Conan 1.9.x, but keep Visual Studio a default choice for Windows with the same behaviour as current. The msvc would behave in the same way as gcc and clang. Then, in v2.0 switch the windows default from Visual Studio to msvc, but keep Visual Studio as a legacy option. Finally, remove Visual Studio in Conan v3.0 and keep it only as the generator, not as the “compiler”.

I think this should be accompanied by appropriate updates in cmake-conan project, but I think this should not be difficult (such a change is even within my capabilities 😛 )

The introduction of msvc in v1.9.0 would enable those that have the same problem as we do to circumvent them almost immediately, without waiting for breaking change - although using prebuilt packages from bintray would require some additional code in consumer’s conanfiles** (let’s discuss that as well - I think that would also require some backwards-compatible additions to conanfiles).

Then, with v2.0 users that were already using msvc instead of Visual Studio compiler would not have a breaking change and v2.x series would give a plenty of time to everyone to switch their packages to msvc compiler. In v3.0 Visual Studio could be removed just to reduce the code bloat, if necessary. If not, it can remain eternally for backwards compatibility if Conan adopts policies in the way similar to how CMake does***).

** Here I generally think of a way for the consumer to “override” the calculation of the package ID of the package being consumed. The idea here is for the consumer to be able to “tell” Conan that if it uses, e.g. compiler msvc 19.15 that it can safely consume a specific package built for Visual Studio 15 (something like how you tell the versioning schema) - to avoid rebuilding the package for compiler the author of the package didn’t anticipate. This is especially useful for binary-only packages built with earlier versions of Conan.

*** I think policies are a good way to keep backward compatibility while providing new features. Yes, it causes bloatware, but I think it is essential for package management software to be as backwards-compatible as possible. Also, as I already mentioned, a recipe should be able to specify the minimum required version of Conan for a recipe to be parsed. This would enable much easier development of packages that support wide range of Conan versions.

OK, I solved this problem for our use cases. The solution (ab)uses the fact that conan will not set vcvars if they are already set and assumes that developers will never manually invoke conan install or conan create on Windows systems and that all packages use CMake build system. This assumption is true for us, as we use cmake helper that automatically invokes conan install during cmake configuration.

Here is how I did it:

First, I’ve added a msvc compiler to my settings.yml. The entry looks like this:

    msvc:
        runtime: [MD, MT, MTd, MDd]
        version: ["19.25", "19.26", "19.27"]
        cppstd: [None, 14, 17, 20]

Then, I’ve created several conan profiles:

msvc-generic:

[settings]
os=Windows
os_build=Windows
arch_build=x86_64
arch=x86_64
compiler=msvc
build_type=Release

msvc-19.27:

include(msvc-generic)

[settings]
compiler.version=19.27

[env]
CONAN_CMAKE_GENERATOR = Ninja

vs-19.27:

include(msvc-generic)

[settings]
compiler.version=19.27

[env]
CONAN_CMAKE_GENERATOR = Visual Studio 16

and similar for every MSVC version (19.25, 19.26 and 19.27).

The real magic happens here. While configuring the project and deciding about command line parameters to be given to conan install, cmake script first obtains the version of MSVC compiler currently used and decides to use vs-<version> if Visual Studio generator is used and msvc-<version> if not (in our case it’s Ninja, but it should also work with Makefiles).

The idea is the following:

  • if Ninja generator is used, then this means that vcvars are definitely set for the current shell, otherwise, Ninja would not be able to find MSVC compiler. This means that the current build will be definitely built with the correct version of the MSVC compiler, but by using msvc-<version> profile we ensure that conan will also use Ninja generator for building any packages that need to be built by dependency. This abuses the fact that conan will not modify vcvars if they are already set. This then ensures that the entire dependency graph will be built with the same version of MSVC, enabling us to enable stuff like LTO.
  • if Visual Studio generator is used, then we cannot assume that vcvars are set for the current shell (they may or may not be). However, Visual Studio generator will always use the latest version of MSVC, as it does not support selecting an older version of MSVC (it’s a limitation of Visual Studio, not CMake). Therefore, here we will use vs-<version> profile to ensure that conan will also use Visual Studio generator for building any packages that need to be built by dependency. By having the same compiler.version set in that profile as well we will ensure that correct packages are selected in the dependency graph (regardless if they were already built before or not).

Of course, this assumes that developers will not manually invoke conan install/create, as invoking conan install/create conanfile.py -pr vs-19.25 while having Visual Studio updated to v16.7 will create invalid packages (the package will be built with MSVC 19.27, but conan package will advertise 19.25, which is wrong).

Similar could happen (I didn’t test) if invoking conan install/create conanfile.py -pr msvc-19.25 from the shell where vcvars are initialized to msvc 19.27 (although this line of code may catch that, but I’m not entirely sure).

Fortunately, the profiles I described above don’t have compiler.runtime set, and conan automatically fills the runtime information only for Visual Studio and intel compilers, so if you accidentally invoke conan install/create manually, you will get an error that the compiler.runtime setting is missing 🎉 . This is hacky, but it works 😛

On the other hand, the Jenkins that builds our packages is configured to always use Ninja generator and set correct vcvars environment before invoking any Conan commands.

In general, I would suggest that conan gets native support for MSVC compiler. The implementation may be based on this solution:

  • if build system is cmake and generator is makefile/ninja/anything similar or build system is vcvars-dependent, let conan automatically set correct vcvars. I see that Conan’s vcvars_command function already supports that
  • if build system is MSBuild or cmake with Visual Studio generator and selected compiler version does not match the latest MSVC version that will be used by MSBuild/Visual Studio, raise ConanException and fail the build
  • add support for runtime filling just like is done for intel and Visual Studio compilers

So, from the twit joke above we have this table, do we all agree the first column should we use? @DoDoENT @sigiesec @psiha @memsharded @claasd image

We need to discuss what to do with the CMake generator argument if the IDE is not available as a setting, probably we should try to deduce (or try) from the compiler version but let the user override it with an environment variable/conan.conf?

Hi all! Thanks for the feedback. About the compatibility between the old Visual Studio setting and the new one, it is not that easy, we would need to think about it carefully but it is not going to be solved in the firsts versions. By the way, it cannot be in 1.8, it is already packaged and struggling to release it, so anyway this will wait for 1.9. Sorry.

I am proposing this:

msvc:
            version: ["19.0",
                      "19.1", "19.10", "19.11", "19.12", "19.13", "19.14", "19.15", "19.16",
                      "19.2", "19.20", "19.21", "19.22", "19.23", "19.24", "19.25", "19.26", "19.27", "19.28"]
            runtime: [static, dynamic]
            runtime_type: [Debug, Release]
            cppstd: [None, 14, 17, 20]

The runtime_type will be filled automatically based on build_type, but the user always has the possibility of explicitly defining it. I am testing it for the CMake toolchain, seem to be a good approach so far.

Changing MT, MTd, MDd, MD for “static”, “dynamic” for the runtime, to avoid incompatible combinations with build_type.

Actually, we are building our windows dev packages in release mode, but with MDd runtime. This enables both optimizations of the binary and the debug runtime (iterator checks and other). This is what we put in a “Debug” version of our windows conan package.

I don’t think we can enforce the combination of runtime and build_type. Tons of companies build .dlls with MT. It works fine for so many people, they don’t care that it’s not safe or not recommended.

I have updated https://github.com/conan-io/conan/pull/8201 to implement:

 msvc:
            version: ["19.0",
                      "19.1", "19.10", "19.11", "19.12", "19.13", "19.14", "19.15", "19.16",
                      "19.2", "19.20", "19.21", "19.22", "19.23", "19.24", "19.25", "19.26", "19.27", "19.28"]
            runtime: [static, dynamic]
            cppstd: [None, 14, 17, 20]

The mapping to default VS versions (to call cmake -G “Generator”) is done dropping the latest digit if existing. This is basically the same approach done for gcc and clang. Auto-detect (when this happens, Conan 2.0) will resolve to 19.1, 19.2, assuming that binary compatibility, and letting users to opt-in into the full versions for LTO and other binary-incompatible issues.

I still need to figure out the runtime and work on the migration plan for package_id compatibility with existing Visual Studio compiler.

This sounds reasonable for me as long as there will be a guarantee that each MSVC compiler version will have its own toolset, i.e. that there will be no MSVC compiler updates (at least incompatible ones, in terms of LTO) without changing the toolset version.

Also, we will also need to update cmake-conan to correctly infer toolset from MSVC version detected by the CMake.

We are going to make a proposal following the lines of all that has been discussed here: it should be a new compiler for Conan v1.x that will use the toolset version. As it is more common to hear about toolset v140, v142 IMO we should choose the toolset over the _MSC_VER variable that is available as a macro provided by the compiler, anyway there is bi-univocal correspondence between them.

The first approach (or the settings.yml provided by default by Conan) should consider only the first number in the minor version as it should be compatible and it is not so easy to have several minor versions installed at the same time (also this), nevertheless as there are bugs from time to time the implementation should allow an advanced user to use full version number (v14.11 -vs- v14.12) in their custom settings.yml. Note.- There is a patch version for toolsets too (as it is _MSC_FULL_VER, but IMHO it is too much to consider at this moment, although if possible, the implementation should not forbid using it.

The profile for this new compiler will contain the toolset version and Conan should be able to go from the toolset version to the compiler that is able to generate the binaries or even to the IDE version if we need to generate the solution using CMake for example. We need to write a POC about this first, but these are some insights to build a POC for this:

  • Using vswhere (at a known path since 15.2) we can ask for an installation using some components: vswhere [-latest] -requires <component-toolset>, where each toolset has its own requirement name:

    • MSVC v141: Microsoft.VisualStudio.Component.VC.v141.x86.x64
    • MSVC v142 (14.20): Microsoft.VisualStudio.Component.VC.14.20.x86.x64
    • MSVC v142 (14.21): Microsoft.VisualStudio.Component.VC.14.21.x86.x64
    • v140: Microsoft.VisualStudio.Component.VC.140

    It could be easier, but at least it is documented.

  • To be able to choose an older toolset (corresponding to v120, v100,…) you need to install the corresponding VS version (need to double-check). This is great, as looking for the compiler/IDE for an old toolset version will require to look for the IDE itself and ⬇️

  • For legacy VS versions vswhere (not installed) it is able to find Visual Studio 2010 and newer using -legacy flag (we cannot use the -requires flag, but we are able to find a suitable compiler for the toolset requested). At least it retrieves the install path.

  • Also interesting (maybe we can use it from Python): https://github.com/microsoft/vs-setup-samples

With this approach we should be able to go back to VS 2010, older versions could require other approaches, but I haven’t looked for information about them, sorry.

Once we have the IDE (and the toolset version) we know the CMake generator name and we can use the cmake ... -G "Visual Studio Studio ..." -T <toolset> or we can call one of the vcvars scripts with the proper arguments,… or find the MSBuild

Probably it is not as easy as it looks like, but I think it is feasible and there are good foundations to start building on top of it.

Thanks everyone for the feedback


There is still something else I would like to mention (and probably discard): what about the Windows SDK? is it something we want/need to consider? And what about the Framework .NET version, can it generate incompatible binaries? Probably these are just subsettings that the user building binaries that depend on them should take into account, but not for the default settings.yml


Ping @DoDoENT @sigiesec @claasd @psiha @DaniCybereason for a final round of feedback before we start walking this path. Thanks!

To my knowledge, MSVC relies much more on LTCG (and PGO) to generate optimal code than other compilers (in particular gcc and clang) do, even though they also provide comparable features. But as @DoDoENT said, maybe not everyone is aware of that. I have found build files of many open source libraries that were not enabled for LTCG.

MS never guaranteed binary compatibility for .lib files between different toolset versions, but in many cases it worked anyway (or at least it did not obviously not work). However, before Visual Studio 2017 there haven’t been multiple toolset versions for the same major Visual Studio version, or at least they did not exhibit such obvious incompatibilities.

Also, not all users of Visual Studio will have already migrated to Visual Studio 2017. This is also true for us. We have prepared new versions of several components that are built with the VS2017 toolsets already, and during development we already stumbled upon this problem where a mismatch between the toolset used on the CI and locally caused problems. But the products using these components still use VS2015 as of now. I am somewhat scared of what will happen when they start to use VS2017, and get compilation failures because of mismatches in the toolset versions.

Just for information, I am not a coworker of @DoDoENT (since @lasote was asking).

In any case, maybe by conan 2.0, Msft has estabilized a bit and we have better idea of their plans regarding compiler minor updates too.

There really is nothing to stabilize here - Visual Studio and the Visual C++ (AKA MSVC) have always been separate products (one is an IDE and the other a compiler - e.g. you can get the compiler w/o the IDE through the Windows SDK) and their separate versioning schemas used currently have been in place for quite some time - it is just plain wrong to ‘guess’ or ‘tie’ the version of the compiler to the version of the IDE (not just because, as DoDoENT already pointed out, you can have multiple toolchains and compiler versions ‘within’ one IDE version). IOW&IMNHO there should be no question of whether this needs to be fixed … only when and how…

With that said, given that it is already creating problems or even showstoppers for MSVC users, it would be nice if you could come up with some kind of a workaround until it gets ‘officially fixed’…

Actually… this could and probably should be discussed earlier. If anything, it could make sense to introduce a new compiler in the setting, to replace the old one, but without replacing. Conan 2.0 would deprecate the previous one (without removing), maybe Conan 3.0 could remove the old one. Lets discuss.