-
Notifications
You must be signed in to change notification settings - Fork 184
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Experimental CMake support #2620
Conversation
I have spent some time investigating on whether it'd be possible to maintain the current build workflow for domain specific sources in src. As we discussed on Wednesday, the current CMake code builds everything in src as a static library which then gets then linked by the various command executables. This works reasonably well, but one clear issue with this approach is that a modification of anything inside src triggers a relinking of the static library for every command (which may take some time). If we consider this workflow to be essential, then this approach is far from ideal. The current Python build script avoids this by using a custom dependency logic which only rebuilds the affected object files by the change and then rebuilds only commands using those objects. CMake doesn't provide an analogous workflow (I've asked on the cpplang Slack forum about this and a CMake dev confirmed to me that indeed CMake doesn't support such mechanism because it would be practically impossible to create something that works with every build system that CMake needs to support on all platforms). Also see here. I think it may be worthwile splitting the static library into multiple static libraries using the existing folder structure inside src (so instead of having one Let me know if anyone has any thoughts about this. |
825ebc6
to
c2c6e08
Compare
App::event is defined twice in gui/mrview/file_open and gui/shview/ file_open. This causes an ODR violation.
This behaviour is similar to how the current build process works.
We now use CMAKE_INSTALL_PREFIX to set the base installation directory. Also standard GNU installation directories are used to install the runtime binaries (typically in bin) and libraries (typically in lib).
The module was taken from the Eigen project
cda6d57
to
a496eec
Compare
a496eec
to
d0acc4e
Compare
As we discussed in our last meeting, I thought it might be a good to give an update on the various considerations that we've made to build MRtrix using CMake and different possible routes that we've considered. Currently, the codebase is structured primarily into three parts: the core library (built by default as a shared library), the commands (in Switching to CMake presents various challenges. In CMake, everything is built around the idea of "targets". In general, a target is either a library (shared or static) or an executable. The idea is that the developer explicitly specifies the dependencies between targets and CMake will take care of generating the appropriate linking and building mechanisms for the chosen generator by the developer (note that CMake is a build system generator and not really a build system). Now clearly, our codebase isn't structured to fit this paradigm: we don't specify the dependencies between targets, the build script does it automatically. In our codebase we only have one library (the core library) but the commands (executables) are built using a custom logic injection (in the build script) that is somewhat hidden to the developer. In CMake world, everything should be explicitly mentioned and even techniques like "globbing" a folder of source files and assigning it to target is considered bad practice (for good reasons as CMake needs to work with a lot of different build systems). The developer is invited to manually list each dependency of a given target. In light of this here are a few approaches that are possible (there are many details I purposefully left out):
|
I have spent time further investigating options 1 and 2 further. Our primary concern with building everything in The only platform where linking times are a concern is MSYS2 on Windows. Unfortunately, the MSYS2 "emulation" slows down the linker and (using I have also tested the second approach more thoroughly and built everything inside Currently, I've encountered some problems using approach 4 since object libraries in CMake don't have the same status as static (or shared) libraries and the generated code by the Python script runs into cyclic dependencies issues. I'm not sure whether there is a particularly good solution for the problem, but more investigation is needed to determine this. Also, in light of the issues just mentioned, I'm less convinced that this approach is actually worthwhile from both a usability and implementation point of view. |
As mentioned above, using the Python script (approach 4 above) to generate the list of targets in CMake, runs into cyclic dependencies issues. The script generates a list of objects (for all .cpp files in |
OK, so it turns that the cyclic dependency issues weren't actually a problem. The python build script (unnecessarily) linked object libraries for the cpp sources to each other. I realised that this wasn't necessary, as all required object files will be included in the target commands anyway. I tried to build (non-gui) commands without the unnecessary linking and things seem to work. |
The other aspect that needs to be reconsidered here is the Python API and algorithm-based scripts. From my latest test of |
I think it's probably a good idea to give a short summary of the progress made on trying to build MRtrix3 using CMake for future reference.
|
Don't be afraid to separate out into a new PR for the
|
Yes, I think it may be a good idea to open a new PR that supersedes this one. For the Python scripts, I have now moved |
|
This adds experimental support for building MRtrix using CMake. At the moment, it's just a quick proof of concept, but it should compile fine (at least on Windows and Linux, MacOS still needs some work).
Why CMake?
CMake is the defacto standard tool for building cross-platform C++ projects as it's the most widely adopted build system in the C++ world. It is open source and it supports all the major operating systems (Windows, Linux and MacOS).
One of the significant benefits of using CMake is that integrates well with most existing C++ IDEs (e.g. Qt Creator, Visual Studio, Visual Studio Code, CLion, KDevelop, Eclipse, etc...). This integration allows developers to build and debug their code within their chosen IDE with little to no setup or configuration. CMake also nicely interoperates with other tools such as code analysis tools, test frameworks and package managers.
Also using CMake will allow us to slim down the current code for building MRtrix since it will automatically handle some of the stuff the current Python script is doing manually (e.g. handling of different build types, running Qt's MOC and resource compiler, compilation instructions, etc...).
Furthermore, since CMake is so widely known, it can make the build process more accessible to other developers, reducing the barrier to entry for new contributors to the project. The current Python script is heavily specialized for the needs of MRtrix, but may feel somewhat foreign to newcomers.
A good resource for CMake is An introduction to Modern CMake.
Some specific issues that are probably worth discussing:
Project structure. We should design a suitable project structure that plays nicely with CMake idioms. Ideally, each subcomponent of the project should have its own CMakeLists.txt file. Effectively this shouldn't be too disruptive since the current codebase is already organised to facilitate this, but further considerations are probably needed.
Minimum CMake version. Ideally we want to use a modern version of CMake to make use of best practices and nice quality of life features. The latest version at this point in time is 3.26.3, but this may not be availble on all systems by default. In practice, this may not be too much of a concern as users can install the latest CMake from pip (
pip install cmake
) or download official prebuilt binaries.Globbing. CMake allows the use of "globbing" to generate a list of files that match a certain regex expression (e.g.
file(GLOB SRCS *.cpp)
will generate a list containing all cpp files in the current directory), which can then be used to build those files. This approach is similar to how the current Python script works, but it is generally considered bad practice (see Why is cmake file GLOB evil and here). We should consider whether it makes sense to use this in MRtrix even if it goes against conventional wisdom.Package manager. I integrated vcpkg with this project to handle thirdparty dependencies. MRtrix currently uses header only libraries so this is not necessary. However, a package manager may make versioning and future dependencies easier to maintain.
Installation of build artifacts. CMake promotes a clean delineation between source trees, build trees and installation trees. The current build system doesn't really distinguish between build and install stages and handles things quite differently. So this also needs to be discussed.
Any feedback on this experiment is very welcome!