Why do we need forks?

Forks put the burden of pushing new branches/releases from upstream, merging and resolving conflicts by maintainers and at the first view look like a bad, aggressively intrusive choice. But in practice it’s the clearest, robust and universal solution for all the issues related to integration of package into Hunter.

Note

Forks are not requirement. Hunterization changes can be pushed upstream without affecting main functionality, see compatibility for details. And if package has no dependencies it can be used as is in Hunter, see examples.

Note

As already noted here all the issues that are not related to hunterization should be pushed upstream. Including most of the issues described in this section.

Solution for bundled sources

Take a look at this example:

Here package rabit has bundled dependencies dmlc. In fact dmlc folder is a separated project and lives here:

Assuming that we can’t change the order of include paths (local includes should have higher priority than external because different version of same package itself can be installed in system) there is no “soft” solution here and the only way to integrate package is to remove dmlc folder from sources. In practice it means forking the project and applying “remove folder” patch. Note that it really doesn’t depend on the package manager, build system or C++ compiler. All C++ compilers works similar to

> c++ -I/path/to/local -I/path/to/external ...

Meaning #include <dmlc/timer.h> will always fall to the choice of picking local files.

Set of patch files

Another way to avoid forks is to keep needed *.patch files in Hunter and apply them to upstream releases before running build instructions. Such approach used by Homebrew and Gentoo for example. In practice such set of patches can be quite big, e.g. 19 commits for package OpenCV (add HunterGate module, lock version in HunterGate, adding hunter_add_package calls, applying ANDROID_* variables introduced by new CMake version and general improvements):

Note that Hunter keep all available OpenCV versions in cmake/projects/OpenCV/hunter.cmake file:

At this moment there are 29 versions of OpenCV available for users, hence it will be 19 x 29 = 551 *.patch files to maintain. Some of them can be shared between versions, some of them can be fused with each other, etc. If such approach will be chosen we will end up with system for maintaining patches, but there is no need to reinvent the wheel, such system already exist and called Git. Assuming the fact that Hunter project hosted on GitHub and GitHub offer free unlimited repositories for public projects there are no real reasons to choose *.patch approach over forks. The use of the forks allow us to rebase, merge, cherry-pick, discuss and review the patches easily.

High cohesion

High cohesion means that you should keep parts of a code base that are related to each other in a single place [1]. The fact that version v1.0 of package Foo works fine with Hunter archive v0.10 is perfectly expressed by adding child commit Add Hunter v0.10 to parent commit Foo v1.0. Change of dependencies from version to version is another example.

Foo version v1.0:

if(WIN32)
  find_package(boo CONFIG REQUIRED)
endif()

find_package(bar CONFIG REQUIRED)

Foo version v2.0:

if(FOO_WITH_BAZ)
  find_package(baz CONFIG REQUIRED)
endif()

find_package(bar CONFIG REQUIRED)

It’s hard to make a mistake in both cases:

--- /home/docs/checkouts/readthedocs.org/user_builds/hunter/checkouts/latest/docs/faq/foo-v1.0.cmake
+++ /home/docs/checkouts/readthedocs.org/user_builds/hunter/checkouts/latest/docs/faq/foo-v1.0-hunter.cmake
@@ -1,5 +1,7 @@
 if(WIN32)
+  hunter_add_package(boo)
   find_package(boo CONFIG REQUIRED)
 endif()
 
+hunter_add_package(bar)
 find_package(bar CONFIG REQUIRED)
--- /home/docs/checkouts/readthedocs.org/user_builds/hunter/checkouts/latest/docs/faq/foo-v2.0.cmake
+++ /home/docs/checkouts/readthedocs.org/user_builds/hunter/checkouts/latest/docs/faq/foo-v2.0-hunter.cmake
@@ -1,5 +1,7 @@
 if(FOO_WITH_BAZ)
+  hunter_add_package(baz)
   find_package(baz CONFIG REQUIRED)
 endif()
 
+hunter_add_package(bar)
 find_package(bar CONFIG REQUIRED)

It will be much easier to miss something while trying to support any fork-free approach:

if(FOO_VERSION VERSION_EQUAL 1.0 AND WIN32)
  magic_download(boo)
endif()

if(FOO_VERSION VERSION_EQUAL 2.0 AND FOO_WITH_BAZ)
  magic_download(baz)
endif()

magic_download(bar)

Any non-CMake custom build scheme suffers from this problem since build instructions have to know everything about all versions available, e.g. see Boost components .

[1]http://enterprisecraftsmanship.com/2015/09/02/cohesion-coupling-difference/

Rejected/pending CMake patches

Having CMake build instructions in package is the easiest way to integrate package into Hunter (but not the only one) however not all developers of the upstream projects are ready to accept CMake code because it may put burden on maintaining another build system (if CMake added as extra build system), learning new build system (if you want to substitute existing system with CMake) or increase CMake minimum version to introduce new code. https://github.com/hunter-packages is a central place where CMake friendly code can leave and shared with others.

Removing usage of FindXXX.cmake

Overwhelming majority of projects use FindXXX.cmake (or even something like find_library) instead of recommended XXXConfig.cmake approach, effectively making project non-relocatable. It’s not a problem for the package managers that are using single-root directory (e.g. /usr/lib for apt-get on Ubuntu and /usr/local/lib for brew on OSX) but since Hunter allow to have multiple custom configurations it will not work.

Lock URL/SHA1 in HunterGate

Even if all the issues will be resolved and ‘hunter_add_package’ will be called by hook inside ‘find_package’ it’s still will be convenient to save latest successful 3rd parties configuration for debugging purposes. In terms of Hunter it means attaching URL/SHA1 arguments of HunterGate to some commit.