CMake Build System: Difference between revisions

Jump to navigation Jump to search
m
Rate, reformat, syntax highlight, add to tool category
[unchecked revision][unchecked revision]
mNo edit summary
m (Rate, reformat, syntax highlight, add to tool category)
Line 1:
{{Rating|01}}
 
CMake is a cross-platform, multi-environment build system which can be leveraged to help remove some of the headache from building your operating system. The aim of this page is to help the reader set up his or her build environment to use CMake.
 
== About CMake ==
 
CMake is "makefile" generator that allows you to provide a description of your build environment, therefore affording the creation of toolchain-specific build files. It is an open-source project that is largely maintained by KitWare, Inc.
 
=== Who uses CMake? ===
 
Because CMake was developed by KitWare, it is closely associated with their software. However, momentum for CMake has steadily been increasing, and some fairly high-profile projects have switched over to it, including:
Line 16:
* ReactOS
 
== Getting Started ==
 
=== Design Considerations ===
=Getting Started=
 
==Design Considerations==
 
There are a variety of options at your disposal when you design your CMake project. It is possible to have CMake cater to some of your personal build preferences, and as a result, you should probably ask yourself a few questions:
Line 59 ⟶ 58:
The chances are that if you're building an operating system, you won't have many library dependencies. Most of your dependencies will be related to the toolchain required to build the code. For instance, you will probably want a C compiler, and almost certainly an assembler to go with it. You may write your own tools to simplify the process of creating your operating system, and in that case, you may want to ensure support for other programming environments like Python or Perl. Fortunately, this part can be fairly forgiving: adding dependencies such as these is not terribly difficult, and if you change your mind, the change is easy to implement.
 
=== A Simple CMakeLists.txt ===
 
One of the nice things about CMake is that it affords you several concepts that you are already familiar with. As one would expect, CMake lets you perform build configuration through variables, which can conveniently be found in a file called CMakeCache.txt. CMake cache is generated when you first run CMake on a CMakeLists.txt file, and can be tweaked to provide various special build options for your script. You can also use the SET() macro, which defines a variable with the name of it's first argument with a value provided by the rest.
Line 72 ⟶ 71:
Finally, you can emit messages back to the console using the MESSAGE command. Note that this is only run at CMake time. You can use this command for debugging your CMake project. Alternately, if you supply STATUS as the first parameter to this command, it will print out a specialized status message for you.
 
<source lang="cmake">
<pre>
# So CMake can tell whether or not it can process this file
CMAKE_MINIMUM_REQUIRED(VERSION 2.8.0)
Line 86 ⟶ 85:
# Note how we obtain this variable's value!
ADD_EXECUTABLE(foo ${C_SRCS})
</presource>
 
This is enough for a small project to generate an executable. Creating the associated Makefile and starting the build is simple:
Line 97 ⟶ 96:
Note that if you chance CMakeLists.txt, you will need to run CMake again. In cases like these, CMake may need to update CMakeCache.txt, for instance. Particularly in the case where you use file globbing find your source files, it is imperative that you do this when you add or delete source files; otherwise, CMake will not pick up the changes, and havoc will break loose.
 
=== Subproject Isolation ===
 
The previous section discussed a sort of "Hello World" implementation of CMakeLists.txt. Unfortunately, operating system development is rarely in the same class as "Hello World", and the chances are that you have advanced beyond intro Computer Science. If you're reading this, you more than likely have an idea of how you want to structure your project, and that probably means breaking it into manageable peices. If you followed the advice from "Design Considerations", then it is very likely that you have thought about this a great deal. The real question is how to put this into practice.
Line 103 ⟶ 102:
That question is answered by a tried-and-true pattern from other build systems, which involves generating custom build scripts for each subproject you create. Thus, we have a separate CMakeLists.txt for each subproject we create, and we link them together in the CMakeLists.txt in the project root. If we adhere to our filesystem layout from above, for instance, we'd have 3 CMakeLists.txt files: one in /, one in /src/kernel, and one in /src/libc. We can link them together thusly:
 
<source lang="cmake">
<pre>
ADD_SUBDIRECTORY(src/kernel)
ADD_SUBDIRECTORY(src/libc)
</presource>
 
