CoderX9527 发表于 2024-10-19 14:19

《CMake 构建实战-项目开发卷》阅读分享2 -- CMake基础语法

<div class='showpostmsg'><div>&nbsp;</div>

<div><strong><span style="font-size:18px;">CMake 程序分类</span></strong></div>

<div>根据用途可以分为三类:</div>

<ul>
        <li><strong>目录程序 CMakeLists.txt</strong><br />
        我们开发者绝大部分情况下接触的是这里文件,即 CMake 被用于构建时,需要处理项目的源程序。处理的入口,就是项目顶层目录下的 CMakeLists.txt。CMakeLists.txt 构成了源程序逻辑上的目录结构。</li>
        <li><strong>脚本 script.cmake</strong><br />
        这里程序我们很少接触,是指在行以 -P 参数运行 CMake 命令行工具执行脚本类型的 CMake 程序。这种 CMake 程序不会配置生成任何构建系统,因此一些与构建相关的 CMake 命令是不允许出现在脚本中的。<br />
        【注:绝大部分情况下,可以忽略这种用法】</li>
        <li><strong>模块 module.cmake</strong></li>
</ul>

<div>CMake 的目录程序和脚本均可以通过 include 等命令引用 CMake 模块程序。CMake 提供了很多预制模块供用户使用,多数与环境检测、搜索使用第三方库有关。</div>

<div>【注:这种模块使用的不多,但是有必要了解】</div>

<h1>命令调用</h1>

<div>CMake 程序几乎完全由命令调用构成。是因为除此之外,只剩下注释和空白符。CMake 程序中的 if 条件分支、for 循环等程序结构统一采用命令调用形式。</div>

<div>命令调用的形式即先写命令名称,其后跟括号括起来的命令参数。注意,CMake 的命令一般不区分大小写,但是一般使用小写。</div>

<div>最简单的命令调用示例如下:</div>

<div></div>

<h2>命令参数</h2>

<div>它出现在命令的括号中,可以由以下三种类型:</div>

<ol>
        <li><strong>引号参数</strong><br />
        即用引号括起来的参数,而且 CMake 规定它必须使用双引号。引号参数作为一个整体传递给命令,引号中间的空白符也会作为这个整体的一部分。那么引号参数中不仅可以包含空格,也可以包含换行符。<br />
        引号参数示例<br />
        </li>
        <li><strong>非引号参数</strong><br />
        非引号参数即没有被引号包裹的参数。这种参数不能包含任何空白符,也不能包括圆括号、#符号、双引号和反斜杠,除非经过转移。非引号参数也支持变量引用和转义字符。<br />
        非引号参数不总是作为一个整体传递给命令,它有可能被拆分成若干参数传递。实际上,非引号参数在被传递前,会被当做CMake列表来处理,而列表中的每一个元素都会作为一个单独的参数传递个命令。<br />
        以下是一个非引号参数的示例:<br />
        </li>
        <li><strong>括号参数</strong></li>
</ol>

<div>与引号参数一样,CMake 的括号参数也会作为一个整体传递给命令。通过自定义的特殊括号将原始文本包括在其中,它不处理文本中的任何特殊字符(包括转义字符)或变量引号语法,直接保留原始文本。</div>

<div>一个简单的括号参数示例</div>

<div></div>

<h1>变量</h1>

<div>CMake 变量与其他编程语言的不同之处在于:其数据类型总是文本类型的,只不过在使用时,文本型的变量可能被一些命令解释成数值、列表等。</div>

<h2>变量分类</h2>

<div>CMake 变量有三种:</div>

<ol>
        <li><strong>普通变量</strong><br />
        大多数变量都是普通变量,它们具有特定的作用于。<br />
        </li>
        <li><strong>缓存变量</strong></li>
</ol>

<div>被缓存起来的变量,被持久化到缓存文件 CMakeCache.txt 中。 CMake 程序每次执行时都会从缓存文件中读取缓存变量的值。这样可以避免每次都执行一些耗时的过程来获取数据。</div>

<div>缓存变量具有全局作用域。</div>

<div>【题外话】说到缓存变量,都是泪啊。第一次迁移公司项目到 CMake + GCC 编译环境,把SDK 拆分成了各个模块module1/module2/module3等等,应用文件夹下不同的应用 app1/app2/app3 依赖不同的模块,所以不是所有的模块都需要参与编译,而且某些模块有互斥关系。我第一次写 CMake 缺少经验,发现 appX 中设置的变量 MODULE_&lt;x&gt;_CFG怎么在模块文件夹下总是初始值,又或者在某个地方修改了 MODULE_&lt;x&gt;_CFG 变量的值但是别的地方没生效,这个问题把我带坑里好长时间。后来查资料解决办法是用缓存变量,SDK 需要配置两次,第一次配置会修改 MODULE_&lt;x&gt;_CFG 变量,第二次才把修改后的 MODULE_&lt;x&gt;_CFG 作用域整个工程,所有的模块都可以正确识别这个变量。虽说这种用法可以解决问题,但是不够优雅。</div>

