As a result of being around for so long, NASSP’s project structure has seen many iterations and revisions across the years. Our IDE of choice is Visual Studio Community, and we use its related MSBuild-style project and solution files to describe our various libraries and vessels to be built. The current project files can be traced all the way back to project files targeted at Visual C++ 6.0, development software which released in 1998, and from there the projects were upgraded progressively (likely though automated processes) to target newer versions of Visual C++. Over the years, these project files have accrued many discrepancies and legacy baggage that makes maintenance difficult. Most vessels and libraries have inconsistent build configurations compared to each other; various (and often obsolete) preprocessor flags are set with unclear purpose, or are set despite no longer being necessary; intermediate and output build filenames and directories are hard-coded when there are variables built into the MSBuild system which would allow such things to be more flexible. What’s more, this has also made it difficult to build NASSP for the upcoming open-source version of the Orbiter simulator, nicknamed “OpenOrbiter”. One or two libraries that we link against in the Orbiter SDK have had their filenames changed, necessitating us to go into each project that uses them and change the filename in our linker config. Not only that: since this new version of Orbiter supports 64-bit native compilation for the first time, it requires two completely new build configurations to be made for that architecture—one for debugging, one for release configuration—for every individual project targeting 64-bit versions of include headers and SDK libraries. This is time-consuming to do, and it requires making breaking changes with the main branch of our code that builds for an earlier version of the sim. As a result, there is no easy way to allow our codebase to build for both OpenOrbiter and the existing version unless we want to create not two, but four new build configurations for all 35 of our projects so we can preserve the old two, two for OpenOrbiter 32-bit builds and two for OpenOrbiter 64-bit builds.
So I started thinking about ways to improve the situation. For my initial OpenOrbiter compatibility branch, I started manually reworking the project files to try and use more variables for flexibility and started trying to remove old preprocessor definitions. I also tried changing the various buld configurations to match each other verbatim for as many sections as I could, so that I could modify them all at once via the Visual Studio interface. However, this proved even more tedious than I anticipated, and wasn’t a tenable approach. It was still frustrating to even need so much information to be duplicated across configurations, and part of the issue was how the Visual Studio build system was designed.
In that case, why not change it? I don’t have experience with a huge variety of C++ build systems, but I was particularly curious about one: CMake. Compared to our current way of doing things, it has a few benefits:
- It is a more abstract representation of our builds than the MSBuild/Visual Studio project files, allowing a single project description to define multiple build configurations. That is, one project’s CMakeLists.txt file would be able to generate a build for 32-bit or 64-bit; debug or release, with virtually zero need for duplicated code or configuration text. These project files are also considerably easier to read and understand, since they are laid out like code rather than an XML-style markup-type language. It also means that special build flags can be documented with comments so they can be sanity checked later on if they become unnecessary.
- By using a top-level project that adds individual libraries and vessels as sub-projects, the individual vessels can inherit global configuration settings and variables easily, meaning we duplicate less configuration settings and any major changes that need to be made can be done so in just one place.
- Since CMake incorporates code-like logic functionality into its syntax, we can perform configuration-time or compile-time checks for various things, such as the filenames of libraries we intend to link against. This allows us to have a single project structure that can simultaneously build for both our current version of Orbiter and the upcoming OpenOrbiter release, with only a few
if()
statements. - We can rely on CMake’s many variables and our own custom variables to allow for a more flexible build process. Previously our project files were structured with the assumption that our code would be placed directly within an Orbiter installation directory. This is now no longer necessary: we can build our code from any location, and direct the compiled output to any location, by simply specifying the location of the Orbiter SDK if it is located externally, and our desired output location. This also lets us use CMake’s “Install” functionality to copy over our custom meshes, sound files, mission data, and more directly into an external Orbiter installation. And every aspect of this process grants us more flexibility with how we structure our code and resource directory layout in the future, if we ever desire to change it.
Critically, CMake also features integration within Visual Studio, meaning that developers in our group who are unfamiliar to CMake itself only need to make minimal changes to their workflow. While much of the advanced flexibility that this new system offers is nice, it doesn’t have to be used; our build configuration files can be set up to expect our traditional build setup by default, so people can focus on the work they actually care about.
And so, I set out to recreate all 35 of our NASSP project files in CMake, with an overarching CMakeLists file that groups them all together into a larger “NASSP” superproject, similar to the Visual Studio solution file. The process took a couple weeks of gradual research and a few moments of confusion, but in the end I managed to successfully accomplish the goal. In this new branch, with a draft pull request in place to merge it upstream, NASSP has been fully migrated to CMake; simplifying our project files and adding flexibility while retaining the majority of our traditional workflow. I am keeping it as a draft until significant testing and experimentation can be done on this branch. Other developers in our group need time to test and adapt to the workflow changes that are unavoidable, and documentation and procedures need to be developed for porting over any outstanding pull requests or custom user-side branches so that they may continue being kept up to date with upstream commits.
All in all, I am extremely pleased with how relatively easy it was to port our build system to CMake, considering the sheer size, scope, and age of our codebase. Furthermore, I do not consider myself to be an expert with CMake by any means, so the fact that it was possible for a relative novice to accomplish this transition bolsters my appreciation for the system. With any luck, these changes will be integrated relatively soon, and we might be able to start reaping the benefits (short and long-term) of this effort.