spektrof / conan_contribute

0 stars 1 forks source link

[question] How to deal with version conflicts? #3

Open memsharded opened 2 years ago

memsharded commented 2 years ago

You are using in your files something like:

conan_dependency(
      name = "boost",
      version = "1.70.0",
      revision = "<rev>",
      pids = ["<pid1>", "<pid2>"]
  )

I guess that if you have more than 1 dependency, lets say that you depend on boost/1.70.0 and poco/1.9.4, you would have something like:

conan_dependency(
      name = "boost",
      version = "1.70.0",
      revision = "<rev>",
      pids = ["<pid1>", "<pid2>"]
  )
conan_dependency(
      name = "poco",
      version = "1.9.4",
      revision = "<rev>",
      pids = ["<pid1>", "<pid2>"]
  )

You are resolving each one with your own resolution, fetching the things directly from the server (conan download). Now lets say that you have the following:

That is a version conflict and should raise an error, or otherwise the linkage might be undefined, and any of the two versions can be linked, or both and produce a linking error, or worse, runtime errors because of binary incompatibilities. How are you managing such conflicts? What happen if there is also a configuration conflict? Lets say that boost depends on zlib with some option enabled and poco depends on zlib with the same option disable. How your dependency resolution addresses these problems?

spektrof commented 2 years ago

Hi,

We are using three repository rules in our Bazel Workspace file which are required to use conan dependencies, besides registering the toolchains. A simple workspace file with your example would look like:

workspace(name = "projectA")

http_archive(name = "toolchain_repo", url=...)

register_toolchains(
  "@toolchain_repo//toolchain:<platform1>",
  "@toolchain_repo//toolchain:<platform2>",
)

load("@toolchain_repo//conan:conan_configurations.bzl", "conan_configurations")

conan_configurations(
  artifactory = "dev/prod/qa",
)

load("@toolchain_repo//conan:conan_dependency.bzl", "conan_dependency")

conan_dependency(
      name = "boost",
      version = "1.70.0",
      revision = "<rev>",
      pids = ["<pid1>", "<pid2>"]
  )
conan_dependency(
      name = "poco",
      version = "1.9.4",
      revision = "<rev>",
      pids = ["<pid1>", "<pid2>"]
  )

load("@toolchain_repo//conan:resolve_conan_dependencies.bzl", "resolve_conan_dependencies")

resolve_conan_dependencies(name = "resolve_conan_dependencies")
load("@resolve_conan_dependencies//:defs.bzl", "load_conan_repositories")
load_conan_repositories()

1)conan_configurations Set up the conan user home, storage path and internally used Artifactory remotes automatically. The users don't need to know how to do it, everything will be set up for them.

2)resolve_conan_dependencies This rule will collect the registered dependencies and resolve their transitive dependencies in parallel. To get the transitive dependencies, it runs conan get, check the requires/full_requires and options/full_options fields of the output, then it generates lockfiles based on these information for each dependency. As we are using both static and shared libraries of some dependencies in our projects, we might have two lockfiles for each platform. These lockfiles will be used by 3) . This step most likely can be simplified a lot by using Conan 2.0 because a single lockfile can hold information for multiple platforms as far as I know.

I have found better to use conan get instead of conan info or conan install because this case I can run the commands in parallel, otherwise I might have malicious cache. Also, in order to get the proper output by conan info or conan install, I would still need a lockfile before that.

After we know the transient dependencies of each direct dependency, we do the required conflict checks, such as version or revision conflicts. We also do configuration checks, e.g. we allow at most one package per library type (static/shared), so if we generate the BUILD files properly, then we won't have any problem. Regarding the version conflict, the name of the repositories are unique, so the Bazel won't let to have two different conan_dependency rules with the same name in the Workspace file.

In case of no conflicts, the rules for transitive dependencies will be written to @resolve_conan_dependencies//:defs.bzl. Otherwise, we can let the users to override some of the package dependencies or they can do onboard requests with the latest dependencies to prevent the conflicts.

The packages will be downloaded by conan download after the dependency resolution completed.

3)conan_dependency Register a dependency. Inside the rule, we already have the lockfile to be used, so we only need to generate the metadatas (including package_info()) by using our custom generator to be able to generate a platform and library type independent BUILD file.

To import precompiled libraries, we are using cc_import. An example rule would be similar than the following:

cc_import(
  name = "stacktrace_basic_import",
  static_library = select({
    "@toolchain_repo//config:<platform1>": "lib/<platform1>/libboost_stacktrace_basic.a",
    "@toolchain_repo//config:<platform2>": "lib/<platform2>/libboost_stacktrace_basic.a",
  }),
  shared_library = select({
    "@toolchain_repo//config:<platform1>": "lib/<platform1>/libboost_stacktrace_basic.so.1.70.0",
    "@toolchain_repo//config:<platform2>": "lib/<platform2>/libboost_stacktrace_basic.so.1.70.0",
  }),
)

This setup is working perfectly for us so far, however the transitive dependencies can be different without doing any modifications at package onboarding, so the resulted build might be not repeatable by default.