<div>定义缓存变量的方法</div>

<div>set(&lt;变量名&gt; &lt;值&gt; &hellip; CACHE &lt;变量类型&gt; &lt;变量描述&gt; )</div>

<div>变量类型有以下几种:</div>

<ul>
        <li>BOOL 布尔类型;</li>
        <li>FILEPATH 文件路径类型;</li>
        <li>PATH 目录路径类型;</li>
        <li>STRING 文本类型</li>
        <li>INTERNAL 内部使用(隐含设置 FORCE 参数)</li>
</ul>

<ol>
        <li><strong>环境变量</strong></li>
</ol>

<div>即操作系统中的变量,因此它对于 CMake 进程来说具有全局的作用域。</div>

<div>定义环境变量的语法如下:</div>

<div>set (ENV${&lt;变量名&gt;} &lt;值&gt;)</div>

<div>通过 CMake 的 set 命令定义的环境变量只会影响当前的 CMake 进程,不会影响到父进程或者系统的环境变量设置。</div>

<h1>列表(变量)</h1>

<div>列表即分号隔开的字符串。</div>

<div>定义列表变量没有什么特别的,可以用 set 命令定义列表变量。首先可以利用引号参数直接定义一个包含分号的字符串,这就是一个列表。其次 set 命令还支持指定多个作为变量值的参数,这样引号参数和非引号参数都可以使用。</div>

<div></div>

<h1>条件语法</h1>

<div>在 CMake 中几乎一切都是命令,没有表达式语句,即条件语法也是命令中特定的参数形式。</div>

<h2>常量、变量和字符串条件</h2>

<div>常量、变量和字符串条件这3种条件在形式上完全一致,需要根据上下文及量的值来判断具体是哪一种条件。</div>

<h3>常量条件</h3>

<div>常量条件仅有一个常量组成,分为真值常量和假值常量。</div>

<div>if 命令中使用的常量条件形式如下:</div>

<table border="1">
        <tbody>
                <tr>
                        <td>常量类型</td>
                        <td>常量值</td>
                        <td>条件结果</td>
                </tr>
                <tr>
                        <td>真值常量</td>
                        <td>1/ON/YES/TRUE/Y/非零数值(不区分大小写)</td>
                        <td>真</td>
                </tr>
                <tr>
                        <td>假值常量</td>
                        <td>0/OFF/NO/FALSE/N/IGNORE/空字符串/NOTFOUND/或以-NOTFOUND结尾的字符串(不区分大小写)</td>
                        <td>假</td>
                </tr>
        </tbody>
</table>

<h3>变量和字符串</h3>

<div>如果条件中仅包含一个字符串,且这个字符串不是真值常量或者假值常量,那么它还有可能是一个变量的名称。如果以这个字符串为名的变量确实存在,则它是一个变量条件,否则是一个字符串条件。它们的形式如下:</div>

<div>if (&lt;字符串|变量&gt;)</div>

<div>endif()</div>

<ul>
        <li>如果条件中的字符串是一个变量的名称,且这个变量的值不是一个假值常量,那么条件为真。</li>
        <li>在其他情况下(如果指定的变量的值为假或者变量为定义),条件为假。</li>
</ul>

<div></div>

<div>作者在此处举了一个奇怪的变量的示例,如下所示:</div>

<div></div>

<div>代码中(1)处 if(on) 遇到了 on ,它是一个字符串常量,为真值,所以调用了第6行代码打印 ON。</div>

<div>代码中(2)处 if(${on}) 先取出变量 on 的值,即 OFF 字符串常量,而在 if() 条件中 OFF 是假值,故调用了第14行代码。</div>

<div>有点绕,注意 if() 条件先判断字符串字面量是否是常量,然后再判断是否是变量。</div>

<h2>逻辑运算</h2>

<div>条件语法中可以包含 AND/OR/NOT 三种逻辑运算,参与运算的也是符合条件语法的参数:</div>

<div>if (条件1 AND 条件2)</div>

<div>endif()</div>

<div>if (条件2 OR 条件2)</div>

<div>endif()</div>

<div>if (NOT 条件)</div>

<div>endif()</div>

<div>有编程经验的人一看就懂,无需多言。</div>

<h2>单参数条件</h2>

<div>即条件中调用命令,命令接收一个参数,以命令的结果作为条件的判断参数。一般用于存在性判断和类型判断。</div>

