Anonymous user
CMake Build System: Difference between revisions
Some copyediting, clarification, and fixes to syntax errors in some code blocks.
[unchecked revision] | [unchecked revision] |
m (Rate, reformat, syntax highlight, add to tool category) |
(Some copyediting, clarification, and fixes to syntax errors in some code blocks.) |
||
Line 1:
{{Rating|1}}
CMake is a cross-platform, multi-environment build system which can
== About CMake ==
CMake is
=== Who uses CMake? ===
Line 27:
* Will I be generating any code from templates?
The first question is largely one of preference. An ''in-source'' build means that the build output will be placed in the same directory
<pre>
Line 50:
</pre>
The above
*
* ISA-dependent code can be isolated from common code or other ISA-dependent code.
* Platform-dependent code (which may add further restrictions to ISA-dependent code) is separated from other platforms sharing the same ISA.
Line 60:
=== A Simple CMakeLists.txt ===
One of the nice things about CMake is that it affords you several programming concepts that you are already familiar with. As one would expect, CMake lets you perform build configuration through variables, which are typically defined in user-provided files named CMakeLists.txt. The resulting values of these variables, after cmake processing, can conveniently be found in a file called CMakeCache.txt.
Of course, variables aren't really enough, so CMake provides two kinds of procedures: macros and functions. Functions and macros are very subtly different: functions create their own environment
A useable CMakeLists.txt can have as little as two lines as code. One of the most important families of macros include <code>ADD_EXECUTABLE()</code> or <code>ADD_LIBRARY()</code>.
Each of these macros take several parameters: the target name as the first parameter and the source file(s) on which the target depends on as the remaining parameter(s).
Each of these each take several parameters, which includes the target name (1st param) and the source files. (remaining params) The CMAKE_MINIMUM_REQUIRED function is also required, because that is how CMake can tell whether or not it meets the requirements to parse your script. However, such a script does have its disadvantages: every time we add a new source file, we need to edit CMakeLists.txt. Fortunately, CMake comes with a built in file manipulation interface. In this case, we might want to use the GLOB operator for the FILE() command, which lets us specify a file globbing pattern to collect all of our source filepaths into one variable. The addition of this extra line of code can make CMake much more useful.▼
The <code>CMAKE_MINIMUM_REQUIRED()</code> function, which takes the version of CMake necessary to parse the CMakeList.txt file is also required, because that is how CMake can tell whether or not it meets the requirements to parse your script.
If you have dependencies you need to resolve, CMake can handle that too. The FIND_PACKAGE command can handle a variety of different dependencies, including libraries (like Boost). FIND_PACKAGE will attempt to locate a script by the name of Find''<1st param>''.cmake and handle it appropriately. In instances where FIND_PACKAGE is unable to resolve the package, it sets a special variable called ''<1st param>''_NOTFOUND, which you might use to detect optional dependencies. However, if a dependency is absolutely required, then you can simply supply REQUIRED as the second parameter. Failing to find the package will cause CMake to bail out.▼
▲
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.▼
▲If you have dependencies you need to resolve, CMake can handle that too. The <code>FIND_PACKAGE()</code> command can handle a variety of different types of dependencies, including libraries (like Boost). <code>FIND_PACKAGE()</code> will also attempt to locate a script by the name of <code>Find''<1st param>''.cmake</code> and handle it appropriately. In instances where <code>FIND_PACKAGE()</code> is unable to resolve the package, it sets a special variable called <code>''<1st param>''_NOTFOUND</code>, which you might use to detect optional dependencies. However, if a dependency is absolutely required, then you can simply supply <code>REQUIRED</code> as the second parameter. Failing to find the package will cause CMake to bail out.
▲
<source lang="cmake">
Line 90 ⟶ 94:
<pre>
$ cd project/
$ cmake .▼
$ mkdir build
$ cd build/
▲$ cmake ../
$ make
</pre>
Line 96 ⟶ 103:
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.
===
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
<source lang="cmake">
Line 107 ⟶ 114:
</source>
Using the <code>ADD_SUBDIRECTORY()</code> command is analagous to recursively calling make, and in many cases, this is precisely what happens. Other generators may interpret this command
Alternately, you can leverage the <code>INCLUDE()</code> command to directly insert CMake code into your CMakeLists.txt file at its point of invocation, which can be useful for important small snippets of code into your project. Note that there are some subtle differences between <code>INCLUDE()</code> and <code>ADD_SUBDIRECTORY()</code>:
* You can use <code>INCLUDE()</code> to include any file as CMake code. <code>ADD_SUBDIRECTORY()</code> expects a CMakeLists.txt file in the directory you point it at.
* <code>INCLUDE()</code> operates from the current working directory. <code>ADD_SUBDIRECTORY()</code> will change the current working directory to the supplied path
== Applying CMake to your Operating System ==
Line 118 ⟶ 125:
=== 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
<source lang="cmake">
Line 129 ⟶ 136:
=== Setting Appropriate Build Options ===
Depending on your project
* For C programs, you can use <code>CMAKE_C_FLAGS</code> in the same way you would use <code>$CFLAGS</code> in the context of make. <code>ADD_DEFINITIONS()</code> can also be used, but it is probably inadvisable to do so since a C flag variable exists by default.
* For other languages, (including assembly) use <code>CMAKE_''<lang>''_COMPILE_OBJECT</code>. For instance, if ASM-ATT is enabled, one would modify <code>CMAKE_ASM-ATT_COMPILE_OJBECT</code>.
* Link-time options can be set using <code>SET_TARGET_FLAGS(''target'' PROPERTIES LINK_FLAGS "''flags''")</code>.
=== 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
* <code>/src/kernel/</code> contains the code for the kernel. (platform-indepdendent code might go into this directory).
* <code>/src/kernel/
* <code>/src/kernel/
Let's consider the ARM branch of our code. While the i386 may see limited usage outside of IBM-compatible PCs, the ARM conversely is found in a number of environments. Many of those environments have their own quirks, such as how the memory bus is physically mapped. Will the kernel binary for a BeagleBone run on a Raspberry Pi? It
We can take two approaches to this problem: we can either make the assumption platforms (and ISAs for that matter) share some things in common, or make the assumption that they don't. In the former approach, we might assume that the following is true of platforms:
Line 153 ⟶ 160:
And likewise, we might say that each ISA provides:
*
*
So far, so good. We can approach this by writing a function in order to handle loading the right profile. When you write a function, you delimit the function body between the <code>FUNCTION()</code> and <code>ENDFUNCTION()</code> commands. The first parameter to <code>FUNCTION()</code> shall be the name of the function you are defining, and those remaining are formal parameters to the function.
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
<source lang="cmake">
Line 171 ⟶ 178:
# Now export our output variables
SET(PLATFORM_LAYOUT "${ISA}/${PLATFORM}/layout.ld")▼
SET(ISA_SRCS ${ISA_SRCS} PARENT_SCOPE)
SET(PLATFORM_SRCS ${PLATFORM_SRCS} PARENT_SCOPE)
▲ SET(PLATFORM_LAYOUT "${ISA}/${PLATFORM}/layout.ld" PARENT_SCOPE)
# And specific flags
Line 182 ⟶ 189:
</source>
Now, all we have to do is call <code>LOAD_PROFILE()</code> with the provided parameters, and we should be able to set up our build environment in a sane manner:
<source lang="cmake">
Line 195 ⟶ 202:
SET(CMAKE_ASM-ATT_COMPILE_OBJECT
"<CMAKE_ASM-ATT_COMPILER> ${
SET(CMAKE_C_FLAGS "${ISA_C_FLAGS} ${PLATFORM_C_FLAGS}")
SET_TARGET_PROPERTIES(kernel PROPERTIES LINK_FLAGS
Line 202 ⟶ 209:
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 <code>FIRST_SRCS()</code> variable present in the platform flags, then use the following loop to extract it from the list:
<source lang="cmake">
FOREACH(I ${FIRST_SRCS})
# Assume path is relative to src/kernel
LIST(APPEND TMP_FIRST_SRCS "${CMAKE_CURRENT_LIST_DIR}/${I}"
ENDFOREACH(I)
Line 218 ⟶ 225:
</source>
Now, all we have to do is put <code>${FIRST_SRCS}</code> at the head of the list, and we can control the order in which our code is linked.
== See Also ==
|