We are pleased to announce that Conan 1.41 is out and comes with some significant new features and bug fixes. Local flows and editable packages are now easier to handle using layout(). We introduced support for Intel’s oneAPI Toolkits with a new intel-cc compiler setting. Also, the new environment supports multi-configuration generators now. We have added a new objects attribute in the conanfile’s cpp_info to provide object files (.obj and .o) with which consumers can link. Finally, from this version, it’s possible to use multiple different toolchains like AutotoolsToolchain, BazelToolchain, and CMakeToolchain in the same recipe.

Easier local flows and editable packages handling using layout()

For this release, we have fixed some issues regarding the new layout() feature introduced in Conan 1.37. This feature makes it easier to work with packages in editable mode and use the local development flow. When a package is in editable mode, Conan looks for its contents in the local work folder instead of the Conan local cache. That way, you can rebuild your package locally so the packages that depend on this editable package will link with the updated package without needing a conan create command for each change in the sources.

Let’s suppose you are developing a package called say using CMake and you have this project structure:

.
├── conanfile.py
└── say_sources
    ├── CMakeLists.txt
    ├── cpp
    │   └── say.cpp
    └── hpp
        └── say.h

Using the layout() feature, we can describe the package contents so consumers of this package can find it no matter if we are working in “regular” (find contents in Conan cache) or editable mode (find contents in local working folder). Let’s see how we set those values in the layout() method so that the say package is found in both cases. The conanfile of this package could look like this:

from conans import ConanFile
import os

class SayConan(ConanFile):
    name = "say"
    version = "0.1"
    exports_sources = "say_sources/*"
    ...
    def layout(self):
        self.folders.source = "say_sources"
        build_type = str(self.settings.build_type).lower()
        self.folders.build = "cmake-build-{}".format(build_type)
        self.folders.generators = os.path.join(self.folders.build, "conan")

        self.cpp.package.libs = ["say"]
        self.cpp.package.includedirs = ["include"] # includedirs is already set to this value by 
                                                   # default, but declared for completion

        # self.cpp.source and self.cpp.build are used for editable mode
        # this information is relative to the source and build folders
        self.cpp.source.includedirs = ["hpp"] # maps to ./say_sources/hpp
        self.cpp.build.libdirs = ["."]        # maps to ./cmake-build-<build_type>
        self.cpp.build.bindirs = ["."]        # maps to ./cmake-build-<build_type>

    def build(self):
        ...

We can set several attributes in the layout() method, for example, those related to where the sources are and where we want to build the package:

  • As we have our sources in the say_sources folder, self.folders.source is set to "./say_sources".
  • We are using CMake for building, so we want to set the build folder to cmake-build-release or cmake-build-debug depending on the build_type.
  • The self.folders.generators folder is where all files generated by Conan will be stored so they don’t pollute the other folders.

Please, note that the values above are for a single-configuration CMake generator. To support multi-configuration generators, such as Visual Studio, you should make some changes to this layout. For a complete layout that supports both single-config and multi-config please check the cmake_layout() in the Conan documentation.

Also, we can set the information about the package that the consumers need to use by setting the conanfile’s cpp.package attributes values:

  • Declaring self.cpp.package.libs inside the layout() method is equivalent to the “classic” self.cpp_info.libs declaration in the package_info() method.
  • Also, as you may know, self.cpp.package.includedirs is set to ["include"] by default, so there’s no need in declaring it but we are leaving it here for completeness.

Setting self.cpp.package.includedirs to ["include"] means that, when the package is not in editable mode, consumers will try to find the say.h file in a folder in the cache that corresponds to something similar to this:

/location/of/cache/data/say/0.1/_/_/package/<package_id>/include

As you can see, we don’t have that structure in our local folder so we need a way to tell the say package consumers where to find the include file there when it’s in editable mode. We can set that information using the conanfile’s cpp.source and cpp.build attributes (which are cpp_info objects):

  • self.cpp.source: to set folders where the source files are (like include files) relative to the self.folders.source. In this example we are setting self.cpp.source.includedirs = ["hpp"]. The self.folders.source information will be automatically prepended to that path for consumers so Conan will try to get the include files from the ./say_sources/hpp folder.
  • self.cpp.build: to set folders where the built files are (like libraries or binary files) relative to the self.folders.build. As before, you don’t have to prepend the build folder (cmake-build-release or cmake-build-debug) and if we set the self.cpp.build.libdirs and self.cpp.build.bindirs to ["."], Conan will try to find libraries and binaries in the ./cmake-build-<build_type> folder.

Note that the information set in self.cpp.source and self.cpp.build will be merged with the information set in self.cpp.package so that we don’t have to declare again something like self.cpp.build.libs = ["say"] that is the same for the consumers independently of if the package is in editable mode or not.

With those declared, we could put the package in editable mode, build it and other packages that require say would consume it in a completely transparent way.

Please, for a complete example on how to use editable packages and layout, check the Conan Examples repository.