<table border="1">
        <tbody>
                <tr>
                        <td>条件语法</td>
                        <td>条件判断类型</td>
                        <td>描述</td>
                </tr>
                <tr>
                        <td>If(COMMAND &lt;命令名称&gt;)</td>
                        <td>命令判断</td>
                        <td>当&lt;命令名称&gt;指代一个可被调用的命令、宏、或函数时,条件为真,否则为假</td>
                </tr>
                <tr>
                        <td>If(POLICY &lt;策略名称&gt;)</td>
                        <td>策略判断</td>
                        <td>当&lt;策略名称&gt;指代一个已定义的策略时,条件为真,否则为假</td>
                </tr>
                <tr>
                        <td>If(TARGET &lt;目标名称&gt;)</td>
                        <td>目标判断</td>
                        <td>当&lt;目标名称&gt;指代一个在任意目录用 add_executable/add_library或者add_custom_target 命令创建的目标时,条件为真,否则为假</td>
                </tr>
                <tr>
                        <td>If(TEST &lt;测试名称&gt;)</td>
                        <td>测试判断</td>
                        <td>当&lt;测试名称&gt;指代一个用add_test命令创建的测试时,条件为真,否则为假</td>
                </tr>
                <tr>
                        <td>If(DEFINED &lt;变量名称&gt;)</td>
                        <td>变量定义判断</td>
                        <td>当&lt;变量名称&gt;指代一个变量时,条件为真,否则为假</td>
                </tr>
                <tr>
                        <td>If(CACHE{&lt;缓存变量名称&gt;})</td>
                        <td>缓存变量定义判断</td>
                        <td>当&lt;缓存变量名称&gt;指代一个缓存变量时,条件为真,否则为假</td>
                </tr>
                <tr>
                        <td>If(ENV{环境变量名称})</td>
                        <td>环境变量定义判断</td>
                        <td>当&lt;环境变量名称&gt;指代一个环境变量时,条件为真,否则为假</td>
                </tr>
                <tr>
                        <td>If(EXISTS &lt;文件或目录路径&gt;)</td>
                        <td>文件或目录存在判断</td>
                        <td>当指定的&lt;文件或目录路径&gt;确实存在时,条件为真,否则为假。该条件要求路径为绝对路径。另外,如果路径指向一个符号链接,那么仅当符号链接对应的文件或目录存在时,条件为真。</td>
                </tr>
                <tr>
                        <td>If(IS_DIRECTORY &lt;目录路径&gt;)</td>
                        <td>目录判断</td>
                        <td>当指定的&lt;目录路径&gt;确实存在且是一个目录时,条件为真,否则为假。该条件要求路径为绝对路径。</td>
                </tr>
                <tr>
                        <td>If(IS_SYMLINK &lt;文件路径&gt;)</td>
                        <td>符号链接判断</td>
                        <td>当指定的&lt;文件路径&gt;确实存在且是一个符号链接时,条件为真,否则为假。该条件要求路径为绝对路径。</td>
                </tr>
                <tr>
                        <td>If(IS_ABSOLUTE &lt;路径&gt;)</td>
                        <td>绝对路径判断</td>
                        <td>当指定的&lt;路径&gt;是一个绝对路径时,条件为真,否则为假。</td>
                </tr>
        </tbody>
</table>

<h2>双参数条件</h2>

<div>双参数条件通过两个参数的取值来决定条件是否为真,一般用于比较关系的判断。</div>

<h3>数值比较</h3>

<div>数值比较有 &ldquo;小于&rdquo;,&ldquo;小于等于&rdquo;,&ldquo;等于&rdquo;,&ldquo;大于等于&rdquo;,&ldquo;大于&rdquo; 这5中比较关系。当关系成立时,条件为真,否则为假。</div>

<div>if (&lt;字符串 | 变量&gt; LESS &lt;字符串 | 变量&gt;) # 小于</div>

<div>if (&lt;字符串 | 变量&gt; LESS_EQUAL &lt;字符串 | 变量&gt;) # 小于等于</div>

<div>if (&lt;字符串 | 变量&gt; EQUAL &lt;字符串 | 变量&gt;) # 等于</div>

<div>if (&lt;字符串 | 变量&gt; GREATER_EQUAL &lt;字符串 | 变量&gt;) # 大于等于</div>

<div>if (&lt;字符串 | 变量&gt; GREATER &lt;字符串 | 变量&gt;) # 大于</div>

<div>对于 &lt;字符串&gt; 或 &lt;变量&gt; 的取值规则:如果它是一个存在的变量名,则变量的值,否则取字符串本身。由于这一组条件仅用于数值比较,取值会被转换为数值类型后再进行比较。</div>

<h3>字符串比较</h3>