Using the ADD_SUBDIRECTORY command is analagous to recursively calling make, and in many cases, this is precisely what happens. Other generators may interpret this command in a slightly different manner, however: for instance, the MSVC generator might decided to turn each of these directories into multiple projects within a solution. In either case, the child CMakeLists.txt inherits the environment of the parent, so variables are propogated. You can use this to your advantage by doing dependency resolution and setting up critical, shared variables in the root CMakeLists.txt file.
Line 115 ⟶ 114:
* INCLUDE() operates from the current working directory. ADD_SUBDIRECTORY will change the current working directory to the supplied path first.
 
== Applying CMake to your Operating System ==
 
=== Building Assembly Code ===
 
Unless you intend to use somebody else's kernel and write your operating system completely from portable code, it is very likely that you will have the need to have access to an assembler. For the kinds of projects that CMake was designed for, this rarely comes up; as an operating system designer, such support is probably critical to your project. Fortunately, some work has been done to address this issue, and in most cases, you can get away with not only detecting an assembler, but even specifying the syntax it uses. By using the ENABLE_LANGUAGE command, it is possible to turn on support for assembly:
 
<source lang="cmake">
<pre>
# We want an AT&T Syntax Assembler
ENABLE_LANGUAGE(ASM-ATT)
 
ADD_EXECUTABLE(foo bar.s baz.s)
</presource>
 
=== Setting Appropriate Build Options ===
 
Depending on your project, the stock compiler options may be insufficient for your needs. You may need to supply switches to your toolchain that affect linking or object assembly. CMake provides a number of ways of doing this:
Line 136 ⟶ 135:
* Link-time options can be set using SET_TARGET_FLAGS(''target'' PROPERTIES LINK_FLAGS "''flags''")
 
=== Build Profile Detection ===
 
Remember how we made the claim that the directory structure could be leveraged in order to help make our lives easier? You might have gotten some idea from the directory structure of how this might be accomplished, at least on a conceptual level. Consider the example provided by ''src/kernel''. Here, we have a well-defined heirarchy which allows us to narrow down on varying scopes of the actual kernel implementation. We can split our code into three directories, such that:
Line 161 ⟶ 160:
However, there is a slight problem: we need a way to report our findings to the caller. Remember that variables defined in functions go out of scope as soon as the function ends. We could use a macro here, at the risk of polluting the namespace, but it would be much better if we could export variables to the parent scope. Fortunately, the SET command accepts PARENT_SCOPE as an optional parameter. When this is used, it means that the variable being set should become part of the parent's environment. Let's write a small build profile function now:
 
<source lang="cmake">
<pre>
FUNCTION(LOAD_PROFILE ISA PLATFORM)
# Obtain sources for the ISA
Line 181 ⟶ 180:
# ...
ENDFUNCTION(LOAD_PROFILE)
</presource>
 
Now, all we have to do is call LOAD_PROFILE with the provided parameters, and we should be able to set up our build environment in a sane manner:
 
<source lang="cmake">
<pre>
FILE(GLOB GENERIC_SRCS "*.c")
 
Line 200 ⟶ 199:
SET_TARGET_PROPERTIES(kernel PROPERTIES LINK_FLAGS
"-T ${PLATFORM_LAYOUT} -N ${ISA_LINKER_FLAGS} ${PLATFORM_LINKER_FLAGS}")
</presource>
 
Here, we make a reasonable attempt to control the build order, but the truth is, we don't really know exactly what that order should be; it might
be dependent on the platform. For instance, for i386/pc, we might want a multiboot header, which must come within the first 8K of the kernel image. In that case, we must somehow control the ordering. We could have a place FIRST_SRCS variable present in the platform flags, then use the following loop to extract it from the list:
 
<source lang="cmake">
<pre>
FOREACH(I ${FIRST_SRCS})
# Assume path is relative to src/kernel
Line 217 ⟶ 216:
# During exports:
SET(FIRST_SRCS ${TMP_FIRST_SRCS})
</presource>
 
Now, all we have to do is put ${FIRST_SRCS} at the head of the list, and we can control the order in which our code is linked.
 
== See Also ==
 
=== Articles ===
* [[Makefile]] - One potential target for CMake. The tried and true method of build management.
 
=== External Links ===
* [http://www.cmake.org CMake Offical Page] - Contains useful links, including how to download and documentation
 
[[Category:Tools]]
Anonymous user
Cookies help us deliver our services. By using our services, you agree to our use of cookies.

Navigation menu