Intel oneAPI support in Conan 1.41

Starting in this version you can set the compiler setting to the intel-cc value to use the Intel compilers from the Intel oneAPI toolkits. This setting has a mode sub-setting to define the actual compiler you want to use from the Base or HPC Toolkits. It can take the following values:

  • icx: Intel oneAPI C++ Compiler (icx/icpx)
  • dpcpp: Intel oneAPI DPC++ Compiler (dpcpp)
  • classic: Intel C++ Compiler Classic (icc/icpc for Linux/macOS and icl for Windows)

Also, IntelCC has been added as a generator for Conan. Please check more about this feature in the Conan documentation.

Multi-config support for environment generators

Since Conan 1.35, new tools are available for managing environments. Now, the generated files for VirtualRunEnv and VirtualBuildEnv take the build_type and arch settings into account for the names of the launcher files to improve the integration with multi-configuration generators. So, for example, calling to conan install cmake/3.20.0@ -g VirtualBuildEnv --build-require -s build_type=Release for example will create a file called conanbuildenv-release-x86_64.sh and will not be overwritten for other build_type values.

Also, now it is possible to group the generated environment variables launcher scripts under different names. By default, they are all aggregated to the group build for Environment and VirtualeBuildEnv and group run for VirtualRunEnv leading to a conan(build/run).(sh/bat) script that sets all the declared environments. Now declaring something like this in the conanfile:

class PkgConan(ConanFile):
    ...
    def generate(self):
        tc = CMakeToolchain(self)
        tc.generate()
        env1 = Environment(self)
        env1.define("env_name", "env1")
        env1.save_script("env1_launcher", group="mygroup")
        env2 = Environment(self)
        env2.define("env_name", "env2")
        env2.save_script("env2_launcher", group="mygroup")
        env3 = Environment(self)
        env2.define("env_name", "env3")
        env2.save_script("env3_launcher")
    ...

Will create a conanmygroup.sh file to set both env1 and env2 and a default conanbuild.sh that will only set env3 variables.

Define object files in cpp_info.objects new attribute

Starting in Conan 1.41 you can set a list of object files (.obj and .o) in the cpp_info objects attribute. This attribute is only compatible with the CMakeDeps generator for the moment. It will add the declared objects to the generated CMake target so those objects can be later consumed by packages that declare the dependency.

Let’s suppose we create a Linux package pkg/1.0 that builds and packages one myobject.obj file. The same way you would declare the libraries that must be consumed by packages that depend on pkg/1.0 setting the cpp_info.libs attribute, you can declare that this library packages some object files that the consumers need to add. This can be done by setting that information in the package_info() method of the conanfile:

class Pkg(ConanFile):
    name = 'pkg'
    version = '1.0'
    ...
    def package_info(self):
        self.cpp_info.objects = ['lib/myobject.o']
    ...

Then, when this package is consumed by other packages using the CMakeDeps generator, the path of that object will be added to the target so the consumers can link against that myobject.obj file.

Use different toolchains in the same recipe

New toolchains create a file named conanbuild.conf to pass certain information to the build helpers. The filename was the same for all the toolchains causing a collision problem when a recipe used different build systems. Conan 1.41 provides a new argument namespace to the build helpers for CMake, Bazel, and Autotools. This argument makes it possible to use more than one toolchain in the same recipe, for example, when parts of the same package build with different build systems. Set this value to append the namespace to the filename (like conanbuild_namespace.conf), generating a unique name for each tool. A conanfile using this argument for the tools could look like this:

  from conans import ConanFile
  from conan.tools.gnu import AutotoolsToolchain, Autotools
  from conan.tools.google import BazelToolchain, Bazel
  from conan.tools.cmake import CMakeToolchain, CMake
  
  class Conan(ConanFile):
      settings = "os", "arch", "compiler", "build_type"
      def generate(self):
          # generates conanbuild_autotools.conf
          autotools = AutotoolsToolchain(self, namespace='autotools')
          autotools.generate() 
          
          # generates conanbuild_bazel.conf
          bazel = BazelToolchain(self, namespace='bazel')
          bazel.generate()     
          
          # generates conanbuild_cmake.conf
          cmake = CMakeToolchain(self, namespace='cmake')
          cmake.generate()     

      def build(self):
          # reads conanbuild_autotools.conf
          autotools = Autotools(self, namespace='autotools')
          autotools.configure() 
          autotools.make()
          
          # reads conanbuild_bazel.conf
          bazel = Bazel(self, namespace='bazel')
          bazel.configure()    
          bazel.build(label="//main:hello-world")
          
          # reads conanbuild_bazel.conf
          cmake = CMake(self, namespace='cmake')
          cmake.configure()   
          cmake.build()


Besides the items listed above, there were some minor bug fixes you may wish to read about. If so, please refer to the changelog for the complete list.

We hope you enjoy this release, and look forward to your feedback.