How to Create a New PackageManager

Note

This document is a work in progress.

Source Setup

The first step in creating a new package manager is creating the initial source files for the package manager. CMaize stores package managers within the package_managers directory. Within package_managers, each package manager gets its own subdirectory. Source code for package managers tends to be involved so we recommend separating the API of your package manager from its implementation. To do this we create a file <package_manager_name>.cmake to house the API and a subdirectory impl_ which will house the implementation. For example, source for the CMake package manager resides within the package_managers/cmake directory. The API lives in package_managers/cmake/cmake_package_manager.cmake (we wanted to avoid the source file being called cmake.cmake) and the implementations reside in package_managers/cmake/impl_. Within the impl_ directory you will create one file per method (named after the method it implements) and a convenience CMake module for including all of the implementations (the convenience module is usually named impl_.cmake). For example, the implementation of the CMake package manager’s install_package method resides in the file package_managers/cmake/impl_/install_package.cmake and the convenience module is package_managers/cmake/impl_/impl_.cmake. For completeness sake, the contents of impl_.cmake look like:

include_guard()

include(cmaize/package_managers/cmake/impl_/install_package)
# include other method implementations here (recommend that modules are
# included in alphabetic order)

To ensure that your new package manager is included when users load CMaize’s package manager component you will need to modify package_managers/package_managers.cmake so that it includes your new class. For example, to ensure the CMake package manager is included, package_managers/package_managers.cmake will look like:

include_guard()

include(cmaize/package_managers/cmake/cmake_package_manager)

Declaring the PackageManager

Now we write the API for our new package manager. For concreteness we assume we are writing a class for Python’s pip package manager. In this case the API lives in package_managers/pip/pip.cmake and the contents of the file look something like:

include_guard()

include(cmaize/package_managers/package_manager)
include(cmaize/package_managers/pip/impl_/impl_)

cpp_class(PipPackageManager PackageManager)
    # Class body goes here
cpp_end_class()

For brevity, and to avoid this tutorial becoming dated, we omit the body of the class. Developers should consult with the documentation for the base class, i.e., PackageManager, for details on what functions the derived class must implement, and what those functions should do.

Registering the PackageManager

Most package managers will require additional dependencies beyond CMake. Thus they should be off by default. When a user wants to use the package manager they must explicitly enable it. This is done by writing an enable function. By means of example, the enable_pip_package_manager function looks like:

function(enable_pip_package_manager)

   # 1. Tell the global environment that a pip package manager will exist
   cpp_get_global(_eppm_pms CMAIZE_SUPPORTED_PACKAGE_MANAGERS)
   if("pip" IN_LIST _eppm_pms) # If pip is in the list, it's already enabled
      return()
   endif()

   list(APPEND _eppm_pms "pip")
   cpp_set_global(CMAIZE_SUPPORTED_PACKAGE_MANAGERS "${_eppm_pms}")

   #2. Construct a PipPackageManater object
   find_package(Python3 COMPONENTS Interpreter QUIET REQUIRED)
   PipPackageManager(CTOR _eppm_package_manager "${Python3_EXECUTABLE}")

   #3. Register the PIP object with the global environment
   register_package_manager("pip" "${_eppm_package_manager}")

endfunction()

The current convention is to implement the enable function after the class’s definition (so for pip the enable_pip_package_manager function will live at the bottom of package_managers/pip/pip.cmake). The last step is to update cmaize_find_or_build_dependency so that it enables the package manager if a user requests it. To do this look for the if/else tree comparing ${_fobd_PACKAGE_MANAGER} against the list of package managers. Once found, add a branch for your new package manager that calls the enable function you just wrote.