﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-溪流漫话-随笔分类-Mac</title><link>http://www.cppblog.com/Streamlet/category/21410.html</link><description>荒废中，求警醒~</description><language>zh-cn</language><lastBuildDate>Sun, 06 Nov 2022 14:10:12 GMT</lastBuildDate><pubDate>Sun, 06 Nov 2022 14:10:12 GMT</pubDate><ttl>60</ttl><item><title>建立一个简单干净的 gn+ninja 工具链</title><link>http://www.cppblog.com/Streamlet/archive/2022/11/06/229483.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Sat, 05 Nov 2022 18:05:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2022/11/06/229483.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/229483.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2022/11/06/229483.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/229483.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/229483.html</trackback:ping><description><![CDATA[
<h2>背景</h2>
<p>事情的起因是，想找个跨 Windows 和 Mac 的构建方案。第一考虑自然是 CMake，毕竟基本上是事实标准了。</p>
<p>但是研究了一下 Modern CMake，也就是以 target 为核心的理念。但发现看了好几天文档，也折腾出了可用的东西，但仍然是没梳理清楚什么理念、原理。然后 CMake 本身语法就很复杂，再加上搞 target 一套概念，要给 target 设置各种属性之类的，有点强行 OOP 的感觉……但其实我们只是需要一个 include_dir 和 lib_dir 而已，其他都是浮云~</p>
<p>但如果退回到传统模式，不用 Modern 概念呢，好像可以将就，但第一不去用一个工具的最新模式，好像有点不上进的感觉（python 2 除外）；第二，CMake 的两大痛点——语法特立独行、文档晦涩难懂——还是让人有点不爽。</p>
<p>那跳出来看别的选择呢？目前相对成熟的也只有 Google 的 gn+ninja 方案了。gn 这套东西在 Chromium 里是完全配置好的，用起来还算顺手，但要是独立拿出来呢，就没那么便宜了。关键是它的 toolchain 是要自己定义的。</p>
<p>之前还在公司搞客户端的时候，大家就从 Chromium 里面把 build、build_overrides 等等东西全部拷出来，好家伙，几百 MB 甚至上 G。但是公司里嘛，没人管干不干净，怎么快怎么来。后来又看到 Google 自家的 Crashpad 里面也用了这套构建，但工具链被简化了一下，叫 mini_chromium。这个比 Chromium 里的小多了，是可以拿过来直接用的，缺少一些配置可以自己加。但是呢，像我们这种洁癖，仍然是受不鸟的。所以呢，我们要干干净净的建立一套工具链。</p>
<h2>构建系统安装</h2>
<p>首先，我们明确定位。gn 和 ninja 都是开发机上需要预装的，不是软件提供的。Chromium 的搞法是自己提供，gn 的文档也说让开发者提供工具。但这套思路跟传统的理念是冲突的。同时，自己安装工具成本是比较低的：</p>
<ul>
<li>linux
<ul>
<li>ninja 在主流包管理系统里已经有了，包名可能是 ninja 或 ninja-build，直接安装就可以</li>
<li>gn 在部分包管理系统有，尝试包名 gn 或 gn-build 等，没有的话可以下载<a href="https://chrome-infra-packages.appspot.com/dl/gn/gn/linux-amd64/+/latest">二进制版本</a>，或者从源代码自行编译</li>
</ul>
</li>
<li>mac
<ul>
<li>ninja 在 brew 里包名叫 ninja，在 MacPorts 里包名叫 ninja-build</li>
<li>gn 在 brew 里没有，可以下载<a href="https://chrome-infra-packages.appspot.com/dl/gn/gn/mac-amd64/+/latest">二进制版本</a>；在 MacPorts 里叫 gn-devel</li>
</ul>
</li>
<li>win
<ul>
<li>ninja 可以在 <a href="https://github.com/ninja-build/ninja/releases">GitHub</a> 下载二进制版本，gn 可以在 gn 官网下载<a href="https://chrome-infra-packages.appspot.com/dl/gn/gn/windows-amd64/+/latest">二进制版本</a></li>
</ul>
</li>
</ul>
<p>自己下载的设置到 PATH，测试 <code>gn --version</code> 以及 <code>ninja --version</code>，能运行即可</p>
<h2>目标</h2>
<p>希望做到提供一个 git repo，使用者 clone 到自己项目的 build 目录，然后使用者只要在 .gn 文件里配置</p>
<pre><code class="language-gn">buildconfig = &quot;//build/BUILDCONFIG.gn&quot;
</code></pre>
<p>就可以使用我们提供的工具链，在 PC 三端进行构建。</p>
<p>使用者的唯一负担就是编写自己的 BUILD.gn</p>
<h2>工具链搭建</h2>
<p>首先我们看 gn 的文档，以及它的例程 simple_build 里的工具链配置：</p>
<p>https://gn.googlesource.com/gn/+/HEAD/examples/simple_build/build/toolchain/BUILD.gn</p>
<p>这个是可以直接用的，只不过只有 linux 端（当然 mac 也能用）。我们再结合 chrome 里的工具链配置，进行一些完善。</p>
<h3>基础概念</h3>
<p>首先我们了解 gn 体系需要的最小配置是什么。</p>
<p>第一，它需要在根目录写一个 .gn 文件，在里面定义 buildconfig，指到另一个文件，一般是</p>
<pre><code class="language-gn">buildconfig = &quot;//build/BUILDCONFIG.gn&quot;
</code></pre>
<p>第二、BUILDCONFIG.gn 里面需要设置默认工具链，也就是写一行</p>
<pre><code class="language-gn">set_default_toolchain(&quot;//build/toolchain:gcc&quot;)
</code></pre>
<p>第三、定义工具链，如上例的 //build/toolchain:gcc。</p>
<p>需要在 build/toolchain 下建立 BUILD.gn 文件，内容是</p>
<pre><code>toolchain(&quot;gcc&quot;) {
	# ...
}
</code></pre>
<p>最后在 toolchain 里定义各种 tool。</p>
<h3>工具链中的工具</h3>
<p>这部分文档在这里：https://gn.googlesource.com/gn/+/main/docs/reference.md#func_tool</p>
<p>简单复述一下，可被定义的工具有：</p>
<ul>
<li>编译工具:<br />
&quot;cc&quot;: C 编译器<br />
&quot;cxx&quot;: C++ 编译器<br />
&quot;cxx_module&quot;: 支持 module 的 C++ 编译器<br />
&quot;objc&quot;: Objective C 编译器<br />
&quot;objcxx&quot;: Objective C++ 编译器<br />
&quot;rc&quot;: Windows 资源脚本编译器<br />
&quot;asm&quot;: 汇编器<br />
&quot;swift&quot;: Swift 编译器</li>
<li>链接工具:<br />
&quot;alink&quot;: 静态库链接器<br />
&quot;solink&quot;: 动态库链接器<br />
&quot;link&quot;: 可执行文件链接器</li>
</ul>
<p>（其他的就先不看了）</p>
<p>我们来看一下 https://gn.googlesource.com/gn/+/HEAD/examples/simple_build/build/toolchain/BUILD.gn 的一些关键配置：</p>
<pre><code class="language-gn">toolchain(&quot;gcc&quot;) {
  tool(&quot;cc&quot;) {
    command = &quot;gcc -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}&quot;
    outputs = [ &quot;{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o&quot; ]
    # ...
  }
  tool(&quot;cxx&quot;) {
    command = &quot;g++ -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}&quot;
    outputs = [ &quot;{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o&quot; ]
    # ...
  }
  tool(&quot;alink&quot;) {
    command = &quot;rm -f {{output}} &amp;&amp; ar rcs {{output}} {{inputs}}&quot;
    outputs = [ &quot;{{target_out_dir}}/{{target_output_name}}{{output_extension}}&quot; ]
    # ...
  }
  tool(&quot;solink&quot;) {
    command = &quot;g++ -shared {{ldflags}} -o $sofile $os_specific_option @$rspfile&quot;
    outputs = [ sofile ]
    # ...
  }
  tool(&quot;link&quot;) {
    command = &quot;g++ {{ldflags}} -o $outfile -Wl,--start-group @$rspfile {{solibs}} -Wl,--end-group {{libs}}&quot;
    outputs = [ outfile ]
    # ...
  }
  tool(&quot;stamp&quot;) {
    command = &quot;touch {{output}}&quot;
  }
  tool(&quot;copy&quot;) {
    command = &quot;cp -af {{source}} {{output}}&quot;
  }
}
</code></pre>
<p>可以看到，cc 和 cxx 执行 command 后，生成 .o 文件，然后这些 .o 文件可以作为 alink、solink、link 的 inputs，被它们 command 继续使用，最后输出静态库、动态库以及可执行文件。</p>
<p>其余属性可以查文档了解含义。</p>
<h3>对比 Chromium 中的配置</h3>
<h4>Linux</h4>
<p>主要配置在这里：https://source.chromium.org/chromium/chromium/src/+/main:build/toolchain/gcc_toolchain.gni</p>
<p>也是 gcc 的，与 simple_build 里的大同小异，没有特别的。</p>
<h4>Mac</h4>
<p>主要配置在这里：https://source.chromium.org/chromium/chromium/src/+/main:build/toolchain/apple/toolchain.gni</p>
<p>区别有：</p>
<ul>
<li>用 clang 系列编译工具，而不是 gcc</li>
<li>alink 不是用 ar，而使用 libtool</li>
<li>solink 的默认扩展名改成了 dylib</li>
<li>用了一个 linker_driver.py 来支持生成 dSYM，在里面调用了 dsymutil 和 strip</li>
</ul>
<h4>Win</h4>
<ul>
<li>编译用 cl，静态库链接用 lib，动态库和可执行文件的链接用 link</li>
<li>lib_switch = &quot;&quot;，lib_dir_switch = &quot;/LIBPATH:&quot;；前两者 lib_switch = &quot;-l&quot;，lib_dir_switch = &quot;-L&quot;</li>
</ul>
<h3>建立我们的工具链</h3>
<p>基本上是根据上面分析的要点配置，最终结果在此：https://github.com/Streamlet/gn_toolchain</p>
<p>新增的一些差异有：</p>
<ul>
<li>
<p>增加全局参数 is_debug，可以在 <code>gn gen out --args=&quot;is_debug=true&quot;</code>传入。默认 is_debug 为 false，开启所有优化。</p>
</li>
<li>
<p>mac 下生成 dSYM 不使用 python 脚本，直接是 <code>$ld ... &amp;&amp; dsymutil ... &amp;&amp; strip</code></p>
</li>
<li>
<p>mac 下加了一个 template：app_bundle，用来生成 xxx.app，主要配置来自于 create_bundle 文档里的例子</p>
</li>
<li>
<p>win 下增加了一些配置集</p>
<ul>
<li>
<p>动态/静态链接 CRT：//build/config/win:console_subsystem、//build/config/win:static_runtime</p>
</li>
<li>
<p>控制台程序、Win32 程序：//build/config/win:console_subsystem、//build/config/win:windows_subsystem</p>
<p>这个其实一般情况下用不着，只要入口函数是 main/WinMain，link 默认就是控制台程序/Win32 程序</p>
</li>
<li>
<p>XP 支持：//build/config/win:console_subsystem_xp、//build/config/win:windows_subsystem_xp</p>
<p>具体实现是链接参数加 /SUBSYSTEM:CONSOLE,5.01 或 /SUBSYSTEM:WINDOWS,5.01。关键是后面的版本号 5.01，为了加版本号则必须指定子系统名称，所以分了 console_subsystem_xp 和 windows_subsystem_xp。又，xp 这里提供了两个 subsystem 的配置集，非 xp 也提供两个。</p>
<p>但是我们没有加 _WIN32_WINNT=0x0501、WINVER=0x0501、_USING_V110_SDK71_，也没有指定必须使用 7.0 版本的 SDK，这些都是非必须的，只要不用到 XP 以后添加的 API 即可。使用者可以在自己的 target 里面定义这些宏。</p>
</li>
</ul>
</li>
</ul>
<h2>使用案例</h2>
<p>提供一个使用案例：https://github.com/Streamlet/gn_toolchain_sample</p>
<p>因为它以 git submodule 形式引用了 https://github.com/Streamlet/gn_toolchain，所以 git clone 以后，需要 <code>git submodule update --init</code>一下。</p>
<p>然后在根目录执行：（确保 gn 和 ninja 已经在 PATH 中）</p>
<pre><code class="language-sh">gn gen out
ninja -C out
</code></pre>
<p>即可。</p>
<p>Mac 下会额外生成一个 objc 项目 objc_console_application 以及一个 app_bundle：ns_application.app。</p>
<p>Win 会额外生成一个 Win32 项目 win32_application。</p>
<p>Win 下需要先执行一下 Visual Studio 带的命令行环境，如 VS 2022 Community 的 “x64 Native Tools Command Prompt for VS 2022”，cl 等工具才会可用。</p>
<p>如果要测试 XP（32位），用“x86 Native Tools Command Prompt for VS 2022”进入，CD 到项目目录，执行：</p>
<pre><code class="language-gn">gn gen out --args=&quot;target_cpu=\&quot;x86\&quot;&quot;
ninja -C out
</code></pre>
<h2>总结</h2>
<p>我们只用几十 KB 的大小完成了跨端支持，是很轻量的一个配置。如果您觉得实用并认可这种方式，欢迎一起来维护、完善。</p>
<img src ="http://www.cppblog.com/Streamlet/aggbug/229483.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2022-11-06 02:05 <a href="http://www.cppblog.com/Streamlet/archive/2022/11/06/229483.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>