<div>字符串同样也有5种关系,比较时会根据字典序决定两个字符串取值的大小:</div>

<div>if (&lt;字符串 | 变量&gt; STRLESS &lt;字符串 | 变量&gt;) # 小于</div>

<div>if (&lt;字符串 | 变量&gt; STRLESS_EQUAL &lt;字符串 | 变量&gt;) # 小于等于</div>

<div>if (&lt;字符串 | 变量&gt; STREQUAL &lt;字符串 | 变量&gt;) # 等于</div>

<div>if (&lt;字符串 | 变量&gt; STRGREATER_EQUAL &lt;字符串 | 变量&gt;) # 大于等于</div>

<div>if (&lt;字符串 | 变量&gt; STRGREATER &lt;字符串 | 变量&gt;) # 大于</div>

<h3>字符串匹配</h3>

<div>用于判断字符串是否匹配特定的正则表达式,仅当匹配成功时,条件为真,否则为假。</div>

<div>if (&lt;字符串 | 变量&gt; MATCHES &lt;正则表达式&gt;)</div>

<h3>版本号比较</h3>

<div>版本号的格式为: 主版本号[.此版本号[.补丁版本号[.修订版本号]]]</div>

<div>下面的双参数条件用于比较版本号:</div>

<div>if (&lt;字符串 | 变量&gt; VERSION_LESS &lt;字符串 | 变量&gt;) # 小于</div>

<div>if (&lt;字符串 | 变量&gt; VERSION_GREATER &lt;字符串 | 变量&gt;) # 大于</div>

<div>if (&lt;字符串 | 变量&gt; VERSION_EQUAL &lt;字符串 | 变量&gt;) # 等于</div>

<div>if (&lt;字符串 | 变量&gt; VERSION_LESS_EQUAL &lt;字符串 | 变量&gt;) # 小于或等于</div>

<div>if (&lt;字符串 | 变量&gt; VERSION_GREATER_EQUAL &lt;字符串 | 变量&gt;) # 大于或等于</div>

<h3>列表元素判断</h3>

<div>下面这个条件语法用于判断列表中的元素是否存在。当第二个参数&lt;列表变量&gt;的元素中存在第一个参数的取值时,条件为真,否则为假。</div>

<div>if (&lt;字符串 | 变量&gt; IN_LIST &lt;列表变量&gt;)</div>

<h1>命令定义</h1>

<ol>
        <li><strong>宏定义</strong><br />
        形式为:<br />
        macro(&lt;宏名&gt; [&lt;参数1&gt;&hellip;])<br />
        &lt;命令&gt;&hellip;<br />
        endmacro()<br />
        宏所包含的命令序列仅在宏被调用时执行,且执行时不会产生额外的作用域。<br />
        在 CMake 中,命令的名称不区分大小写,宏作为一种命令,其名称也不例外。<br />
        </li>
        <li><strong>函数定义</strong></li>
</ol>

<div>函数定义的形式如下:</div>

<div>function(&lt;函数名&gt; [&lt;参数1&gt;&hellip;])</div>

<div>&lt;命令&gt;&hellip;</div>

<div>endfunction()</div>

<div>函数会产生一个新的作用域,仅在函数内部生效。因此函数内部直接使用 set() 命令定义的变量是不能被外部所访问的。为了实现外部可访问的目的,必须为 set() 命令指定 PARENT_SCOPE 参数,是的变量定义到外部作用域。</div>

<div></div>

<div>在本示例中定义了一个函数 my_func 有两个形参 x1 和 x2. 函数内部的 loc_result 是局部变量,global_result 因为添加 PARENT_SCOPE 选项,所以它的作用域是调用者可见的。</div>

<div>截图中(1)处打印 loc_result 是一个空字符串,因为主代码中没有定义,在 my_func() 定义的局部变量 loc_result 不可见,所以是空字符串。而 global_result 是以对调用者可见的,所以结果不为空。</div>

<div>截图中(2)处因为现在主代码中设置了 loc_result 和 global_result,所以 loc_result 值不为空,只为 14 行设定的字符串。而 global_result 被 my_func() 修改了,所以15行设定的内容改变了。</div>

<div>&nbsp;</div>

<p><!--importdoc--></p>
</div><script>                                        var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;"   style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
                                       
                                        if(parseInt(discuz_uid)==0){
                                                                                                (function($){
                                                        var postHeight = getTextHeight(400);
                                                        $(".showpostmsg").html($(".showpostmsg").html());
                                                        $(".showpostmsg").after(loginstr);
                                                        $(".showpostmsg").css({height:postHeight,overflow:"hidden"});
                                                })(jQuery);
                                        }                </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>
页: [1]
查看完整版本: 《CMake 构建实战-项目开发卷》阅读分享2 -- CMake基础语法