--- title: Xmake Getting Started Tutorial 4, C/C++ project description settings tags: [xmake, lua, c/c++, run, debug] date: 2019-11-10 author: Ruki outline: deep --- Xmake is a lightweight and modern C/C++ project build tool based on Lua. Its main features are: easy to use syntax, easy to use project maintenance, and a consistent build experience across platforms. This article mainly explains in detail how to write some commonly used basic xmake.lua description configurations to achieve some simple C/C++ project build management. For most small projects, these configurations are completely sufficient. In the later advanced tutorials in this series, I will explain in detail how to use some advanced features to configure the project more flexibly and customized. * [Project Source](https://github.com/xmake-io/xmake) * [Official Documents](https://xmake.io/) ### A simplest example One line of description compiles all c source files in the src directory, and then generates an executable file named demo. ```lua target("demo", {kind = "binary", files = "src/*.c"}) ``` The above is a condensed version. Generally, we recommend the following expansion method: ```lua target("demo") set_kind("binary") add_files("src/*.c") ``` The two are completely equivalent. If the configuration is short, it can be completely reduced to one line, and splitting into multiple lines is more convenient and flexible. If there is no special purpose, we will use the second paragraph. ### Configure project target type There are three main types of object files generated by common C/C++ projects: executable programs, static libraries, and dynamic libraries. We can set it through `set_kind()` configuration, corresponding to: binary, static, shared For example, if we want to compile the dynamic library, we just need to modify the kind: ```lua target("demo") set_kind("shared") add_files("src/*.c") ``` ### Add macro definition The compilation macro settings are used by most c/c++ projects. Generally, if we set compilation flags to be passed to gcc/clang, we need to configure: `-DXXX` In xmake, the `add_defines()` built-in interface is provided for configuration: ```lua target("demo") set_kind("shared") add_files("src/*.c") add_defines("XXX") ``` ### Conditional configuration What if we want to set different macro switches on different compilation platforms? We can easily implement it using lua's built-in if statement: ```lua target("demo") set_kind("shared") add_files("src/*.c") add_defines("XXX") if is_plat("linux", "macosx") then add_defines("YYY") end ``` We judge by `is_plat()`. If the current compilation target platform is linux or macosx, then the target will additionally add the `-DYYY` macro definition. ### Global configuration All the configurations under `target("demo")` belong to the target subdomain of demo, not global, so you will see that the indentation is usually added to the configuration to highlight the scope of influence. . Generally, if multiple targets are defined consecutively, the next target definition will automatically end the scope of the previous target. The configuration of each target is completely independent and does not interfere with each other: ```lua target("test1") set_kind("shared") add_files("src/*.c") add_defines("TEST1") target("test2") set_kind("shared") add_files("src/*.c") add_defines("TEST2") ``` For example, the above configuration has two targets, each with its own independent macro definition: `TEST1` and` TEST2`. So, we need to set a common macro definition for these two targets. How should we configure it? `add_defines("TEST")` is configured under each target? Of course it can, but this is a bit redundant, and it will be difficult to maintain if you configure more. In fact, we only need to place it in the global root scope: ```lua -- global settings add_defines("TEST") if is_arch("arm64", "armv7") then add_defines("ARM") end target("test1") set_kind("shared") add_files("src/*.c") add_defines("TEST1") target("test2") set_kind("shared") add_files("src/*.c") add_defines("TEST2") ``` All configurations outside the target belong to the global configuration. We can also call `target_end()` to forcibly end the target subdomain and switch back to the global scope: ```lua target("test1") set_kind("shared") add_files("src/*.c") add_defines("TEST1") target_end() -- global settings add_defines("TEST") if is_arch("arm64", "armv7") then add_defines("ARM") end target("test2") set_kind("shared") add_files("src/*.c") add_defines("TEST2") target_end() ``` ### Add compilation options If there are some compilation options, xmake does not provide built-in api settings, then we can degenerate to `add_cflags`,` add_cxflags`, `add_cxxflags` to set, However, this requires the user to determine the compilation platform, because not all compilation flags are supported on every platform. such as: ```lua add_cflags("-g", "-O2", "-DDEBUG") if is_plat("windows") then add_cflags("/MT") end ``` All option values are based on the definition of gcc as standard. If other compilers are not compatible (for example: vc), xmake will automatically convert them internally to option values supported by the corresponding compiler. The user does not need to worry about its compatibility. If other compilers do not have corresponding matching values, xmake will automatically ignore the compiler settings. We can also force the automatic detection of flags through the force parameter, and pass it directly to the compiler, even if the compiler may not support it, it will be set: ```lua add_cflags("-g", "-O2", {force = true}) ``` So how do you know which flags failed to be ignored, you can see with `-v` compilation, such as: ```bash $ xmake -v checking for the /usr/bin/xcrun -sdk macosx clang ... ok checking for the flags(-Oz) ... ok checking for the flags(-Wno-error=deprecated-declarations) ... ok checking for the flags(-fno-strict-aliasing) ... ok checking for the flags(-Wno-error=expansion-to-defined) ... no ``` Finally, note the differences between these three APIs: * `add_cflags`: add only C code-related compilation flags * `add_cxflags`: add C/c++ code related flags * `add_cxxflags`: add only c++ code-related compilation flags ### Add library related settings For the integrated use of a C/c++ library, you usually need to set the header file search directory, link library name, and library search directory, such as: ```lua target("test") set_kind("binary") add_links("pthread") add_includedirs("/usr/local/include") add_linkdirs("/usr/local/lib") ``` Generally, in order to ensure the dependency order of the linked libraries, the system library links are usually backward. We use `add_syslinks()` to set the system library links specifically, and `add_links()` is usually used for non-system library links: ```lua target("test") set_kind("binary") add_links("A", "B") add_syslinks("pthread") ``` In the above configuration, we added two third-party link libraries: A, B, and the system library pthread. The complete link sequence is: `-lA -lB -lpthread`, and syslinks will be placed at the end. If you are unsure of the actual linking order, we can execute `xmake -v` compilation to see the complete link parameter command line. ### Setting the language standard The c standard and c++ standard can be set at the same time, for example: ```lua -- set the c code standard: c99, c++ code standard: c++ 11 set_languages("c99", "c++11") ``` Note: The specified standard is not set, and the compiler will compile according to this standard. After all, each compiler supports different strengths, but xmake will try its best to adapt to the current standard of the compilation tool. For example: the compiler for windows vs does not support compiling c code according to the c99 standard, only c89 is supported, but xmake supports it as much as possible, so after setting the c99 standard, xmake will compile c code according to c++ code mode, which solves the problem of c code compiling c99 under windows. ### Set compilation optimization xmake provides several built-in compilation optimization configurations: none, fast, faster, fastest, smallest, aggressive, to achieve various levels of compilation optimization. ```lua set_optimize("fastest") ``` If you set it through flags, you also need to consider the different compilation options of different compilers. Xmake has an internal mapping process for it, which greatly facilitates users to provide cross-platform. If you want to view detailed mapping rules, you can go to the official documentation of xmake to check it: [Compile Optimization Settings](https://xmake.io) ### Debug and Release Mode Even though xmake provides `set_optimize` to simplify the complicated configuration of different compilers, for different compilation modes: debug/release, you still have to make some tedious judgments and configurations yourself: ```lua if is_mode("debug") then set_symbols("debug") set_optimize("none") end if is_mode("release") then set_symbols("hidden") set_strip ("all") if is_plat("iphoneos", "android") then set_optimize("smallest") else set_optimize("fastest") end end ``` These seemingly commonly used settings, if each project is repeated, it is also very tedious, resulting in xmake.lua not concise and readable, so xmake provides some commonly used built-in rules to simplify the setting: ```lua add_rules("mode.release", "mode.debug") ``` Only this line is needed, the effect is completely the same, and the user can also do some additional custom configurations to rewrite based on this: ```lua add_rules("mode.release", "mode.debug") if is_mode("release") then set_optimize("fastest") end ``` For example, I want to force fastest compilation optimization in release mode. Now that we have a mode configuration, how do we switch to debug mode compilation? (Default is release compilation) answer: ```lua xmake f -m debug; xmake ``` ### Add source files Finally, we introduce one of the most common and powerful settings of xmake, which is the configuration management of compiled source files: `add_files()`. We can use this interface to add various source files supported by xmake, such as: c/c++, asm, objc, swift, go, dlang and other source files, and even: `.obj`, `.a/.lib`, etc. Binary objects and library files. E.g: ```lua add_files("src/test_*.c") add_files("src/xxx/**.cpp") add_files("src/asm/*.S", "src/objc/**/hello.m") ``` The wildcard `*` matches files in the current directory, while `**` matches files in multiple directories. The use of `add_files` is actually quite flexible and convenient. Its matching pattern borrows the style of premake, but it is improved and enhanced. This makes it possible not only to match files, but also to filter out a batch of files with a specified pattern while adding files. E.g: ```lua -- Recursively add all c files under src, but not all c files under src/impl/ add_files("src/**.c|impl/*.c") -- Add all cpp files under src, but exclude src/test.cpp, src/hello.cpp and all cpp files with xx_ prefix under src add_files("src/*.cpp|test.cpp|hello.cpp|xx_*.cpp") ``` The files after the delimiter `|` are all files that need to be excluded. These files also support matching patterns, and multiple filtering patterns can be added at the same time, as long as they are separated by `|`. . One of the benefits of supporting filtering some files when adding files is that it can provide a basis for subsequent addition of files based on different switch logic. Note: In order to make the description more concise, the filtering description after `|` is based on a pattern: the directory before `*` in `src/*.cpp`. So the above example filters the files under src, which should be noted. After 2.1.6, `add_files` has been improved to support more fine-grained compilation option control based on files, for example: ```lua target("test") add_defines("TEST1") add_files("src/*.c") add_files("test/*.c", "test2/test2.c", {defines = "TEST2", languages = "c99", includedirs = ".", cflags = "-O0"}) ``` You can pass a configuration table in the last parameter of `add_files` to control the compilation options of the specified files. The configuration parameters are the same as those of the target, and these files will inherit the target's general configuration` -DTEST1`. After version 2.1.9, it supports adding unknown code files. By setting rule customization rules, these files can be custom built, for example: ```lua target("test") -- ... add_files("src/test/*. md", {rules = "markdown"}) ``` And after version 2.1.9, you can use the force parameter to forcibly disable the automatic detection of cxflags, cflags and other compilation options, and pass it directly to the compiler, even if the compiler may not support it, it will be set: ```lua add_files("src/*.c", {force = {cxflags = "-DTEST", mflags = "-framework xxx"}}) ``` ### Delete the specified source file Now that we ’ve talked about adding source files, how to delete them, let ’s just drop in. We only need to use the `del_files()` interface to delete the specified files from the list of files added by the `add_files` interface. : ```lua target("test") add_files("src/*.c") del_files("src/test.c") ``` In the above example, you can add all files except `test.c` from the` src` directory. Of course, this can also be achieved by `add_files("src/*.c|test.c")`. But this approach is more flexible. For example, we can use conditional judgment to control which files are deleted, and this interface also supports `add_files` matching mode, filtering mode, and batch removal. ```lua target("test") add_files("src/**.c") del_files("src/test * .c") del_files("src/subdir/*.c|xxx.c") if is_plat("iphoneos") then add_files("xxx.m") end ``` Through the above example, we can see that `add_files` and` del_files` are added and deleted sequentially according to the calling order, and delete one by `del_files("src/subdir/*.c|xxx.c")` Batch file, And exclude `src/subdir/xxx.c` (that is, do not delete this file).