Skip to content

Latest commit

 

History

History
193 lines (140 loc) · 10.2 KB

dependencies.md

File metadata and controls

193 lines (140 loc) · 10.2 KB

Dependencies

This section outlines all the practices and guidelines for the requirements() and build_requirements() methods. This includes everything from handling "vendored" dependencies to what versions should be used.

Contents

List Dependencies

Since all ConanCenterIndex recipes are to build and/or package projects they are exclusively done in conanfile.py. This offers a few ways to add requirements. The most common way is requirements:

    def requirements(self):
        self.requires("fmt/9.1.0")

Note: With Conan 2.0, you'll also need to pay attention to new properties like the transitive_header attributed which is needed when a project include a dependencies header files in its public headers.

When a project supports a range of version of a dependency, it's generally advised to pick the most recent available in ConanCenter. This helps ensure there are fewer conflicts with other, up to-date, recipes that share the same requirement.

Optional Requirements

Many projects support enabling certain features by adding dependencies. In ConanCenterIndex this is done by adding an option, see naming recommendation, which should be set to match the upstream project's by default.

class ExampleConan(ConanFile):
    options = {
        "with_zlib": [True, False], # Possible values
    }
    default_options = {
        "with_zlib": True, # Should match upstream's CMakeLists.txt `option(...)`
    }

    def requirements(self):
        if self.options.with_zlib:
            self.requires("zlib/1.2.13")

If a dependency was added (or removed) with a release, then the if condition could check self.version. Another common case is self.settings.os dependant requirements which need to be added for certain plaforms.

Build Requirements

In ConanCenter we only assume CMake is available. If a project requires any other specific tool, those can be added as well. We like to do this with build_requirements:

    def build_requirements(self):
        self.tool_requires("ninja/1.1.0")

Accessing Dependencies

It's fairly common to need to pass information from a dependency to the project. This is the job of the generate() method. This is generally covered by the built-in generators like CMakeDeps However the self.dependencies are available.

Alternatively, a project may depend on a specific versions or configuration of a dependency. This use case is again covered by the self.dependencies within the validate() method. Additionally it's possible to suggest the option's values while the graph is built through configure() this is not guaranteed and not a common practice.

Handling Requirement's Options

Forcing options of dependencies inside a ConanCenter should be avoided, except if it is mandatory for the library to build. Our general belief is the users input should be the most important; it's unexpected for command line arguments to be over ruled by specifc recipes.

You need to use the validate() method in order to ensure they check after the Conan graph is completely built.

Certain projects are dependent on the configuration (also known as options) of a dependency. This can be enforced in a recipe by accessing the options field of the dependency.

  def configure(self):
      self.options["foobar"].enable_feature = True # This will still allow users to override this option

  def validate(self):
      if not self.dependencies["foobar"].options.enable_feature:
          raise ConanInvalidConfiguration(f"{self.ref} requires foobar/*:enable_feature=True.")

Verifying Dependency's Version

Some project requirements need to respect a version constraint, this can be done as follows:

def validate(self):
    if Version(self.dependencies["foobar"].ref.version) < "1.2":
        raise ConanInvalidConfiguration(f"{self.ref} requires [foobar>=1.2] to build and work.")

Passing Requirement's info to build()

The self.dependencies are limited to generate() and validate(). This means configuring a projects build scripts is a touch more complicated when working with unsupported build scripts.

In general, with CMake project, this can be very simple with the CMakeToolchain, such as:

    def generate(self):
        tc = CMakeToolchain(self)
        # deps_cpp_info, deps_env_info and deps_user_info are no longer used
        if self.dependencies["dependency"].options.foobar:
            tc.variables["DEPENDENCY_LIBPATH"] = self.dependencies["dependency"].cpp_info.libdirs

This pattern can be recreated for less common build system by, generating a script to call configure or capture the required values in a YAML files for example.

Note: This needs to be saved to disk because the conan install and conan build commands can be separated when developing packages so for this reason the class may not persists the information. This is a very common workflow, even used in ConanCenter in other areas such as testing.

from conan import ConanFile
from conan.tools.files import save, load


class ExampleConan(ConanFile):
    _optional_build_args = []

    @property
    def _optional_build_args_filename(self):
        return os.path.join(self.recipe_folder, self.folders.generators, "build_args.yml")

    def generate(self):
        # This is required as `self.dependencies` is not available in `build()` or `test()`
        if self.dependencies["foobar"].options.with_compression:
            self._optional_build_args.append("--enable-foobar-compression")

        save(self, self._optional_build_args_filename, file)

    def build(self):
        opts_args = load(self, self._optional_build_args_filename)
        # Some magic setup
        self.run(f"./configure.sh {opts_args}")

Overriding the provided properties from the consumer

Note: This was adding in Conan 1.55 to the generators... we need to write docs for when that's available

Adherence to Build Service

It's very rare we layout "rules", most often it's guidelines, however in order to ensure graph and the package generated are usable for consumer, we do impose some limits on Conan features to provide a smoother first taste to using Conan.

Note: These are very specific to the ConanCenter being the default remote and may not be relevant to your specifc use case.

  • Version ranges are not allowed.
  • Specify explicit RREV (recipe revision) of dependencies is not allowed.
  • Only ConanCenter recipes are allowed in requires/requirements() and build_requires/build_requirements().
  • python_requires are not allowed.

Version Ranges

Version ranges are a useful Conan feature, documentation here. However, in the context of ConanCenter they pose a few key challenges when being used generally to consume packages, most notably:

  • Non-Deterministic package-id: With version ranges the newest compatible package may yield a different package_id than the one built and published by ConanCenter resulting in frustrating error "no binaries found". For more context see this excellent explanation.

  • Build Reproducibility: If consumers try to download and build the recipe at a later time, it may resolve to a different package version that may generate a different binary (that may or may not be compatible). In order to prevent these types of issues, we have decided to only allow exact requirements versions. This is a complicated issue, check this thread for more information.

Handling "internal" dependencies

Vendoring in library source code should be removed (best effort) to avoid potential ODR violations. If upstream takes care to rename symbols, it may be acceptable.