﻿<?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++博客-溪流漫话-随笔分类-Linux</title><link>http://www.cppblog.com/Streamlet/category/15218.html</link><description>荒废中，求警醒~</description><language>zh-cn</language><lastBuildDate>Sat, 05 Nov 2022 18:05:21 GMT</lastBuildDate><pubDate>Sat, 05 Nov 2022 18:05:21 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><item><title>折腾了两天 LFS</title><link>http://www.cppblog.com/Streamlet/archive/2010/10/19/130365.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Mon, 18 Oct 2010 16:59:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2010/10/19/130365.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/130365.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2010/10/19/130365.html#Feedback</comments><slash:comments>13</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/130365.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/130365.html</trackback:ping><description><![CDATA[<p>我自认为一向是很不感冒Linux那些东东的，也不知道为什么，前两天突然就心血来潮去搞一番LFS。于是很有纪念意义，特此记录。</p> <p>起先准备搞的是 LFS 6.1，因为只有 6.1 有官方中文手册。但是我的宿主系统是 Arch Linux 2010.05，也许太新了，刚开始编译 gcc 4.0.3 就过不了。后来就放弃了，换 6.7 的玩。</p> <p>说到底这是件很无聊的事情。打过的最多的命令就是<br>tar -xzvf ...<br>tar -xvjf ...<br>./configure ...<br>make<br>make install<br>rm -rf ...</p> <p>这么一套操作重复个百来下，加上无休止的等待，就成了。</p> <p>以为成了，结果出状况了：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/-LFS_141B7/%E5%A4%B1%E8%B4%A5_2.jpg"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="失败" border="0" alt="失败" src="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/-LFS_141B7/%E5%A4%B1%E8%B4%A5_thumb.jpg" width="724" height="405"></a></p> <p>似乎好像大概可能它找不到硬盘，而且我明明要 sda2 的，它却找了 sdb2。</p> <p>第一，在 8.4.2&nbsp; grub-mkconfig -o /boot/grub/grub.cfg 的时候，grub的配置文件是利用它的命令自动生成的，结果它找错了。可能是因为我一开始装的时候拿块硬盘是sdb，它就认sdb了。或者是之前那条命令 grub-install --grub-setup=/bin/true /dev/sda 我自作聪明地以为它要实际操作，把最后的sda换成了sdb的缘故吧。</p> <p>第二是因为我在 VMWare 上跑，虚拟硬盘是 SCSI 的，编译内核之前没配置对。后来看到了 <a title="http://www.cnblogs.com/benben7466/archive/2009/04/01/1427404.html" href="http://www.cnblogs.com/benben7466/archive/2009/04/01/1427404.html" target="_blank">http://www.cnblogs.com/benben7466/archive/2009/04/01/1427404.html</a>，于是把 fusion mpt 中的全选上了（文章中的 Fusion MPT (base + ScsiHost) drivers 我没找到，于是全选了= =），重新编译内核，启动成功。</p> <p>谨以此截图留念：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/-LFS_141B7/%E6%88%90%E5%8A%9F_2.jpg"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="成功" border="0" alt="成功" src="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/-LFS_141B7/%E6%88%90%E5%8A%9F_thumb.jpg" width="723" height="403"></a></p> <p>流水账结束了。正文开始。</p> <p>我想谈谈对 LFS 中的工具链切换的理解。请允许我把 binutil 和 gcc 简称为编译系统，把 glibc 简称为运行库。用下面这张图简单表示一下：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/-LFS_141B7/image_2.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/-LFS_141B7/image_thumb.png" width="534" height="312"></a></p> <p>首先，利用宿主系统的编译系统编译出一个依赖于宿主运行库的新的编译系统（Pass1），和独立的新的运行库（Pass1）。然后再利用运行在宿主运行库上的新的编译系统（Pass1）编译出依赖于新的运行库（Pass1）的新的编译系统（Pass2）。这样，产生了一个脱离宿主的编译环境，利用这个编译环境编译出其他工具，一起作为临时系统使用。</p> <p>再在临时系统中，编译出目标系统中要用的运行库（Pass2）和依赖于目标运行库（Pass2）的编译系统（Pass3）。目标系统中的编译环境搭建完毕。最后使用这个编译环境编译出目标系统上的其他软件。</p> <p>不知道这个陈述有没有问题？如果没说错的话，问题来了。其实，得到的临时系统，已经是一个不依赖于宿主的系统了，何不把这个作为 LFS 的目标系统呢？理由似乎只有“使它更纯净”之类的了。如果追求纯净，多搞一遍是不够的，还是不纯净的；既然反正不纯净，为啥多做一遍呢？</p> <p>由此，我想到了挺久以前我一直压抑在心里的问题：同一个环境下的编译器的升级问题。加入已经有了 1.0 版的编译器执行文件和 2.0 版的编译器源代码，要如何产生 2.0 版的编译器的执行文件呢？是拿 1.0 版的去编译 2.0 的源代码，然后直接发布？还是再用新的 2.0 版的编译器再编译一遍（两遍、三遍）？6.1 版的 LFS 手册部分解决了这个疑问，它提到了在 gcc pass1 的时候做 bootstrap，即编译一次后用产生的新编译器编译第二遍，再用产生的新的编译器编译第三遍，比较第二遍与第三遍结果是否相同。（LFS 6.7无此要求。）不知道这里的相同是指逐字节相同吗？如果是，这在理论上可能吗？我的想法是，已有的1.0版可能存在一个固有问题（或者不称为问题，叫“特征”吧），它可能将影响到后面的一切，2.0 的编译器不管自举几遍，或许总是无法完全消灭来自 1.0 的某些影响？</p> <p>不知道现在理论上是怎样回答这个问题的。工程上又是如何对待这个问题的呢？</p> <p>这也许是个比较深层次的问题。抑或只是一个很肤浅的问题，只是我心生执念罢了。期待解惑 ~_~</p><img src ="http://www.cppblog.com/Streamlet/aggbug/130365.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2010-10-19 00:59 <a href="http://www.cppblog.com/Streamlet/archive/2010/10/19/130365.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>