Package files: the package() method

We already used the package() method in our hello package to invoke CMake’s install step. In this tutorial, we will explain the use of the CMake.install() in more detail and also how to modify this method to do things like:

  • Using conan.tools.files utilities to copy the generated artifacts from the build folder to the package folder

  • Copying package licenses

  • Manage how to package symlinks

Please, first clone the sources to recreate this project. You can find them in the examples2 repository on GitHub:

  1. $ git clone https://github.com/conan-io/examples2.git
  2. $ cd examples2/tutorial/creating_packages/package_method

Using CMake install step in the package() method

This is the simplest choice when you have already defined in your CMakeLists.txt the functionality of extracting the artifacts (headers, libraries, binaries) from the build and source folder to a predetermined place and maybe do some post-processing of those artifacts. This will work without changes in your CMakeLists.txt because Conan will set the CMAKE_INSTALL_PREFIX CMake variable to point to the recipe’s package_folder attribute. Then, just calling install() in the CMakeLists.txt over the created target is enough for Conan to move the built artifacts to the correct location in the Conan local cache.

CMakeLists.txt

  1. cmake_minimum_required(VERSION 3.15)
  2. project(hello CXX)
  3. add_library(hello src/hello.cpp)
  4. target_include_directories(hello PUBLIC include)
  5. set_target_properties(hello PROPERTIES PUBLIC_HEADER "include/hello.h")
  6. ...
  7. install(TARGETS hello)

conanfile.py

  1. def package(self):
  2. cmake = CMake(self)
  3. cmake.install()

Let’s build our package again and pay attention to the lines regarding the packaging of files in the Conan local cache:

  1. $ conan create . --build=missing -tf=""
  2. ...
  3. hello/1.0: Build folder /Users/user/.conan2/p/tmp/b5857f2e70d1b2fd/b/build/Release
  4. hello/1.0: Generated conaninfo.txt
  5. hello/1.0: Generating the package
  6. hello/1.0: Temporary package folder /Users/user/.conan2/p/tmp/b5857f2e70d1b2fd/p
  7. hello/1.0: Calling package()
  8. hello/1.0: CMake command: cmake --install "/Users/user/.conan2/p/tmp/b5857f2e70d1b2fd/b/build/Release" --prefix "/Users/user/.conan2/p/tmp/b5857f2e70d1b2fd/p"
  9. hello/1.0: RUN: cmake --install "/Users/user/.conan2/p/tmp/b5857f2e70d1b2fd/b/build/Release" --prefix "/Users/user/.conan2/p/tmp/b5857f2e70d1b2fd/p"
  10. -- Install configuration: "Release"
  11. -- Installing: /Users/user/.conan2/p/tmp/b5857f2e70d1b2fd/p/lib/libhello.a
  12. -- Installing: /Users/user/.conan2/p/tmp/b5857f2e70d1b2fd/p/include/hello.h
  13. hello/1.0 package(): Packaged 1 '.h' file: hello.h
  14. hello/1.0 package(): Packaged 1 '.a' file: libhello.a
  15. hello/1.0: Package 'fd7c4113dad406f7d8211b3470c16627b54ff3af' created
  16. hello/1.0: Created package revision bf7f5b9a3bb2c957742be4be216dfcbb
  17. hello/1.0: Full package reference: hello/1.0#25e0b5c00ae41ef9fbfbbb1e5ac86e1e:fd7c4113dad406f7d8211b3470c16627b54ff3af#bf7f5b9a3bb2c957742be4be216dfcbb
  18. hello/1.0: Package folder /Users/user/.conan2/p/47b4c4c61c8616e5/p

As you can see both the include and library files were copied to the package folder after calling to the cmake.install() method.

Use conan.tools.files.copy() in the package() method and packaging licenses

For the cases that you don’t want to rely on CMake’s install functionality or that you are using another build-system, Conan provides the tools to copy the selected files to the package_folder. In this case, you can use the tools.files.copy function to make that copy. We can replace the previous cmake.install() step with a custom copy of the files and the result would be the same.

Note that we are also packaging the LICENSE file from the library sources in the licenses folder. This is a common pattern in Conan packages and could also be added to the previous example using cmake.install() as the CMakeLists.txt will not copy this file to the package folder.

conanfile.py

  1. def package(self):
  2. copy(self, "LICENSE", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses"))
  3. copy(self, pattern="*.h", src=os.path.join(self.source_folder, "include"), dst=os.path.join(self.package_folder, "include"))
  4. copy(self, pattern="*.a", src=self.build_folder, dst=os.path.join(self.package_folder, "lib"), keep_path=False)
  5. copy(self, pattern="*.so", src=self.build_folder, dst=os.path.join(self.package_folder, "lib"), keep_path=False)
  6. copy(self, pattern="*.lib", src=self.build_folder, dst=os.path.join(self.package_folder, "lib"), keep_path=False)
  7. copy(self, pattern="*.dll", src=self.build_folder, dst=os.path.join(self.package_folder, "bin"), keep_path=False)
  8. copy(self, pattern="*.dylib", src=self.build_folder, dst=os.path.join(self.package_folder, "lib"), keep_path=False)

Let’s build our package one more time and pay attention to the lines regarding the packaging of files in the Conan local cache:

  1. $ conan create . --build=missing -tf=""
  2. ...
  3. hello/1.0: Build folder /Users/user/.conan2/p/tmp/222db0532bba7cbc/b/build/Release
  4. hello/1.0: Generated conaninfo.txt
  5. hello/1.0: Generating the package
  6. hello/1.0: Temporary package folder /Users/user/.conan2/p/tmp/222db0532bba7cbc/p
  7. hello/1.0: Calling package()
  8. hello/1.0: Copied 1 file: LICENSE
  9. hello/1.0: Copied 1 '.h' file: hello.h
  10. hello/1.0: Copied 1 '.a' file: libhello.a
  11. hello/1.0 package(): Packaged 1 file: LICENSE
  12. hello/1.0 package(): Packaged 1 '.h' file: hello.h
  13. hello/1.0 package(): Packaged 1 '.a' file: libhello.a
  14. hello/1.0: Package 'fd7c4113dad406f7d8211b3470c16627b54ff3af' created
  15. hello/1.0: Created package revision 50f91e204d09b64b24b29df3b87a2f3a
  16. hello/1.0: Full package reference: hello/1.0#96ed9fb1f78bc96708b1abf4841523b0:fd7c4113dad406f7d8211b3470c16627b54ff3af#50f91e204d09b64b24b29df3b87a2f3a
  17. hello/1.0: Package folder /Users/user/.conan2/p/21ec37b931782de8/p

Check how the include and library files are packaged. The LICENSE file is also copied as we explained above.

Another thing you can do in the package method is managing how to package symlinks. Conan won’t manipulate symlinks by default, so we provide several tools to convert absolute symlinks to relative ones and removing external or broken symlinks.

Imagine that some of the files packaged in the latest example were symlinks that point to an absolute location inside the Conan cache. Then, calling to conan.tools.files.symlinks.absolute_to_relative_symlinks() would convert those absolute links into relative paths and make the package relocatable.

conanfile.py

  1. from conan.tools.files.symlinks import absolute_to_relative_symlinks
  2. def package(self):
  3. copy(self, "LICENSE", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses"))
  4. copy(self, pattern="*.h", src=os.path.join(self.source_folder, "include"), dst=os.path.join(self.package_folder, "include"))
  5. copy(self, pattern="*.a", src=self.build_folder, dst=os.path.join(self.package_folder, "lib"), keep_path=False)
  6. ...
  7. absolute_to_relative_symlinks(self, self.package_folder)

See also