├── .emmyrc.json ├── .gitignore ├── CONTRIBUTORS.md ├── LICENCE.md ├── README.md ├── build_gcc.md ├── build_llvm.md ├── doc ├── Makefile ├── make.bat └── source │ ├── conf.py │ ├── index.rst │ └── toolchains.rst ├── poetry.lock ├── pyproject.toml ├── script ├── .gdbinit ├── aarch64-libc.so ├── aarch64-libm.a.py ├── aarch64-libm.so ├── arm-hf-libc.so ├── arm-sf-libc.so ├── common.py ├── i686-libc.so ├── loongarch64-libc.so ├── loongarch64-loongnix-libc.so ├── mips64el-libc.so ├── python_config.py ├── python_config.sh ├── riscv64-libc.so ├── x86_64-libc.so ├── x86_64-libm.a.py └── x86_64-libm.so ├── test ├── dynamic_import_test │ ├── dynamic-import-test.py │ └── need_import.py ├── test_basic_configure.py ├── test_chdir_guard.py ├── test_dynamic_import.py ├── test_global_settings.py └── test_triplet_field.py ├── toolchains ├── __init__.py ├── build_gcc.py ├── build_gcc_source.py ├── build_llvm.py ├── build_llvm_source.py ├── common.py ├── download.py ├── download_source.py ├── gcc_environment.py ├── llvm_environment.py ├── utils.py ├── utils_source.py └── x86_64_w64_mingw32_clang.py ├── wrapper ├── build_gcc.py ├── build_llvm.py ├── download.py └── utils.py └── xmake ├── option.lua ├── rule.lua ├── toolchain.lua └── utility ├── common.lua ├── target.lua └── utility.lua /.emmyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "completion": { 3 | "enable": true, 4 | "autoRequire": true, 5 | "autoRequireFunction": "require", 6 | "autoRequireNamingConvention": "keep", 7 | "autoRequireSeparator": ".", 8 | "callSnippet": false, 9 | "postfix": "@" 10 | }, 11 | "diagnostics": { 12 | "disable": [ 13 | "undefined-global", 14 | "undefined-field" 15 | ], 16 | "enable": true, 17 | "globals": [], 18 | "globalsRegex": [], 19 | "severity": {}, 20 | "enables": [], 21 | "diagnosticInterval": null 22 | }, 23 | "signature": { 24 | "detailSignatureHelper": true 25 | }, 26 | "hint": { 27 | "enable": true, 28 | "paramHint": true, 29 | "indexHint": true, 30 | "localHint": true, 31 | "overrideHint": true 32 | }, 33 | "runtime": { 34 | "version": "LuaLatest", 35 | "requireLikeFunction": [], 36 | "frameworkVersions": [], 37 | "extensions": [], 38 | "requirePattern": [] 39 | }, 40 | "workspace": { 41 | "ignoreDir": [], 42 | "ignoreGlobs": [], 43 | "library": [], 44 | "workspaceRoots": [], 45 | "preloadFileSize": 0, 46 | "encoding": "utf-8", 47 | "moduleMap": [], 48 | "reindexDuration": 5000, 49 | "enableReindex": false 50 | }, 51 | "resource": { 52 | "paths": [] 53 | }, 54 | "codeLens": { 55 | "enable": true 56 | }, 57 | "strict": { 58 | "requirePath": false, 59 | "typeCall": false, 60 | "arrayIndex": true, 61 | "metaOverrideFileDefine": true 62 | }, 63 | "semanticTokens": { 64 | "enable": true 65 | }, 66 | "references": { 67 | "enable": true, 68 | "fuzzySearch": true, 69 | "shortStringSearch": false 70 | }, 71 | "hover": { 72 | "enable": true 73 | }, 74 | "documentColor": { 75 | "enable": true 76 | } 77 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | __pycache__ 3 | .VSCodeCounter 4 | .xmake 5 | build 6 | xmake.lua 7 | *.cpp 8 | dist 9 | .mypy_cache 10 | .coverage 11 | .pytest_cache 12 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | - **24bit-xjkp** <2283572185@qq.com> 4 | - **Arendelle** <2381642961@qq.com> 5 | - **situNagisa** <1300296933@qq.com> 6 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | # MIT Licence 2 | 3 | Copyright (C) 2024-2025 24bit-xjkp and others. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | The list of contributors can be found in the `CONTRIBUTORS.md` file. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GCC和LLVM工具链 2 | 3 | ## 工具链 4 | 5 | 本项目提供开发版的GCC和LLVM工具链。它们具有如下特征: 6 | 7 | - 带有Python支持的GDB 8 | - 带有Python支持的libstdc++ 9 | - 支持pretty-printer的.gdbinit 10 | - 使用相对路径,可重新部署 11 | - 已配置rpath并带有必要的动态库 12 | - 支持调试符号 13 | 14 | 支持如下工具链: 15 | 16 | | 工具链 | Host | Target | 17 | | :----- | :----------------- | :---------------------------------- | 18 | | gcc | x86_64-linux-gnu | x86_64-linux-gnu | 19 | | gcc | x86_64-linux-gnu | i686-linux-gnu | 20 | | gcc | x86_64-linux-gnu | x86_64-w64-mingw32 | 21 | | gcc | x86_64-linux-gnu | i686-w64-mingw32 | 22 | | gcc | x86_64-linux-gnu | arm-none-eabi | 23 | | gcc | x86_64-linux-gnu | arm-nonewlib-none-eabi | 24 | | gcc | x86_64-linux-gnu | x86_64-elf | 25 | | gcc | x86_64-linux-gnu | loongarch64-linux-gnu | 26 | | gcc | x86_64-linux-gnu | loongarch64-loongnix-linux-gnu | 27 | | gcc | x86_64-linux-gnu | riscv64-linux-gnu | 28 | | gcc | x86_64-linux-gnu | riscv64-none-elf | 29 | | gcc | x86_64-linux-gnu | aarch64-linux-gnu | 30 | | gcc | x86_64-linux-gnu | arm-linux-gnueabi | 31 | | gcc | x86_64-linux-gnu | arm-linux-gnueabihf | 32 | | gcc | x86_64-linux-gnu | mips64el-linux-gnuabi64 | 33 | | gcc | x86_64-w64-mingw32 | x86_64-w64-mingw32 | 34 | | gcc | x86_64-w64-mingw32 | i686-w64-mingw32 | 35 | | gcc | x86_64-w64-mingw32 | x86_64-linux-gnu | 36 | | gcc | x86_64-w64-mingw32 | i686-linux-gnu | 37 | | gcc | x86_64-w64-mingw32 | arm-none-eabi | 38 | | gcc | x86_64-w64-mingw32 | arm-nonewlib-none-eabi | 39 | | gcc | x86_64-w64-mingw32 | x86_64-elf | 40 | | gcc | x86_64-w64-mingw32 | loongarch64-linux-gnu | 41 | | gcc | x86_64-w64-mingw32 | loongarch64-loongnix-linux-gnu | 42 | | gcc | x86_64-w64-mingw32 | riscv64-linux-gnu | 43 | | gcc | x86_64-w64-mingw32 | riscv64-none-elf | 44 | | gcc | x86_64-w64-mingw32 | aarch64-linux-gnu | 45 | | gcc | x86_64-w64-mingw32 | arm-linux-gnueabi | 46 | | gcc | x86_64-w64-mingw32 | arm-linux-gnueabihf | 47 | | gcc | x86_64-w64-mingw32 | mips64el-linux-gnuabi64 | 48 | | llvm | x86_64-linux-gnu | X86, ARM, AArch64, LoongArch, RISCV | 49 | | llvm | x86_64-w64-mingw32 | X86, ARM, AArch64, LoongArch, RISCV | 50 | 51 | ## 构建流程说明与构建脚本 52 | 53 | ### 构建流程说明 54 | 55 | 本项目提供GCC和LLVM工具链的构建流程说明,可以参阅下表中的文件了解工具链构建流程。 56 | 57 | | 文件 | 说明 | 58 | | :------------ | :--------------------- | 59 | | build_gcc.md | GCC工具链构建流程说明 | 60 | | build_llvm.md | LLVM工具链构建流程说明 | 61 | 62 | ### 构建脚本 63 | 64 | 本项目在`script`目录下也提供了一组Python脚本用于自动化构建工具链,下表是相关文件说明: 65 | 66 | | 文件 | 说明 | 67 | | :------------------------------------ | :------------------------------------------------------------------------------ | 68 | | prefix-lib.suffix $^{[1]}$ | 链接器脚本,用于代替autotools生成的带有绝对路径的链接器脚本以得到可移植的工具链 | 69 | | .gdbinit | gdb配置文件,用于自动配置pretty-print | 70 | | auto_gcc.py | gcc构建脚本的接口,可以获取各种信息和实现全自动构建 $^{[2]}$ | 71 | | common.py | GCC和LLVM构建脚本共用的一些构建环境和实用函数 | 72 | | gcc_environment.py | GCC构建环境,所有GCC构建脚本均通过该环境完成构建流程 | 73 | | llvm_environment.py | LLVM构建环境,所有LLVM构建脚本均通过该环境完成构建流程 | 74 | | python_config.py | 在交叉编译gdb时用于获取Python库所在路径 | 75 | | python_config.sh | 将Python脚本包装为sh脚本,因为binutils-gdb的configure脚本仅支持sh脚本 | 76 | | sysroot.py | 制作构建LLVM工具链所需的初始sysroot,仅包含libc和GNU相关库文件 | 77 | | plat_clang.py $^{[3]}$ | LLVM工具链构建脚本 $^{[4]}$ | 78 | | plat_native_gcc.py $^{[3]}$ | GCC本地工具链/加拿大工具链构建脚本 $^{[4]}$ | 79 | | plat_host_plat_target_gcc.py $^{[3]}$ | GCC交叉工具链/加拿大交叉工具链构建脚本 $^{[4]}$ | 80 | | download.py $^{[5]}$ | 自动从github和其他源下载构建GCC和LLVM所需的包 | 81 | 82 | 注释: 83 | 84 | [1] 字段说明如下表所示: 85 | 86 | | 字段 | 说明 | 87 | | :----- | :---------------------------------------------------------------------------------------------------- | 88 | | prefix | 一般为arch,若存在多个target则会增加vendor字段(如`loongnix`)或abi字段(如`hf`),取决于target间差异 | 89 | | lib | glibc中库名称,如`libc`和`libm`,取决于哪些库使用了链接器脚本而非软链接 | 90 | | suffix | glibc中库文件名后缀,如`.so`和`.a`,取决于哪些类型的库使用了链接器脚本而非软链接 | 91 | 92 | [2] 该脚本支持如下选项: 93 | 94 | | 选项 | 说明 | 95 | | :---------- | :----------------------------------------------------------------------- | 96 | | --build | 全自动构建所有GCC工具链 | 97 | | --dump_info | 打印所有构建脚本、目标和构建顺序信息 | 98 | | --dump_path | 以shell脚本形式打印所有存在的工具链的bin目录列表,可用于设置PATH环境变量 | 99 | | --help | 打印帮助信息 | 100 | | 作为库包含 | 可以通过`scripts`对象获取构建脚本、目标和已安装工具链相关信息 | 101 | 102 | [3] plat字段表示一个triplet,用于描述一个目标平台,如`x86_6-linux-gnu` 103 | 104 | [4] 构建脚本可作为库使用,也可直接执行。引用这些脚本可以获取工具链的构建环境和一些实用函数,直接执行可以完成自动化构建 105 | 106 | [5] 该脚本支持如下选项: 107 | 108 | | 选项 | 说明 | 109 | | :-------------- | :--------------------------------------------------------------------- | 110 | | --glibc_version | 设置目标平台的Glibc版本,默认为系统Glibc版本 | 111 | | --home | 设置源码树的根目录,默认为`$HOME` | 112 | | --clone_type | Git克隆类型,默认为部分克隆 | 113 | | --depth | 使用Git进行浅克隆时的克隆深度,默认为1 | 114 | | --ssh | 是否在从github克隆时使用SSH,默认为否 | 115 | | --extra_libs | 要下载或更新的额外包 | 116 | | --retry | 网络操作失败时最大重试次数,默认为5次 | 117 | | --remote | 设置首选git源,在源可用时使用源以加速克隆,默认为github | 118 | | --update | 更新已安装的包,要求所有包均已安装 | 119 | | --download | 下载缺失的包,不会更新已安装的包 | 120 | | --auto | 先下载缺失的包,然后更新已安装的包。由于二次检查,可能会需要更多时间。 | 121 | | --system | 打印需要的系统包 | 122 | | --remove | 删除已经安装的包,不指定包名则删除所有安装的包 | 123 | | --import | 从文件中导入配置 | 124 | | --export | 将配置导出到文件 | 125 | | --help | 打印帮助信息 | 126 | 127 | remote选项支持的git源如下: 128 | 129 | | 源 | 说明 | 130 | | :----- | :----------------------------------------------------------------------------- | 131 | | github | 默认远程源,部分为仓库镜像,支持ssh克隆 | 132 | | native | 各个git库的原生远程源,可以克隆最新的提交,但可能访问较慢,部分仓库使用git协议 | 133 | | nju | 南京大学开源镜像站,包含gcc、binutils、linux、glibc、llvm仓库镜像 | 134 | | tuna | 清华大学开源软件镜像站,镜像同上 | 135 | | bfsu | 北京外国语大学开源软件镜像站,镜像同上 | 136 | | nyist | 南阳理工学院开源软件镜像站,镜像同上 | 137 | | cernet | 校园网联合镜像站,mirrorz-302 智能选择,镜像同上 | 138 | 139 | ### 工具链说明 140 | 141 | 在`readme`目录下可以找到各个工具链的说明文件,构建脚本会将说明文件和工具链一同打包。在使用工具链前请参阅工具链目录下的`README.md`文件, 142 | 或参阅[构建流程说明](#构建流程说明)。 143 | 144 | ## Xmake支持 145 | 146 | 本项目为[受支持的工具链](#工具链)提供了xmake支持,下面是一个示例: 147 | 148 | ```lua 149 | -- 导入所有文件 150 | includes("xmake/*.lua") 151 | -- 设置允许的xmake模式 152 | set_allowedmodes(support_rules_table) 153 | -- 根据mode选项添加规则 154 | add_rules(get_config("mode")) 155 | target("test") 156 | add_files("*.cpp") 157 | target_end() 158 | ``` 159 | 160 | 通过`toolchain`选项可以轻松地选取要使用的工具链,并完成`--sysroot`等选项的配置,下面是一个示例: 161 | 162 | ```shell 163 | # 使用clang进行交叉编译 164 | xmake f --toolchain=aarch64-linux-gnu-clang -a arm64-v8a -p linux 165 | # 使用gcc进行交叉编译 166 | xmake f --toolchain=aarch64-linux-gnu-gcc -a arm64-v8a -p linux 167 | ``` 168 | 169 | xmake支持也提供了`target-clang`和`target-gcc`工具链。使用这两个工具链时脚本会尝试根据`plat`和`arch`推导出目标平台,若无法推导则配置失败,此时应该指定一个具体的工具链。 170 | 在常用平台上编译时可以直接使用这两个工具链,而无需在xmake选项和工具链间重复指定目标平台。下面是一个示例: 171 | 172 | ```shell 173 | # 根据arch和plat自动推导工具链,相当于--toolchain=aarch64-linux-gnu-clang 174 | xmake f --toolchain=target-clang -a arm64-v8a -p linux 175 | ``` 176 | 177 | 如果使用`cross`平台,则需要通过`--target_os`选项指定目标平台。下面是一个示例: 178 | 179 | ```shell 180 | # 相当于--toolchain=loongarch64-linux-gnu-clang 181 | xmake f --toolchain=target-clang -a loong64 -p cross --target_os=linux 182 | ``` 183 | 184 | 使用`cross`平台并且不指定`--target_os`选项则会推导出独立工具链,下面是一个示例: 185 | 186 | ```shell 187 | # 推导出独立工具链,相当于--toolchain=arm-none-eabi 188 | xmake f --toolchain=target-clang -a arm -p cross 189 | ``` 190 | 191 | ### xmake文件说明 192 | 193 | xmake支持文件位于`xmake`文件夹下,下面是各个文件的说明: 194 | 195 | | 文件 | 说明 | 196 | | :------------------ | :-------------------------------------------------------------- | 197 | | option.lua | 提供各种xmake配置选项,包含`utility/utility.lua`文件 | 198 | | rule.lua | 提供4种常用xmake规则,包含`option.lua`文件 | 199 | | toolchain.lua | 提供各种xmake工具链,包含`option.lua`和`utility/target.lua`文件 | 200 | | utility/target.lua | 受支持的目标平台表,内部文件 | 201 | | utility/utility.lua | 提供各种配置工具,内部文件 | 202 | 203 | 其中`option.lua`可以单独使用,此时需要使用`add_options`来关联选项和目标。如果使用`toolchain.lua`中定义的工具链,这些选项会自动添加到相应工具链中。 204 | `utility/target.lua`可以通过`includes`函数引入到描述域中,此时函数和变量均可使用;也可以通过`import`函数引入脚本域,此时仅函数接口可用。 205 | 206 | ### xmake选项说明 207 | 208 | 下面是`option.lua`提供的xmake配置选项说明: 209 | 210 | - march 设置工具链的`-march`选项,默认为`default` 211 | 212 | | 选项 | 说明 | 213 | | :----- | :-------------------------------------------------------- | 214 | | no | 不添加`-march`选项 | 215 | | detect | 如果可以则添加`-march=native`选项,否则不添加`-march`选项 | 216 | | arch | 添加`-march=arch`选项,`arch`不能为`no`和`detect` | 217 | 218 | - sysroot 设置工具链的`--sysroot`选项,默认为`detect` 219 | 220 | | 选项 | 说明 | 221 | | :----- | :--------------------------------------------------------------------------- | 222 | | no | 不添加`--sysroot`选项 | 223 | | detect | 工具链为clang则自动探测sysroot,为gcc则不添加选项 $^*$ | 224 | | path | 添加`--sysroot=path`选项,`path`可以是绝对路径或不为`no`和`detect`的相对路径 | 225 | 226 | *:关于自动探测支持请参阅[sysroot说明](readme/sysroot.md#说明)。 227 | 228 | - rtlib 设置clang的`-rtlib`选项,默认为`default` 229 | 230 | | 选项 | 说明 | 231 | | :---------- | :-------------------------------------------------- | 232 | | default | 不添加`-rtlib`选项,即使用构建clang时指定的默认选项 | 233 | | libgcc | 添加`-rtlib=libgcc`,指定使用`libgcc` | 234 | | compiler-rt | 添加`-rtlib=compiler-rt`,指定使用`compiler-rt` | 235 | | platform | 添加`-rtlib=platform`,即使用目标平台的默认选项 | 236 | 237 | - unwindlib 设置clang的`-unwindlib`选项,默认为`default` 238 | 239 | | 选项 | 说明 | 240 | | :-------------- | :-------------------------------------------------------- | 241 | | default | 不添加`-unwind`选项,即使用构建clang时指定的默认选项 | 242 | | libgcc | 添加`-unwind=libgcc`,指定使用`libgcc` $^*$ | 243 | | libunwind | 添加`-unwind=libunwind`,指定使用`libunwind` $^*$ | 244 | | platform | 添加`-unwind=platform`,即使用目标平台的默认选项 $^*$ | 245 | | force_libgcc | 强制添加`-unwind=libgcc`,指定使用`libgcc` $^*$ | 246 | | force_libunwind | 强制添加`-unwind=libunwind`,指定使用`libunwind` $^*$ | 247 | | force_platform | 强制添加`-unwind=platform`,即使用目标平台的默认选项 $^*$ | 248 | 249 | *:为避免`argument unused`警告,默认情况下仅在`rtlib`选项为`compiler-rt`时添加该选项,可以使用force版本强制添加 250 | 251 | - debug_strip 设置在启用调试符号的规则中是否要剥离符号表,默认为`no` 252 | 253 | | 选项 | 说明 | 254 | | :---- | :----------------------------------------------------- | 255 | | no | 如果可能,不剥离任何符号 | 256 | | debug | 剥离调试符号到独立符号文件 | 257 | | all | 剥离调试符号到独立符号文件,然后去除目标文件中所有符号 | 258 | 259 | - enable_lto 设置在具有发布属性的规则(如`release`,`minsizerel`和`releasedbg`)中是否启用链接时优化(LTO),默认为`true` 260 | 261 | | 选项 | 说明 | 262 | | :---- | :------ | 263 | | true | 启用LTO | 264 | | false | 禁用LTO | 265 | 266 | 该选项在编译器与链接器不匹配时特别有用,因为此时需要禁用LTO才能正常完成构建流程。例如使用`clang`进行编译而使用`ld.bfd`进行链接,此时若启用LTO则会提示无法识别文件格式。 267 | 通常,`clang`使用`lld`进行链接,但如果`lld`不支持目标平台,则可能发生上述情况。使用该选项可以方便地根据需求设置或禁用LTO。 268 | 269 | ### xmake工具链说明 270 | 271 | 可以使用`xmake show -l toolchains`命令查看所有受支持的工具链名称,具体工具链的信息可以参阅[受支持的工具链](#工具链)。 272 | 工具链的命名规则和说明如下: 273 | 274 | | 名称 | 说明 | 275 | | :---------- | :-------------------------------------------------------------------------------------- | 276 | | native-tool | 本地工具链,对于clang不会添加`--target`选项,对于gcc会查找`gcc`工具 | 277 | | target-tool | 根据xmake的`arch`和`plat`选项自动推导出目标平台工具链 | 278 | | plat-tool | 目标平台为plat的工具链,对于clang会添加`--target=plat`选项,对于gcc会查找`plat-gcc`工具 | 279 | 280 | 注解:`tool`表示工具链类型,为`clang`或`gcc` 281 | 282 | xmake工具链还会根据目标平台的特性添加一些选项,如为`loongarch64-loongnix-linux-gnu`平台添加`-Wl,-dynamic-linker=/lib64/ld.so.1`选项以修改动态库加载器路径。 283 | -------------------------------------------------------------------------------- /build_llvm.md: -------------------------------------------------------------------------------- 1 | # 构建LLVM工具链 2 | 3 | ## 基本信息 4 | 5 | | 项目 | 版本 | 6 | | :--- | :----------- | 7 | | OS | Ubuntu 24.10 | 8 | | LLVM | 20.0.0 | 9 | | GCC | 16.0.0 | 10 | 11 | ## 准备工作 12 | 13 | ### 1.安装系统包 14 | 15 | ```shell 16 | # 使用clang自举 17 | sudo apt install git python3 cmake ninja-build clang lld libxml2-dev zlib1g-dev 18 | ``` 19 | 20 | ### 2.准备sysroot 21 | 22 | 作为一个交叉编译器,clang需要设置sysroot来查找所需的库和头文件,值得注意的是,clang不适用multilib,故而需要为32位目标和64位目标分别制作sysroot。 23 | 一个平台的sysroot需要包含如下内容: 24 | 25 | | 内容 | 说明 | 26 | | ------------ | ---------------------- | 27 | | Linux Header | 内核头文件 | 28 | | libc | C 标准库 | 29 | | libgcc | 提供异常处理等底层功能 | 30 | | libstdc++ | C++ 标准库 | 31 | | ldscripts | 链接器脚本 | 32 | 33 | 故而制作一个sysroot需要用到如下项目: 34 | 35 | | 项目 | 说明 | 36 | | -------------------------------------- | --------------------------------------- | 37 | | Linux | 提供内核头文件 | 38 | | Glibc/Mingw-w64 | 提供C 标准库 | 39 | | gcc | 提供libgcc和C++ 标准库 | 40 | | binutils | 提供链接器脚本 | 41 | | compiler-rt/libunwind/libcxx/libcxxabi | 在完成LLVM工具链编译后用于替代GNU相关库 | 42 | 43 | 对于一个安装在`prefix`并且交叉到`target`平台的交叉工具链,在`prefix/target`目录下可以找到`include`、`lib`、`lib64`等文件夹,而在`prefix/lib/gcc`可以找到libgcc相关文件夹, 44 | 其中已经包含了sysroot所需的全部文件。对于一个本地工具链,由于系统相关的库和头文件安装在系统目录下,故本地工具链本身仅带有gcc和binutils相关文件, 45 | 而linux和libc相关文件需要自行安装,安装流程可以参考[build_gcc.md](build_gcc.md)中的说明。 46 | 47 | 故而制作一个sysroot的流程如下: 48 | 49 | | 平台 | 流程 | 50 | | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | 51 | | 非host平台 | 直接从`prefix/target`目录下复制`include`、`lib`、`lib64`(如果存在)文件夹 | 52 | | host平台 | 首先安装Linux头文件和libc文件,然后从`prefix`目录下复制`include`、`lib32/64`(取决于host的性质,复制成lib文件夹),再从`prefix/host/lib`下复制`ldscripts` | 53 | | 通用 | 从`prefix/lib`下复制`gcc`文件夹 | 54 | 55 | 对于启用multilib编译的gcc,在制作sysroot时需要视作两个目标处理。例如对于启用了multilib的`x86_64-linux-gnu-gcc`,制作sysroot时需要拆分成`x86_64-linux-gnu`和`i686-linux-gnu`两个目标。 56 | 故而在编译gcc时也可以选择不启用multilib来简化sysroot的制作流程。 57 | 58 | 一个sysroot的目录结构实例如下: 59 | 60 | ```txt 61 | sysroot 62 | ├── aarch64-linux-gnu 63 | ├── i686-linux-gnu 64 | ├── i686-w64-mingw32 65 | ├── lib 66 | │ └── gcc 67 | │ ├── aarch64-linux-gnu 68 | │ ├── i686-linux-gnu 69 | │ ├── i686-w64-mingw32 70 | │ ├── loongarch64-linux-gnu 71 | │ ├── riscv64-linux-gnu 72 | │ ├── x86_64-linux-gnu 73 | │ └── x86_64-w64-mingw32 74 | ├── loongarch64-linux-gnu 75 | ├── riscv64-linux-gnu 76 | ├── x86_64-linux-gnu 77 | └── x86_64-w64-mingw32 78 | ``` 79 | 80 | ## 构建流程 81 | 82 | ### 编译x86_64-linux-gnu-llvm工具链 83 | 84 | 通过clang自举出不依赖gnu相关库的完整llvm工具链需要分为4个阶段进行: 85 | 86 | 1. 第一次编译llvm及runtimes,依赖gnu相关库 87 | 2. 使用刚才编译的llvm和runtimes重新编译llvm,得到不依赖gnu相关库的llvm 88 | 3. 使用刚才编译的llvm和runtimes重新编译runtimes,得到不依赖gnu相关库的runtimes 89 | 4. 打包工具链 90 | 91 | 上述流程共计需要编译llvm部分2次,runtimes部分2次,为缩短构建流程可跳过一些自举步骤。 92 | 如果无需自举llvm,则可以跳过[再次编译llvm](#2再次编译llvm),并且可以按需在[首次编译llvm](#1首次编译llvm以及runtimes)流程中启用`clang-tools-extra`组件和LTO优化; 93 | 如果无需自举runtimes,则可以跳过[再次编译runtimes](#3再次编译runtimes)。 94 | 95 | #### (1)首次编译llvm以及runtimes 96 | 97 | 首先编译llvm: 98 | 99 | ```shell 100 | export llvm_prefix=~/x86_64-linux-gnu-clang20 101 | export runtimes_prefix=$llvm_prefix/install 102 | export compiler_rt_prefix=$llvm_prefix/lib/clang/20/lib 103 | # 启用动态链接以减小项目体积 104 | export dylib_option_list="-DLLVM_LINK_LLVM_DYLIB=ON -DLLVM_BUILD_LLVM_DYLIB=ON -DCLANG_LINK_CLANG_DYLIB=ON" 105 | # 设置构建模式为Release 106 | # 关闭llvm文档、示例、基准测试和单元测试的构建 107 | # 构建目标:X86;AArch64;RISCV;ARM;LoongArch 108 | # 在编译llvm时编译子项目:clang;lld 109 | # 在编译llvm时编译运行时:libcxx;libcxxabi;libunwind;compiler-rt 110 | # 禁用警告以减少回显噪音 111 | # 关闭clang单元测试的构建 112 | # 禁止基准测试安装文档 113 | # 设置clang默认链接器为lld 114 | # 使用lld作为链接器(支持多核链接,可加速链接) 115 | # 使用rpath连接选项,在Linux系统上可以避免动态库环境混乱 116 | # 关闭libcxx基准测试的构建 117 | # 使用compiler-rt和libcxxabi构建libcxx 118 | # 使用compiler-rt和libunwind构建libcxxabi 119 | # compiler-rt只需构建默认目标即可,禁止自动构建multilib 120 | # 使用libcxx构建compiler-rt中的asan等项目 121 | # 值得注意的是,libunwind的构建早于compiler-rt,故而此处不能选择使用compiler-rt编译libunwind 122 | export llvm_option_list1='-DCMAKE_BUILD_TYPE=Release -DLLVM_BUILD_DOCS=OFF -DLLVM_BUILD_EXAMPLES=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_TARGETS_TO_BUILD="X86;AArch64;RISCV;ARM;LoongArch" -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind;compiler-rt" -DLLVM_ENABLE_WARNINGS=OFF -DCLANG_INCLUDE_TESTS=OFF -DBENCHMARK_INSTALL_DOCS=OFF -DCLANG_DEFAULT_LINKER=lld -DLLVM_ENABLE_LLD=ON -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DLIBCXX_INCLUDE_BENCHMARKS=OFF -DLIBCXX_USE_COMPILER_RT=ON -DLIBCXX_CXX_ABI=libcxxabi -DLIBCXXABI_USE_LLVM_UNWINDER=ON -DLIBCXXABI_USE_COMPILER_RT=ON -DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON -DCOMPILER_RT_USE_LIBCXX=ON' 123 | # 设置编译器为clang 124 | # 编译器目标平台:x86_64-linux-gnu 125 | # 禁用unused-command-line-argument警告并设置gcc查找路径以使用最新的gcc 126 | # 设置COMPILER_WORKS选项以跳过探测(有时探测不能正常工作,尤其是交叉编译时) 127 | # 设置llvm运行时的目标平台:x86_64-linux-gnu 128 | # 设置llvm默认的目标平台:x86_64-unknown-linux-gnu 129 | # 设置llvm的宿主平台:x86_64-unknown-linux-gnu 130 | export flags="\"-Wno-unused-command-line-argument --gcc-toolchain=$HOME/sysroot\"" 131 | export target=x86_64-linux-gnu 132 | export host=x86_64-unknown-linux-gnu 133 | export compiler_option="-DCMAKE_C_COMPILER=\"clang\" -DCMAKE_C_COMPILER_TARGET=$target -DCMAKE_C_FLAGS=$flags -DCMAKE_C_COMPILER_WORKS=ON -DCMAKE_CXX_COMPILER=\"clang++\" -DCMAKE_CXX_COMPILER_TARGET=$target -DCMAKE_CXX_FLAGS=$flags -DCMAKE_CXX_COMPILER_WORKS=ON -DCMAKE_ASM_COMPILER=\"clang\" -DCMAKE_ASM_COMPILER_TARGET=$target -DCMAKE_ASM_FLAGS=$flags -DCMAKE_ASM_COMPILER_WORKS=ON -DLLVM_RUNTIMES_TARGET=$target -DLLVM_DEFAULT_TARGET_TRIPLE=$host -DLLVM_HOST_TRIPLE=$host" 134 | # 进入llvm项目目录 135 | cd ~/llvm 136 | # 配置llvm 137 | cmake -G Ninja --install-prefix $llvm_prefix -B build-x86_64-linux-gnu-llvm -S llvm $dylib_option_list $llvm_option_list1 $compiler_option 138 | # 编译llvm 139 | ninja -C build -j 20 140 | # 安装llvm 141 | ninja -C build install/strip -j 20 142 | ``` 143 | 144 | 接下来编译所有需要的runtimes,值得注意的是,在Windows上尚不支持使用动态链接的libcxxabi,故而需要改用静态链接的libcxxabi作为libcxx的abi实现。这需要增加 145 | `-DLIBCXXABI_ENABLE_SHARED=OFF`和`-DLIBCXX_ENABLE_STATIC_ABI_LIBRARY=ON`选项。参见[GitHub Issue](https://github.com/llvm/llvm-project/issues/62798)。 146 | 而编译runtimes需要较新的Linux header和Glibc,故而不能为`loongarch64-loongnix-linux-gnu`等老旧目标编译runtimes。 147 | 同时在为arm平台编译runtimes时需要armv6+的配置才能完成编译,故而需要为`$flags`增加`-march=armv6`选项。 148 | 149 | ```shell 150 | # 静态构建libcxxabi 151 | export llvm_option_list_w32_1="$llvm_option_list1 -DLIBCXXABI_ENABLE_SHARED=OFF -DLIBCXX_ENABLE_STATIC_ABI_LIBRARY=ON" 152 | # 进入llvm项目目录 153 | cd ~/llvm 154 | # 配置runtimes 155 | cmake -G Ninja --install-prefix $runtimes_prefix -B build-x86_64-linux-gnu-runtimes -S runtimes $dylib_option_list $llvm_option_list1 $compiler_option 156 | # 编译runtimes 157 | ninja -C build -j 20 158 | # 安装runtimes 159 | ninja -C build install/strip -j 20 160 | 161 | # 在编译非host平台的runtimes时需要进行交叉编译,故而需要修改compiler_option 162 | # 以编译x86_64-w64-mingw32上的runtimes为例 163 | export target=x86_64-w64-mingw32 164 | # 设置编译器为clang 165 | # 设置编译器的目标平台为x86_64-w64-mingw32 166 | # 禁用unused-command-line-argument警告并设置gcc查找路径以使用最新的gcc 167 | # 设置COMPILER_WORKS选项以跳过探测(有时探测不能正常工作,尤其是交叉编译时) 168 | # 设置目标平台:Windows,目标架构:x86_64 169 | # 设置sysroot为先前制作的sysroot以进行交叉编译 170 | # 设置llvm运行时的目标平台:x86_64-w64-mingw32 171 | # 设置llvm默认的目标平台:x86_64-unknown-w64-mingw32 172 | # 设置llvm的宿主平台:x86_64-unknown-linux-gnu 173 | export compiler_option="-DCMAKE_C_COMPILER=\"clang\" -DCMAKE_C_COMPILER_TARGET=$target -DCMAKE_C_FLAGS=$flags -DCMAKE_C_COMPILER_WORKS=ON -DCMAKE_CXX_COMPILER=\"clang++\" -DCMAKE_CXX_COMPILER_TARGET=$target -DCMAKE_CXX_FLAGS=$flags -DCMAKE_CXX_COMPILER_WORKS=ON -DCMAKE_ASM_COMPILER=\"clang\" -DCMAKE_ASM_COMPILER_TARGET=$target -DCMAKE_ASM_FLAGS=$flags -DCMAKE_ASM_COMPILER_WORKS=ON -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_SYSTEM_PROCESSOR=x86_64 -DCMAKE_SYSROOT=\"$HOME/sysroot\" -DLLVM_RUNTIMES_TARGET=$target -DLLVM_DEFAULT_TARGET_TRIPLE=x86_64-unknown-w64-mingw32 -DLLVM_HOST_TRIPLE=$host" 174 | # 进入llvm项目目录 175 | cd ~/llvm 176 | # 配置runtimes(编译Windows平台的需要设置llvm_option_list_w64(32)_1,Linux平台使用llvm_option_list1) 177 | cmake -G Ninja --install-prefix $runtimes_prefix -B build-x86_64-linux-gnu-runtimes -S runtimes $dylib_option_list $llvm_option_list_w32 $compiler_option 178 | # 编译runtimes 179 | ninja -C build -j 20 180 | # 安装runtimes 181 | ninja -C build install/strip -j 20 182 | ``` 183 | 184 | 在编译完runtimes后还需要将llvm相关库复制到sysroot下,以便在后续使用过程中通过命令行选项切换使用的库,以及编译出不依赖gnu相关库的llvm。 185 | 值得注意的是,compiler-rt相关库需要复制到`prefix/lib/clang/version`而不是sysroot下。同时,对于Windows平台而言,dll位于`bin`目录下,而对于 186 | Linux平台而言,so位于`lib`目录下。最后,在安装带runtimes的llvm时会安装一份libcxx的头文件到`prefix/include/c++/v1`下,此部分头文件是跨平台的, 187 | 但在编译Windows平台的程序时,clang不会在`prefix/include`路径下查找,故还需要复制一份到sysroot下,但该过程只需要进行一次即可。 188 | 而libcxx中与平台相关的部分储存在`__config_site`文件中,故该文件需要复制到sysroot下。 189 | 下面以复制`x86_64-linux-gnu`相关的库为例: 190 | 191 | ```python 192 | import os 193 | import shutil 194 | 195 | def overwrite_copy(src: str, dst: str): 196 | """复制文件或目录,会覆盖已存在项 197 | 198 | Args: 199 | src (str): 源路径 200 | dst (str): 目标路径 201 | """ 202 | if os.path.isdir(src): 203 | if os.path.exists(dst): 204 | shutil.rmtree(dst) 205 | shutil.copytree(src, dst) 206 | else: 207 | if os.path.exists(dst): 208 | os.remove(dst) 209 | shutil.copyfile(src, dst, follow_symlinks=False) 210 | 211 | home = os.path.expanduser("~") 212 | prefix = f"{home}/x86_64-linux-gnu-clang20/install" 213 | sysroot = f"{home}/sysroot" 214 | compiler_rt = f"{home}/x86_64-linux-gnu-clang20/lib/clang/20/lib" 215 | for dir in os.listdir(prefix): 216 | src_dir = os.path.join(prefix, dir) 217 | match dir: 218 | case "lib": 219 | dst_dir = os.path.join(sysroot, target, "lib") 220 | if not os.path.exists(compiler_rt): 221 | os.mkdir(compiler_rt) 222 | for item in os.listdir(src_dir): 223 | # 复制compiler-rt 224 | if item == "linux": 225 | item = item.lower() 226 | rt_dir = os.path.join(compiler_rt, item) 227 | if not os.path.exists(rt_dir): 228 | os.mkdir(rt_dir) 229 | for file in os.listdir(os.path.join(src_dir, item)): 230 | overwrite_copy(os.path.join(src_dir, item, file), os.path.join(rt_dir, file)) 231 | continue 232 | # 复制其他库 233 | overwrite_copy(os.path.join(src_dir, item), os.path.join(dst_dir, item)) 234 | case "include": 235 | # 复制__config_site 236 | dst_dir = os.path.join(sysroot, target, "include") 237 | overwrite_copy(os.path.join(src_dir, "c++", "v1", "__config_site"), os.path.join(dst_dir, "__config_site")) 238 | # 只要复制一次即可 239 | src_dir = os.path.join(prefix, "include", "c++") 240 | dst_dir = os.path.join(sysroot, "include", "c++") 241 | overwrite_copy(src_dir, dst_dir) 242 | ``` 243 | 244 | #### (2)再次编译llvm 245 | 246 | 此次只编译llvm及子项目,不编译runtimes,但可以启用`clang-tools-extra`等额外的子项目。 247 | 248 | 为了实现全面的llvm化,可以将clang默认的库设置成llvm相关库而不是gnu相关库,以实现运行库的替换。它们的关系如下: 249 | 250 | | gnu | llvm | 251 | | ------------ | ------------------------------- | 252 | | libsanitizer | compiler-rt | 253 | | libgcc | compiler-rt+libunwind+libcxxabi | 254 | | libstdc++ | libcxx | 255 | 256 | 使用选项`-stdlib=libc++`,`-unwindlib=libunwind`和`-rtlib=compiler-rt`即可将clang使用的运行库切换到llvm相关库上。 257 | 258 | 下面是再次编译llvm的相关选项(一些重复选项的说明请参阅[首次编译流程](#1首次编译llvm以及runtimes): 259 | 260 | ```shell 261 | # 重复部分参考llvm_option_list1 262 | # 增加子项目:clang-tools-extra 263 | # 开启LTO以提升性能 264 | # 设置clang默认运行库为libcxx、libunwind和compiler-rt 265 | export llvm_option_list2='-DCMAKE_BUILD_TYPE=Release -DLLVM_BUILD_DOCS=OFF -DLLVM_BUILD_EXAMPLES=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_TARGETS_TO_BUILD="X86;AArch64;RISCV;ARM;LoongArch" -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;lld" -DLLVM_ENABLE_WARNINGS=OFF -DCLANG_INCLUDE_TESTS=OFF -DBENCHMARK_INSTALL_DOCS=OFF -DCLANG_DEFAULT_LINKER=lld -DLLVM_ENABLE_LLD=ON -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DLIBCXX_INCLUDE_BENCHMARKS=OFF -DLIBCXX_USE_COMPILER_RT=ON -DLIBCXX_CXX_ABI=libcxxabi -DLIBCXXABI_USE_LLVM_UNWINDER=ON -DLIBCXXABI_USE_COMPILER_RT=ON -DLIBUNWIND_USE_COMPILER_RT=ON -DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON -DCOMPILER_RT_USE_LIBCXX=ON -DLLVM_ENABLE_LTO=Thin -DCLANG_DEFAULT_CXX_STDLIB=libc++ -DCLANG_DEFAULT_RTLIB=compiler-rt -DCLANG_DEFAULT_UNWINDLIB=libunwind' 266 | # 切换运行库为llvm相关库 267 | export lib_flags="-stdlib=libc++ -unwindlib=libunwind -rtlib=compiler-rt" 268 | # 设置编译器为clang并且使用llvm相关库 269 | export flags="\"-Wno-unused-command-line-argument --gcc-toolchain=$HOME/sysroot $lib_flags\"" 270 | export compiler_option="-DCMAKE_C_COMPILER=\"clang\" -DCMAKE_C_COMPILER_TARGET=$target -DCMAKE_C_FLAGS=$flags -DCMAKE_C_COMPILER_WORKS=ON -DCMAKE_CXX_COMPILER=\"clang++\" -DCMAKE_CXX_COMPILER_TARGET=$target -DCMAKE_CXX_FLAGS=$flags -DCMAKE_CXX_COMPILER_WORKS=ON -DCMAKE_ASM_COMPILER=\"clang\" -DCMAKE_ASM_COMPILER_TARGET=$target -DCMAKE_ASM_FLAGS=$flags -DCMAKE_ASM_COMPILER_WORKS=ON -DLLVM_RUNTIMES_TARGET=$target -DLLVM_DEFAULT_TARGET_TRIPLE=$host -DLLVM_HOST_TRIPLE=$host -DCMAKE_LINK_FLAGS=\"$lib_flags\"" 271 | # 进入llvm项目目录 272 | cd ~/llvm 273 | # 配置llvm 274 | cmake -G Ninja --install-prefix $llvm_prefix -B build-x86_64-linux-gnu-llvm -S llvm $dylib_option_list $llvm_option_list2 $compiler_option 275 | # 编译llvm 276 | ninja -C build -j 20 277 | # 安装llvm 278 | ninja -C build install/strip -j 20 279 | ``` 280 | 281 | #### (3)再次编译runtimes 282 | 283 | 此次构建的流程可以参考[首次编译runtimes](#1首次编译llvm以及runtimes),下面阐述一些注意事项。 284 | 285 | - 如果是自举编译,即完成了[再次编译llvm](#2再次编译llvm)流程,那么此时的clang默认就是使用llvm相关库,反之clang依然使用gnu相关库。 286 | 那么需要向`$flags`中额外增加`-stdlib=libc++ -unwindlib=libunwind -rtlib=compiler-rt`选项以切换构建时使用的库。 287 | - 尽管已经完成了llvm相关库的构建,在编译Windows目标时依然需要依赖libgcc来提供`___chkstk_ms`等函数,故而需要向`$flags`中额外增加`-lgcc`选项,但这不会引入对`libgcc`动态库的依赖。 288 | - 尽管使用了llvm相关库代替gnu相关库,但个别链接库如`libclang_rt.asan-x86_64.so`依然会依赖`libgcc_s.so.1`,没有完全脱离gnu相关库。 289 | 290 | #### (4)打包工具链 291 | 292 | 到此为止,一个完整的llvm工具链就完成了构建和组装。下面对工具链的两部分进行打包。 293 | 294 | ```shell 295 | cd ~ 296 | tar -cf x86_64-linux-gnu-clang20.tar x86_64-linux-gnu-clang20 297 | tar -cf sysroot.tar sysroot 298 | xz -ev9 -T 0 --memlimit=14GiB x86_64-linux-gnu-clang20.tar 299 | xz -ev9 -T 0 --memlimit=14GiB sysroot.tar 300 | ``` 301 | 302 | ### 编译x86_64-w64-mingw32-llvm工具链 303 | 304 | 这是一个加拿大工具链,运行在Windows平台上,故而不需要对它进行自举编译,同时runtimes已经完成编译,此时只需编译llvm即可。 305 | 306 | ### (1)编译依赖库 307 | 308 | 此处需要编译的依赖库为`zlib`和`libxml2`。 309 | 310 | ```shell 311 | export native_prefix=~/x86_64-linux-gnu-clang20 312 | export zlib_prefix=~/zlib/install 313 | export libxml2_prefix=~/libxml2/install 314 | # 切换运行库为llvm相关库,编译libxml2需要ws2_32和bcrypt,为简化流程而在此处添加 315 | export lib_flags="-stdlib=libc++ -unwindlib=libunwind -rtlib=compiler-rt -lws2_32 -lbcrypt" 316 | export flags="-Wno-unused-command-line-argument --gcc-toolchain=/home/luo/sysroot $lib_flags" 317 | export host=x86_64-w64-mingw32 318 | export sysroot=~/sysroot 319 | export compiler_option="-DCMAKE_C_COMPILER=\"clang\" -DCMAKE_C_COMPILER_TARGET=$target -DCMAKE_C_FLAGS=$flags -DCMAKE_C_COMPILER_WORKS=ON -DCMAKE_CXX_COMPILER=\"clang++\" -DCMAKE_CXX_COMPILER_TARGET=$target -DCMAKE_CXX_FLAGS=$flags -DCMAKE_CXX_COMPILER_WORKS=ON -DCMAKE_ASM_COMPILER=\"clang\" -DCMAKE_ASM_COMPILER_TARGET=$target -DCMAKE_ASM_FLAGS=$flags -DCMAKE_ASM_COMPILER_WORKS=ON -DLLVM_RUNTIMES_TARGET=$target -DLLVM_DEFAULT_TARGET_TRIPLE=$host -DLLVM_HOST_TRIPLE=$host -DCMAKE_LINK_FLAGS=\"$lib_flags\" -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_SYSTEM_PROCESSOR=x86_64 -DCMAKE_SYSROOT=\"$sysroot\" -DCMAKE_CROSSCOMPILING=TRUE" 320 | # 构建zlib 321 | cd ~/zlib 322 | cmake -G Ninja --install-prefix $zlib_prefix -B build -S . $compiler_option 323 | ninja -C build -j 20 324 | ninja -C build install/strip -j 20 325 | # 构建libxml2 326 | cd ~/zlib 327 | cmake -G Ninja --install-prefix $libxml2_prefix -B build -S . $compiler_option 328 | ninja -C build -j 20 329 | ninja -C build install/strip -j 20 330 | # 定义交叉编译所需选项 331 | # 设置libxml2头文件查找路径 332 | # 设置libxml2链接库查找路径 333 | # 启用libxml2支持 334 | # 设置zlib头文件查找路径 335 | # 设置zlib链接库查找路径,静态链接 336 | # 启用zlib支持 337 | # 设置llvm本地工具查找路径,如llvm-tblgen 338 | export llvm_cross_option="-DLIBXML2_INCLUDE_DIR=\"$libxml2_prefix/include/libxml2\" -DLIBXML2_LIBRARY=\"$libxml2_prefix/lib/libxml2.dll.a\" -DCLANG_ENABLE_LIBXML2=ON -DZLIB_INCLUDE_DIR=\"$zlib_prefix/include\" -DZLIB_LIBRARY=\"$zlib_prefix/lib/libzlibstatic.a\" -DZLIB_LIBRARY_RELEASE=\"$zlib_prefix/lib/libzlibstatic.a\" -DLLVM_NATIVE_TOOL_DIR=\"$native_prefix/bin\"" 339 | ``` 340 | 341 | #### (2)编译llvm 342 | 343 | ```shell 344 | export prefix=~/$host-clang20 345 | # 如果符号过多则需要改用-DBUILD_SHARED_LIBS=ON 346 | export dylib_option_list="-DLLVM_LINK_LLVM_DYLIB=ON -DLLVM_BUILD_LLVM_DYLIB=ON -DCLANG_LINK_CLANG_DYLIB=ON" 347 | export llvm_option_list1='-DCMAKE_BUILD_TYPE=Release -DLLVM_BUILD_DOCS=OFF -DLLVM_BUILD_EXAMPLES=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_TARGETS_TO_BUILD="X86;AArch64;RISCV;ARM;LoongArch" -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;lld" -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind;compiler-rt" -DLLVM_ENABLE_WARNINGS=OFF -DCLANG_INCLUDE_TESTS=OFF -DBENCHMARK_INSTALL_DOCS=OFF -DCLANG_DEFAULT_LINKER=lld -DLLVM_ENABLE_LLD=ON -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DLIBCXX_INCLUDE_BENCHMARKS=OFF -DLIBCXX_USE_COMPILER_RT=ON -DLIBCXX_CXX_ABI=libcxxabi -DLIBCXXABI_USE_LLVM_UNWINDER=ON -DLIBCXXABI_USE_COMPILER_RT=ON -DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON -DCOMPILER_RT_USE_LIBCXX=ON' 348 | # 进入llvm项目目录 349 | cd ~/llvm 350 | # 配置llvm 351 | cmake -G Ninja --install-prefix $prefix -B build-$host-llvm -S llvm $dylib_option_list $llvm_option_list1 $compiler_option $llvm_cross_option 352 | # 编译llvm 353 | ninja -C build -j 20 354 | # 安装llvm 355 | ninja -C build install/strip -j 20 356 | ``` 357 | 358 | #### (3)打包工具链 359 | 360 | 此时需要从本地工具链中复制未编译的`compiler-rt`到交叉工具链下,还需要复制`libxml2.dll`、`libc++.dll`和`libunwind.dll`以提供运行库, 361 | 复制`libc++`和`libunwind`头文件以提供头文件支持,最后打包工具链。 362 | 363 | ```shell 364 | # 复制compiler-rt 365 | export src_dir=$native_prefix/lib/clang/20/lib 366 | export dst_dir=$prefix/lib/clang/20/lib 367 | cp -rf $src_dir $dst_dir 368 | # 复制libxml2.dll 369 | cp $libxml2_prefix/bin/libxml2.dll $prefix/bin 370 | # 复制libc++.dll 371 | cp $sysroot/$host/lib/libc++.dll $prefix/bin 372 | # 复制libunwind.dll 373 | cp $sysroot/$host/lib/libunwind.dll $prefix/bin 374 | # 复制libc++头文件 375 | cp -r $native_prefix/include/c++ $prefix/include 376 | # 复制libunwind头文件 377 | cp $native_prefix/include/*unwind* $prefix/include 378 | # 打包工具链 379 | cd ~ 380 | tar -cf $host-clang20.tar $host-clang20 381 | xz -ev9 -T 0 --memlimit=14GiB $host-clang20.tar 382 | ``` 383 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | 3 | from sphinx_pyproject import SphinxConfig 4 | 5 | root_dir = pathlib.Path(__file__).parents[2] 6 | config = SphinxConfig(root_dir / "pyproject.toml", globalns=globals()) 7 | 8 | project = name # type:ignore 9 | for key in config: 10 | globals()[key] = config[key] 11 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | toolchains 文档 2 | ======================== 3 | 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :caption: 目录 8 | 9 | toolchains 10 | -------------------------------------------------------------------------------- /doc/source/toolchains.rst: -------------------------------------------------------------------------------- 1 | toolchains 模块 2 | ================== 3 | 4 | toolchains.build\_gcc 模块 5 | --------------------------- 6 | .. automodule:: toolchains.build_gcc 7 | :members: 8 | :show-inheritance: 9 | 10 | toolchains.build\_llvm 模块 11 | --------------------------- 12 | .. automodule:: toolchains.build_llvm 13 | :members: 14 | :show-inheritance: 15 | 16 | toolchains.common 模块 17 | --------------------------- 18 | .. automodule:: toolchains.common 19 | :members: 20 | :show-inheritance: 21 | 22 | toolchains.download 模块 23 | ------------------------- 24 | .. automodule:: toolchains.download 25 | :members: 26 | :show-inheritance: 27 | 28 | toolchains.gcc\_environment 模块 29 | --------------------------------- 30 | .. automodule:: toolchains.gcc_environment 31 | :members: 32 | :show-inheritance: 33 | 34 | toolchains.llvm\_environment 模块 35 | ----------------------------------- 36 | .. automodule:: toolchains.llvm_environment 37 | :members: 38 | :show-inheritance: 39 | 40 | toolchains.utils 模块 41 | -------------------------------- 42 | .. automodule:: toolchains.utils 43 | :members: 44 | :show-inheritance: 45 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["poetry-core>=1.9.0"] 3 | build-backend = "poetry.core.masonry.api" 4 | 5 | [project] 6 | name = "toolchains" 7 | version = "0.2.2" 8 | description = "Build GCC and LLVM toolchains automatically." 9 | authors = [ 10 | { name = "24bit-xjkp", email = "2283572185@qq.com" }, 11 | { name = "Arendelle", email = "2381642961@qq.com" }, 12 | { name = "situNagisa", email = "1300296933@qq.com" }, 13 | ] 14 | readme = "README.md" 15 | license = "MIT" 16 | requires-python = "^3.12.0" 17 | dependencies = ["packaging>=21.0", "colorama>=0.4.6", "libarchive-c (>=5.1,<6.0)", "zstandard (>=0.23.0,<0.24.0)"] 18 | optional-dependencies = { complete = ["argcomplete (>=3.5.3,<4.0.0)"] } 19 | 20 | [project.urls] 21 | repository = "https://github.com/24bit-xjkp/toolchains" 22 | 23 | [project.scripts] 24 | toolchains-download = "toolchains.download:main" 25 | toolchains-gcc = "toolchains.build_gcc:main" 26 | toolchains-llvm = "toolchains.build_llvm:main" 27 | toolchains-util = "toolchains.utils:main" 28 | 29 | [tool.poetry] 30 | package-mode = true 31 | packages = [{ include = "toolchains" }] 32 | include = ["script/*", "xmake/*", "CONTRIBUTORS.md"] 33 | 34 | [tool.black] 35 | line-length = 140 36 | skip-string-normalization = false 37 | include = "(\\.pyi?)$" 38 | exclude = ''' 39 | /( 40 | \.git 41 | | \.mypy_cache 42 | | \.vscode 43 | | \.VSCodeCounter 44 | | \.xmake 45 | | buck-out 46 | | build 47 | | dist 48 | )/ 49 | ''' 50 | 51 | [tool.isort] 52 | profile = "black" 53 | line_length = 140 54 | 55 | [tool.mypy] 56 | strict = true 57 | pretty = true 58 | files = ["script/*.py", "toolchains/*.py", "test/*.py"] 59 | 60 | [tool.pytest.ini_options] 61 | addopts = "-v --maxfail=3" 62 | testpaths = ["test"] 63 | python_files = "test_*.py" 64 | python_classes = "test*" 65 | python_functions = "test_*" 66 | 67 | [tool.sphinx-pyproject] 68 | build_dir = "docs/build" 69 | conf_dir = "docs/source" 70 | extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx.ext.viewcode", "sphinx_rtd_theme", "myst_parser"] 71 | html_theme = "sphinx_rtd_theme" 72 | html_theme_options = { "collapse_navigation" = true, "sticky_navigation" = true, "navigation_depth" = 4, "titles_only" = false } 73 | package_root = "toolchains" 74 | language = "zh_CN" 75 | 76 | [tool.sphinx-pyproject.latex_elements] 77 | makeindex = '\usepackage[columns=1]{idxlayout}\makeindex' 78 | papersize = "a4paper" 79 | pointsize = "10pt" 80 | latex_engine = "xelatex" 81 | preamble = ''' 82 | \usepackage[UTF8, scheme = plain]{ctex} 83 | \usepackage{ctex} 84 | 85 | \addto\captionsenglish{\renewcommand{\chaptername}{}} 86 | \parindent 2em 87 | \setcounter{tocdepth}{3} 88 | \setCJKmainfont[BoldFont=SimHei]{SimSun} 89 | \setmainfont[Scale=0.95]{Fira Sans} 90 | ''' 91 | 92 | [tool.poetry.group.dev.dependencies] 93 | mypy = "^1.14.1" 94 | mypy-extensions = "^1.0.0" 95 | poetry = "^2.0.0" 96 | black = "^24.10.0" 97 | isort = "^5.13.2" 98 | pytest = "^8.3.4" 99 | pytest-cov = "^6.0.0" 100 | sphinx = "^8.1.3" 101 | sphinx-rtd-theme = "^3.0.2" 102 | myst-parser = "^4.0.0" 103 | sphinx-pyproject = "^0.3.0" 104 | types-colorama = "^0.4.15.20240311" 105 | -------------------------------------------------------------------------------- /script/.gdbinit: -------------------------------------------------------------------------------- 1 | python 2 | import gdb # type: ignore 3 | import os 4 | import sys 5 | 6 | share_dir: str = os.path.abspath(os.path.join(sys.path[0], "../../")) 7 | python_dir: str = "" 8 | for dir in os.listdir(share_dir): 9 | current_dir = os.path.join(share_dir, dir, "python") 10 | if dir.startswith("gcc") and os.path.isdir(current_dir): 11 | python_dir = current_dir 12 | break 13 | if python_dir: 14 | if python_dir not in sys.path: 15 | sys.path.insert(0, python_dir) 16 | from libstdcxx.v6 import register_libstdcxx_printers # type: ignore 17 | 18 | register_libstdcxx_printers(gdb.current_objfile()) 19 | else: 20 | print("Cannot find gcc python support because share/gcc*/python directory does not exist.") 21 | end 22 | -------------------------------------------------------------------------------- /script/aarch64-libc.so: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-littleaarch64) 2 | GROUP (libc.so.6 libc_nonshared.a AS_NEEDED (ld-linux-aarch64.so.1 )) 3 | -------------------------------------------------------------------------------- /script/aarch64-libm.a.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from common import * 4 | 5 | from toolchains.common import support_dry_run 6 | 7 | 8 | @support_dry_run(create_ldscript_echo) 9 | def create_ldscript(ldscript_path: Path, dry_run: bool | None = None) -> None: 10 | """创建libm.a链接器脚本 11 | 12 | Args: 13 | ldscript_path (Path): 链接器脚本路径 14 | dry_run (bool | None, optional): 是否只回显而不执行命令. 15 | """ 16 | 17 | ldscript = "OUTPUT_FORMAT(elf64-littleaarch64)\n" f"GROUP({find_libm(ldscript_path.parent)} libmvec.a)\n" 18 | 19 | ldscript_path.write_text(ldscript) 20 | 21 | 22 | def main(lib_dir: Path) -> None: 23 | create_ldscript(lib_dir / "libm.a") 24 | -------------------------------------------------------------------------------- /script/aarch64-libm.so: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-littleaarch64) 2 | GROUP (libm.so.6 AS_NEEDED (libmvec.so.1 )) 3 | -------------------------------------------------------------------------------- /script/arm-hf-libc.so: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf32-littlearm) 2 | GROUP (libc.so.6 libc_nonshared.a AS_NEEDED (ld-linux-armhf.so.3)) 3 | -------------------------------------------------------------------------------- /script/arm-sf-libc.so: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf32-littlearm) 2 | GROUP (libc.so.6 libc_nonshared.a AS_NEEDED (ld-linux.so.3)) 3 | -------------------------------------------------------------------------------- /script/common.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from toolchains.common import toolchains_info 4 | 5 | 6 | def find_libm(lib_dir: Path) -> str | None: 7 | """查找libm-version.a文件 8 | 9 | Args: 10 | lib_dir (Path): 库目录 11 | 12 | Returns: 13 | str | None: libm文件名,未找到则返回None 14 | """ 15 | 16 | for item in lib_dir.iterdir(): 17 | if (name := item.name).startswith("libm-"): 18 | return name 19 | return None 20 | 21 | 22 | def create_ldscript_echo(ldscript_path: Path) -> str: 23 | """创建链接器脚本时回显的内容 24 | 25 | Args: 26 | ldscript_path (Path): 链接器脚本路径 27 | 28 | Returns: 29 | str: 回显内容 30 | """ 31 | 32 | return toolchains_info(f'Create ldscript "{ldscript_path}".') 33 | 34 | 35 | __all__ = ["find_libm", "create_ldscript_echo"] 36 | -------------------------------------------------------------------------------- /script/i686-libc.so: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf32-i386) 2 | GROUP (libc.so.6 libc_nonshared.a AS_NEEDED (ld-linux.so.2)) 3 | -------------------------------------------------------------------------------- /script/loongarch64-libc.so: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-loongarch) 2 | GROUP (libc.so.6 libc_nonshared.a AS_NEEDED(ld-linux-loongarch-lp64d.so.1)) 3 | -------------------------------------------------------------------------------- /script/loongarch64-loongnix-libc.so: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-loongarch) 2 | GROUP (libc.so.6 libc_nonshared.a AS_NEEDED (ld.so.1)) 3 | -------------------------------------------------------------------------------- /script/mips64el-libc.so: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-tradlittlemips) 2 | GROUP (libc.so.6 libc_nonshared.a AS_NEEDED (ld.so.1 )) 3 | -------------------------------------------------------------------------------- /script/python_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import argparse 4 | import enum 5 | import os 6 | 7 | assert __name__ == "__main__", "Run this file directly instead of importing it as a module." 8 | assert ( 9 | "PYTHON_EMBED_PACKAGE" in os.environ 10 | ), "Must set the environment variable PYTHON_EMBED_PACKAGE to the path of the python embed package." 11 | python_dir = os.environ["PYTHON_EMBED_PACKAGE"] 12 | assert os.path.exists(python_dir), f"The path {python_dir} does not exist." 13 | 14 | 15 | class flag_list(enum.StrEnum): 16 | includes = f"-I{os.path.join(python_dir, 'include')}" 17 | ldflags = f"-L{python_dir} -lpython" 18 | exec_prefix = f"-L{python_dir}" 19 | 20 | 21 | parse = argparse.ArgumentParser( 22 | description="Get the configure of python embed package when build GDB with python support for MinGW platform." 23 | ) 24 | for flag in flag_list: 25 | name = flag.name 26 | parse.add_argument( 27 | f"--{name.replace('_', '-')}", action="store_true", help=f"The {name.replace('_', ' ')} of the python embed package." 28 | ) 29 | args, _ = parse.parse_known_args() 30 | for flag in flag_list: 31 | if getattr(args, flag.name, None): 32 | print(flag) 33 | -------------------------------------------------------------------------------- /script/python_config.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # 获取脚本的目录部分 3 | script_dir=$(dirname "$0") 4 | # 转换为绝对路径 5 | script_dir=$(cd "$script_dir" && pwd) 6 | /usr/bin/env python3 "$script_dir/python_config.py" $@ 7 | -------------------------------------------------------------------------------- /script/riscv64-libc.so: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-littleriscv) 2 | GROUP (libc.so.6 libc_nonshared.a AS_NEEDED (ld-linux-riscv64-lp64d.so.1)) 3 | -------------------------------------------------------------------------------- /script/x86_64-libc.so: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-x86-64) 2 | GROUP (libc.so.6 libc_nonshared.a AS_NEEDED (ld-linux-x86-64.so.2)) 3 | -------------------------------------------------------------------------------- /script/x86_64-libm.a.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from common import * 4 | 5 | from toolchains.common import support_dry_run 6 | 7 | 8 | @support_dry_run(create_ldscript_echo) 9 | def create_ldscript(ldscript_path: Path, dry_run: bool | None = None) -> None: 10 | """创建libm.a链接器脚本 11 | 12 | Args: 13 | ldscript_path (Path): 链接器脚本路径 14 | """ 15 | 16 | ldscript = "OUTPUT_FORMAT(elf64-x86-64)\n" f"GROUP({find_libm(ldscript_path.parent)} libmvec.a)\n" 17 | 18 | ldscript_path.write_text(ldscript) 19 | 20 | 21 | def main(lib_dir: Path) -> None: 22 | create_ldscript(lib_dir / "libm.a") 23 | -------------------------------------------------------------------------------- /script/x86_64-libm.so: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-x86-64) 2 | GROUP (libm.so.6 AS_NEEDED (libmvec_nonshared.a libmvec.so.1)) 3 | -------------------------------------------------------------------------------- /test/dynamic_import_test/dynamic-import-test.py: -------------------------------------------------------------------------------- 1 | # 测试能否在脚本中再次加载模块 2 | from need_import import * 3 | 4 | 5 | def foo(i: int) -> int: 6 | """返回输入的整数,用于测试动态导入功能 7 | 8 | Args: 9 | i (int): 输入整数 10 | 11 | Returns: 12 | int: 输入整数 13 | """ 14 | 15 | return i 16 | -------------------------------------------------------------------------------- /test/dynamic_import_test/need_import.py: -------------------------------------------------------------------------------- 1 | def bar(string: str) -> str: 2 | """返回输入的字符串,用于测试在动态导入的脚本中再次导入同目录下的脚本 3 | 4 | Args: 5 | string (str): 输入字符串 6 | 7 | Returns: 8 | str: 输入字符串 9 | """ 10 | return string 11 | -------------------------------------------------------------------------------- /test/test_basic_configure.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import pathlib 4 | import typing 5 | 6 | import py # type: ignore 7 | import pytest 8 | 9 | import toolchains.common as common 10 | from toolchains.common import command_dry_run 11 | 12 | type Path = py.path.LocalPath 13 | 14 | 15 | class configure(common.basic_prefix_build_configure): 16 | libs: set[str] 17 | _origin_libs: set[str] 18 | _private: int # 私有对象,在序列化/反序列化时不应该被访问 19 | 20 | def __init__(self, libs: list[str] | None = None, **kwargs: typing.Any) -> None: 21 | super().__init__(**kwargs) 22 | self._origin_libs = {*(libs or [])} 23 | self.register_encode_name_map("libs", "_origin_libs") 24 | self.libs = {"basic", *self._origin_libs} 25 | self._private = 0 26 | 27 | def __eq__(self, other: object) -> bool: 28 | assert isinstance(other, configure) 29 | return self.home == other.home and self.build == other.build and self.prefix_dir == other.prefix_dir and self.libs == other.libs 30 | 31 | 32 | def test_default_construct() -> None: 33 | """测试configure是否可以正常默认构造""" 34 | 35 | configure() 36 | 37 | 38 | class test_basic_configure: 39 | parser: argparse.ArgumentParser 40 | default_config: configure 41 | 42 | @classmethod 43 | def setup_class(cls) -> None: 44 | cls.default_config = configure() 45 | cls.parser = argparse.ArgumentParser() 46 | subparsers = cls.parser.add_subparsers(dest="command") 47 | build_parser = subparsers.add_parser("build") 48 | prefix_parser = subparsers.add_parser("prefix") 49 | libs_parser = subparsers.add_parser("libs") 50 | 51 | # 添加公共选项 52 | for subparser in (build_parser, prefix_parser, libs_parser): 53 | configure.add_argument(subparser) 54 | 55 | # 添加各个子命令的选项 56 | libs_parser.add_argument("--libs", nargs="*", action="extend") 57 | 58 | def test_common_args(self) -> None: 59 | """测试公共选项是否添加到每个子命令中,针对basic_configure.add_argument""" 60 | 61 | subparsers = self.parser._subparsers 62 | assert subparsers 63 | subparser_actions = subparsers._group_actions[0].choices 64 | assert subparser_actions 65 | for _, subparser in subparser_actions.items(): # type: ignore 66 | arg_list: set[str] = set() 67 | subparser = typing.cast(argparse.ArgumentParser, subparser) 68 | for action in subparser._actions: 69 | arg_list.add(action.dest) 70 | assert {"home", "import_file", "export_file", "dry_run", "build", "prefix_dir"} < arg_list 71 | 72 | @pytest.mark.parametrize("command", ["build", "prefix", "libs"]) 73 | def test_default_config(self, command: str) -> None: 74 | """测试在不传递参数全部使用默认设置的情况下,各个子命令是否解析参数得到的configure对象是否和默认一致 75 | 针对basic_configure.parse_args 76 | """ 77 | 78 | args = self.parser.parse_args([command]) 79 | current_config = configure.parse_args(args) 80 | assert self.default_config == current_config 81 | 82 | def test_subcommand_build(self) -> None: 83 | """测试build选项能否正常解析 84 | 针对整数解析 85 | """ 86 | 87 | custom_build = "x86_64-w64-mingw32" 88 | args = self.parser.parse_args(["build", "--build", custom_build]) 89 | current_config = configure.parse_args(args) 90 | gt = configure() 91 | gt.build = custom_build 92 | assert current_config == gt 93 | 94 | def test_subcommand_prefix(self, tmpdir: Path) -> None: 95 | """测试prefix选项能否正常解析 96 | 针对需要从字符串构造的对象 97 | """ 98 | 99 | custom_prefix = str(tmpdir) 100 | args = self.parser.parse_args(["prefix", "--prefix", custom_prefix]) 101 | current_config = configure.parse_args(args) 102 | gt = configure() 103 | gt.prefix_dir = pathlib.Path(custom_prefix) 104 | assert current_config == gt 105 | 106 | def test_subcommand_libs(self) -> None: 107 | """测试libs选项能否正常解析 108 | 针对可空列表解析 109 | """ 110 | 111 | custom_libs = ["extra1", "extra2"] 112 | args = self.parser.parse_args(["libs", "--libs", *custom_libs]) 113 | current_config = configure.parse_args(args) 114 | assert current_config == configure(libs=custom_libs) 115 | 116 | # 用户尝试清空列表,变回默认设置 117 | args = self.parser.parse_args(["libs", "--libs"]) 118 | current_config = configure.parse_args(args) 119 | assert current_config == self.default_config 120 | 121 | def test_import(self, tmpdir: Path) -> None: 122 | """测试从文件导入配置信息 123 | 124 | Args: 125 | tmpdir (Path): 临时文件路径 126 | """ 127 | 128 | home = pathlib.Path(tmpdir) 129 | tmpfile = home / "test.json" 130 | custom_prefix = str(tmpdir) # 用户输入prefix 131 | import_libs = ["basic", "extra1", "extra2"] # 保存在json中的libs 132 | 133 | test_json: dict[str, typing.Any] = { 134 | "home": str(home), 135 | "build": self.default_config.build, 136 | "prefix": str(self.default_config.prefix_dir), 137 | "libs": import_libs, 138 | } 139 | with open(tmpfile, "w") as file: 140 | json.dump(test_json, file) 141 | 142 | args = self.parser.parse_args(["prefix", "--import", str(tmpfile), "--prefix", custom_prefix]) 143 | current_config = configure.parse_args(args) 144 | gt = configure(libs=["extra1", "extra2"]) 145 | gt.prefix_dir = pathlib.Path(custom_prefix) 146 | gt.build = self.default_config.build 147 | gt.home = home 148 | assert current_config == gt 149 | 150 | # 尝试恢复默认配置 151 | args = self.parser.parse_args(["libs", "--import", str(tmpfile), "--libs"]) 152 | current_config = configure.parse_args(args) 153 | gt = configure() 154 | gt.build = self.default_config.build 155 | gt.home = home 156 | assert current_config == gt 157 | 158 | def test_export(self, tmpdir: Path) -> None: 159 | """测试导出配置信息到文件 160 | 161 | Args: 162 | tmpdir (Path): 临时文件路径 163 | """ 164 | 165 | tmpfile = pathlib.Path(tmpdir) / "test.json" 166 | custom_prefix = str(tmpdir) # 用户输入prefix 167 | args = self.parser.parse_args(["prefix", "--prefix", custom_prefix, "--home", ".", "--export", str(tmpfile)]) 168 | current_config = configure.parse_args(args) 169 | current_config.save_config() 170 | 171 | gt: dict[str, typing.Any] = {"home": ".", "build": self.default_config.build, "prefix_dir": custom_prefix, "libs": []} 172 | with tmpfile.open() as file: 173 | export_config = json.load(file) 174 | assert export_config == gt 175 | 176 | def test_dry_run(self) -> None: 177 | """测试全局的dry_run状态是否正常设置""" 178 | 179 | args = self.parser.parse_args(["prefix", "--dry-run"]) 180 | _ = configure.parse_args(args) 181 | assert command_dry_run.get() == True 182 | args = self.parser.parse_args(["prefix", "--no-dry-run"]) 183 | _ = configure.parse_args(args) 184 | assert command_dry_run.get() == False 185 | 186 | def test_import_noexist_file(self, tmpdir: Path) -> None: 187 | """测试打开一个不存在的配置文件 188 | 189 | Args: 190 | tmpdir (Path): 临时文件目录 191 | """ 192 | 193 | with pytest.raises(Exception): 194 | args = self.parser.parse_args(["prefix", "--import", str(tmpdir / "test.json")]) 195 | _ = configure.parse_args(args) 196 | 197 | def test_export_unwritable_file(self) -> None: 198 | """测试写入一个不可写的配置文件 199 | 200 | Args: 201 | tmpdir (Path): 临时文件目录 202 | """ 203 | 204 | with pytest.raises(Exception): 205 | args = self.parser.parse_args(["prefix", "--export", "/dev/full"]) 206 | _ = configure.parse_args(args) 207 | _.save_config() 208 | -------------------------------------------------------------------------------- /test/test_chdir_guard.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import py # type: ignore 4 | 5 | from toolchains.common import chdir_guard 6 | 7 | 8 | def test_chdir_guard(tmpdir: py.path.LocalPath) -> None: 9 | """测试chdir_guard""" 10 | 11 | cwd = Path.cwd() 12 | path = Path(tmpdir) 13 | with chdir_guard(path): 14 | assert Path.cwd() == path 15 | assert Path.cwd() == cwd 16 | with chdir_guard(path, True): 17 | assert Path.cwd() == cwd 18 | assert Path.cwd() == cwd 19 | -------------------------------------------------------------------------------- /test/test_dynamic_import.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from collections.abc import Callable 3 | from pathlib import Path 4 | 5 | from toolchains.common import * 6 | 7 | 8 | def test_dynamic_import_function() -> None: 9 | """测试从模块动态导入函数能否正常工作""" 10 | 11 | root_dir = Path(__file__).parent 12 | module_path = root_dir / "dynamic_import_test" / "dynamic-import-test.py" 13 | sys_path = sys.path.copy() 14 | with dynamic_import_module(module_path) as module: 15 | foo: Callable[[int], int] = dynamic_import_function("foo", module) 16 | i = 1 17 | assert foo(i) == i 18 | bar: Callable[[str], str] = dynamic_import_function("bar", module) 19 | string = "Hello World" 20 | assert bar(string) == string 21 | # 测试sys.path是否复原 22 | assert sys_path == sys.path 23 | -------------------------------------------------------------------------------- /test/test_global_settings.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from toolchains.common import basic_configure, command_dry_run, command_quiet, need_dry_run, status_counter, toolchains_quiet 4 | 5 | 6 | def test_need_dry_run() -> None: 7 | """测试need_dry_run是否能正确判断""" 8 | 9 | # 全局不进行的dry run 10 | command_dry_run.set(False) 11 | assert need_dry_run(None) == False 12 | assert need_dry_run(False) == False 13 | assert need_dry_run(True) == True 14 | 15 | # 全局进行dry run 16 | command_dry_run.set(True) 17 | assert need_dry_run(None) == True 18 | assert need_dry_run(False) == False 19 | assert need_dry_run(True) == True 20 | 21 | 22 | def test_need_quiet() -> None: 23 | """测试安静选项是否正确判断""" 24 | 25 | parser = argparse.ArgumentParser() 26 | basic_configure.add_argument(parser) 27 | 28 | args = parser.parse_args([]) 29 | basic_configure.parse_args(args) 30 | assert not command_quiet.get() and not toolchains_quiet.get() and not status_counter.get_quiet() 31 | assert command_quiet.get_option() == "" 32 | 33 | args = parser.parse_args(["-q"]) 34 | basic_configure.parse_args(args) 35 | assert command_quiet.get() and not toolchains_quiet.get() and not status_counter.get_quiet() 36 | assert command_quiet.get_option() == "--quiet" 37 | 38 | args = parser.parse_args(["-qq"]) 39 | basic_configure.parse_args(args) 40 | assert command_quiet.get() and toolchains_quiet.get() and not status_counter.get_quiet() 41 | 42 | args = parser.parse_args(["-qqq"]) 43 | basic_configure.parse_args(args) 44 | assert command_quiet.get() and toolchains_quiet.get() and status_counter.get_quiet() 45 | 46 | 47 | def test_status_counter() -> None: 48 | """测试状态计数器""" 49 | 50 | status_counter.clear() 51 | for name in ("error", "warning", "note", "info", "success"): 52 | getattr(status_counter, f"add_{name}")() 53 | assert status_counter.get_counter(name) == 1 54 | -------------------------------------------------------------------------------- /test/test_triplet_field.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from toolchains.common import triplet_field 4 | 5 | 6 | def test_0_filed() -> None: 7 | with pytest.raises(RuntimeError): 8 | triplet_field("") 9 | 10 | 11 | def test_1_filed() -> None: 12 | with pytest.raises(RuntimeError): 13 | triplet_field("x86_64") 14 | 15 | 16 | def test_2_field() -> None: 17 | fields = triplet_field("x86_64-elf") 18 | assert fields.arch == "x86_64" 19 | assert fields.vendor == "unknown" 20 | assert fields.os == "unknown" 21 | assert fields.abi == "elf" 22 | 23 | 24 | def test_3_field() -> None: 25 | fields = triplet_field("x86_64-linux-gnu") 26 | assert fields.arch == "x86_64" 27 | assert fields.vendor == "unknown" 28 | assert fields.os == "linux" 29 | assert fields.abi == "gnu" 30 | 31 | fields = triplet_field("arm-none-eabi") 32 | assert fields.arch == "arm" 33 | assert fields.vendor == "unknown" 34 | assert fields.os == "none" 35 | assert fields.abi == "eabi" 36 | 37 | fields = triplet_field("x86_64-nonewlib-elf") 38 | assert fields.arch == "x86_64" 39 | assert fields.vendor == "nonewlib" 40 | assert fields.os == "unknown" 41 | assert fields.abi == "elf" 42 | 43 | 44 | def test_4_field() -> None: 45 | fields = triplet_field("x86_64-pc-linux-gnu") 46 | assert fields.arch == "x86_64" 47 | assert fields.vendor == "pc" 48 | assert fields.os == "linux" 49 | assert fields.abi == "gnu" 50 | 51 | 52 | def test_field_check() -> None: 53 | assert triplet_field.check("") == False 54 | assert triplet_field.check("x86_64") == False 55 | assert triplet_field.check("x86_64-") == False 56 | assert triplet_field.check("x86_64--") == False 57 | assert triplet_field.check("x86_64---") == False 58 | assert triplet_field.check("x86_64-elf") == True 59 | 60 | 61 | def test_drop_vendor() -> None: 62 | assert triplet_field("x86_64-pc-linux-gnu").drop_vendor() == "x86_64-linux-gnu" 63 | 64 | 65 | def test_weak_eq() -> None: 66 | assert triplet_field("x86_64-linux-gnu").weak_eq(triplet_field("x86_64-pc-linux-gnu")) 67 | assert not triplet_field("x86_64-linux-gnu").weak_eq(triplet_field("x86_64-linux-musl")) 68 | assert not triplet_field("x86_64-linux-gnu").weak_eq(triplet_field("x86_64-pc-linux-musl")) 69 | 70 | 71 | def test_normalize() -> None: 72 | fields = triplet_field("arm-none-eabi", True) 73 | assert fields.arch == "arm" 74 | assert fields.vendor == "unknown" 75 | assert fields.os == "unknown" 76 | assert fields.abi == "eabi" 77 | -------------------------------------------------------------------------------- /toolchains/__init__.py: -------------------------------------------------------------------------------- 1 | from . import build_gcc, build_llvm, common, download, gcc_environment 2 | 3 | __all__ = ["build_gcc", "build_llvm", "common", "download", "gcc_environment"] 4 | -------------------------------------------------------------------------------- /toolchains/build_gcc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # PYTHON_ARGCOMPLETE_OK 4 | 5 | import argparse 6 | 7 | from . import common 8 | from .build_gcc_source import * 9 | 10 | 11 | def _check_input(args: argparse.Namespace, need_check: bool) -> None: 12 | if need_check: 13 | assert args.jobs > 0, common.toolchains_error(f"Invalid jobs: {args.jobs}.") 14 | assert 1 <= args.compress_level <= 22, common.toolchains_error(f"Invalid compress level: {args.compress_level}") 15 | check_triplet(args.host, args.target) 16 | 17 | 18 | def build_specific_gcc( 19 | config: gcc_configure, 20 | host: str, 21 | target: str, 22 | ) -> None: 23 | """构建gcc工具链 24 | 25 | Args: 26 | config (configure): 编译环境 27 | host (str): 宿主平台 28 | target (str): 目标平台 29 | """ 30 | 31 | config_list = config.get_public_fields() 32 | env = build_gcc_environment(host=host, target=target, **config_list) 33 | modifier_list.modify(env, target) 34 | env.build() 35 | common.toolchains_print(common.toolchains_success(f"Build {env.env.name} successfully.")) 36 | 37 | 38 | def dump_support_platform() -> None: 39 | """打印所有受支持的平台""" 40 | 41 | print(common.color.note.wrapper("Host support:")) 42 | for host in gcc_support_platform_list.host_list: 43 | print(f"\t{host}") 44 | print(common.color.note.wrapper("Target support:")) 45 | for target in gcc_support_platform_list.target_list: 46 | print(f"\t{target}") 47 | 48 | print(common.color.note.wrapper("NOTE:"), "You can add a vendor field to triplets above.") 49 | # 没有执行任何实际操作,无需打印状态计数 50 | common.status_counter.set_quiet(True) 51 | 52 | 53 | __all__ = [ 54 | "modifier_list", 55 | "gcc_support_platform_list", 56 | "gcc_configure", 57 | "build_gcc_environment", 58 | "check_triplet", 59 | "build_specific_gcc", 60 | "dump_support_platform", 61 | ] 62 | 63 | 64 | def main() -> int: 65 | default_config = gcc_configure() 66 | 67 | parser = argparse.ArgumentParser(description="Build GCC toolchain to specific platform.") 68 | subparsers = parser.add_subparsers(dest="command", required=True, help="Available commands.") 69 | build_parser = subparsers.add_parser("build", help="Build the GCC toolchain.", formatter_class=common.arg_formatter) 70 | subparsers.add_parser("dump", help="Print support platforms and exit.", formatter_class=common.arg_formatter) 71 | 72 | # 添加build相关选项 73 | gcc_configure.add_argument(build_parser) 74 | action = build_parser.add_argument("--host", type=str, help="The host platform of the GCC toolchain.", default=default_config.build) 75 | common.register_completer(action, common.triplet_completer(gcc_support_platform_list.host_list)) 76 | action = build_parser.add_argument("--target", type=str, help="The target platform of the GCC toolchain.", default=default_config.build) 77 | common.register_completer(action, common.triplet_completer(gcc_support_platform_list.target_list)) 78 | build_parser.add_argument( 79 | "--gdb", action=argparse.BooleanOptionalAction, help="Whether to enable gdb support in GCC toolchain.", default=default_config.gdb 80 | ) 81 | build_parser.add_argument( 82 | "--gdbserver", 83 | action=argparse.BooleanOptionalAction, 84 | help="Whether to enable gdbserver support in GCC toolchain.", 85 | default=default_config.gdbserver, 86 | ) 87 | build_parser.add_argument( 88 | "--newlib", 89 | action=argparse.BooleanOptionalAction, 90 | help="Whether to enable newlib support in GCC freestanding toolchain.", 91 | default=default_config.newlib, 92 | ) 93 | build_parser.add_argument( 94 | "--nls", 95 | action=argparse.BooleanOptionalAction, 96 | help="Whether to enable nls(nature language support) in GCC toolchain.", 97 | default=default_config.nls, 98 | ) 99 | 100 | common.support_argcomplete(parser) 101 | args = parser.parse_args() 102 | 103 | def do_main() -> None: 104 | match (args.command): 105 | case "build": 106 | _check_input(args, args.command == "build") 107 | current_config = gcc_configure.parse_args(args) 108 | 109 | # 检查合并配置后环境是否正确 110 | current_config.check() 111 | current_config.save_config() 112 | build_specific_gcc(current_config, args.host, args.target) 113 | case "dump": 114 | dump_support_platform() 115 | case _: 116 | pass 117 | 118 | return common.toolchains_main(do_main) 119 | -------------------------------------------------------------------------------- /toolchains/build_gcc_source.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from . import common 4 | from .gcc_environment import build_gcc_environment 5 | 6 | 7 | class modifier_list: 8 | """针对特定平台修改gcc构建环境的回调函数""" 9 | 10 | @staticmethod 11 | def arm_linux_gnueabi(env: build_gcc_environment) -> None: 12 | """针对arm-linux-gnueabi平台使用arm-sf的链接器脚本 13 | 14 | Args: 15 | env (build_gcc_environment): 当前gcc构建平台 16 | """ 17 | 18 | env.adjust_glibc_arch = "arm-sf" 19 | 20 | @staticmethod 21 | def arm_linux_gnueabihf(env: build_gcc_environment) -> None: 22 | """针对arm-linux-gnueabihf平台使用arm-hf的链接器脚本 23 | 24 | Args: 25 | env (build_gcc_environment): 当前gcc构建平台 26 | """ 27 | 28 | env.adjust_glibc_arch = "arm-hf" 29 | 30 | @staticmethod 31 | def loongarch64_loongnix_linux_gnu(env: build_gcc_environment) -> None: 32 | """针对loongarch64-loongnix-linux-gnu平台 33 | 1. 使用loongarch64-loongnix的链接器脚本 34 | 2. 使用预编译的glibc 35 | 3. gcc添加--disable-libsanitizer选项 36 | 37 | Args: 38 | env (build_gcc_environment): 当前gcc构建平台 39 | """ 40 | 41 | def build_gcc(build_env: build_gcc_environment) -> None: 42 | """loongarch64-loongnix-linux-gnu专业编译流程,跳过glibc编译 43 | 44 | Args: 45 | self (build_gcc_environment): gcc构建环境 46 | """ 47 | 48 | env = build_env.env 49 | # 编译binutils,如果启用gdb则一并编译 50 | env.enter_build_dir("binutils") 51 | env.configure("binutils", *build_env.basic_option, *build_env.gdb_option) 52 | env.make() 53 | env.install() 54 | 55 | # 安装Linux头文件 56 | env.enter_build_dir("linux") 57 | env.make(*build_env.linux_option) 58 | 59 | # 复制glibc文件 60 | glibc_dir = env.home / "glibc-loongnix" 61 | for dir in ("include", "lib"): 62 | src_dir = glibc_dir / dir 63 | dst_dir = env.lib_prefix / dir 64 | for item in src_dir.iterdir(): 65 | common.copy(item, dst_dir / item.name) 66 | 67 | # 编译完整gcc 68 | env.enter_build_dir("gcc") 69 | env.configure("gcc", *build_env.basic_option, *build_env.gcc_option) 70 | build_gcc_environment.make_with_libbacktrace_patch(env) 71 | env.install() 72 | 73 | # 完成后续工作 74 | build_env.after_build_gcc() 75 | 76 | env.adjust_glibc_arch = "loongarch64-loongnix" 77 | # 若成功找到glibc-loongnix则直接从预编译包中复制 78 | if env.env.lib_dir_list["glibc"].name == "glibc-loongnix": 79 | env.full_build_linux = build_gcc # type: ignore 80 | env.gcc_option.append("--disable-libsanitizer") 81 | 82 | @staticmethod 83 | def x86_64_w64_mingw32(env: build_gcc_environment) -> None: 84 | env.libc_option += ["--disable-lib32", "--enable-lib64"] 85 | 86 | @staticmethod 87 | def i686_w64_mingw32(env: build_gcc_environment) -> None: 88 | env.libc_option += ["--disable-lib64", "--enable-lib32"] 89 | 90 | @staticmethod 91 | def arm_none_eabi(env: build_gcc_environment) -> None: 92 | """arm嵌入式cpu大多使用armv7-m,只支持Thumb2 93 | 94 | Args: 95 | env (build_gcc_environment): 当前gcc构建平台 96 | """ 97 | 98 | env.gcc_option += ["--with-mode=thumb", "--with-arch=armv7-m"] 99 | 100 | @staticmethod 101 | def arm_nonewlib_none_eabi(env: build_gcc_environment) -> None: 102 | """arm嵌入式cpu大多使用armv7-m,只支持Thumb2 103 | 104 | Args: 105 | env (build_gcc_environment): 当前gcc构建平台 106 | """ 107 | 108 | env.gcc_option += ["--with-mode=thumb", "--with-arch=armv7-m"] 109 | 110 | @staticmethod 111 | def arm_fpv4_none_eabi(env: build_gcc_environment) -> None: 112 | """Thumb2+fpv4-sp-d16 113 | 114 | Args: 115 | env (build_gcc_environment): 当前gcc构建平台 116 | """ 117 | 118 | env.gcc_option += ["--with-mode=thumb", "--with-arch=armv7-m", "--with-fpu=fpv4-sp-d16", "--with-float=hard"] 119 | env.libc_option += ['CFLAGS_FOR_TARGET="-march=armv7-m -mfpu=fpv4-sp-d16 -mfloat-abi=hard -O3"'] 120 | 121 | @staticmethod 122 | def modify(env: build_gcc_environment, target: str) -> None: 123 | target = target.replace("-", "_") 124 | if modifier := getattr(modifier_list, target, None): 125 | modifier(env) 126 | 127 | 128 | class gcc_support_platform_list: 129 | """受支持的平台列表,不包含vendor字段 130 | 131 | Attributes: 132 | host_list : 支持的GCC工具链宿主平台 133 | target_list: 支持的GCC工具链目标平台 134 | """ 135 | 136 | host_list: typing.Final[list[str]] = ["x86_64-linux-gnu", "x86_64-w64-mingw32"] 137 | target_list: typing.Final[list[str]] = [ 138 | "x86_64-linux-gnu", 139 | "i686-linux-gnu", 140 | "aarch64-linux-gnu", 141 | "arm-linux-gnueabi", 142 | "arm-linux-gnueabihf", 143 | "loongarch64-linux-gnu", 144 | "riscv64-linux-gnu", 145 | "riscv64-none-elf", 146 | "x86_64-w64-mingw32", 147 | "i686-w64-mingw32", 148 | "arm-none-eabi", 149 | "x86_64-elf", 150 | "mips64el-linux-gnuabi64", 151 | ] 152 | 153 | 154 | class gcc_configure(common.basic_build_configure): 155 | """gcc构建配置""" 156 | 157 | gdb: bool 158 | gdbserver: bool 159 | newlib: bool 160 | nls: bool 161 | toolchain_type: str = "GCC" 162 | 163 | def __init__(self, gdb: bool = True, gdbserver: bool = True, newlib: bool = True, nls: bool = True, **kwargs: typing.Any) -> None: 164 | """设置gcc构建配置 165 | 166 | Args: 167 | gdb (bool, optional): 是否构建gdb. 默认为构建. 168 | gdbserver (bool, optional): 是否构建gdbserver. 默认为构建. 169 | newlib (bool, optional): 是否为独立工具链构建newlib. 默认为构建. 170 | nls (bool, optional): 是否启用nls. 默认为启用. 171 | """ 172 | 173 | super().__init__(**kwargs) 174 | self.gdb = gdb 175 | self.gdbserver = gdbserver 176 | self.newlib = newlib 177 | self.nls = nls 178 | 179 | 180 | def check_triplet(host: str, target: str) -> None: 181 | """检查输入triplet是否合法 182 | 183 | Args: 184 | host (str): 宿主平台 185 | target (str): 目标平台 186 | """ 187 | 188 | for input_triplet, triplet_list, name in ( 189 | (host, gcc_support_platform_list.host_list, "Host"), 190 | (target, gcc_support_platform_list.target_list, "Target"), 191 | ): 192 | input_triplet_field = common.triplet_field(input_triplet) 193 | for support_triplet in triplet_list: 194 | support_triplet_field = common.triplet_field(support_triplet) 195 | if input_triplet_field.weak_eq(support_triplet_field): 196 | break 197 | else: 198 | raise RuntimeError(common.toolchains_error(f'{name} "{input_triplet}" is not support.')) 199 | 200 | 201 | __all__ = ["modifier_list", "gcc_support_platform_list", "gcc_configure", "build_gcc_environment", "check_triplet"] 202 | -------------------------------------------------------------------------------- /toolchains/build_llvm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # PYTHON_ARGCOMPLETE_OK 4 | 5 | import argparse 6 | import typing 7 | from pathlib import Path 8 | 9 | from . import common 10 | from .build_llvm_source import * 11 | from .gcc_environment import get_specific_environment 12 | 13 | 14 | def sysroot(env: llvm_environment) -> None: 15 | """从已安装的gcc中复制库并创建sysroot 16 | 17 | Args: 18 | env (llvm_environment): llvm构建环境 19 | """ 20 | 21 | sysroot_dir = env.sysroot_dir[env.build] 22 | common.mkdir(sysroot_dir, True) 23 | libgcc_prefix = sysroot_dir / "lib" / "gcc" 24 | common.mkdir(libgcc_prefix) 25 | for target in llvm_support_platform_list.target_list: 26 | target_dir = sysroot_dir / target 27 | common.mkdir(target_dir) 28 | match (target): 29 | case "armv7m-none-eabi" | "armv7m-fpv4-none-eabi": 30 | # 将armv7m转化为arm以便和gcc保持一致 31 | triplet_filed = common.triplet_field(target) 32 | triplet_filed.arch = "arm" 33 | target = str(triplet_filed) if triplet_filed.vendor != "unknown" else triplet_filed.drop_vendor() 34 | gcc = get_specific_environment(env, env.build, target) 35 | # 复制include和lib 36 | for dir in ("include", "lib"): 37 | common.copy(gcc.lib_prefix / dir, target_dir / dir) 38 | # 复制libgcc 39 | for file in filter( 40 | lambda file: file.suffix in (".a", ".o", ".specs"), 41 | (gcc.prefix / "lib" / "gcc" / target / gcc.version).iterdir(), 42 | ): 43 | common.copy(file, target_dir / "lib" / file.name) 44 | # 复制target相关文件夹到c++ include根目录的文件夹下 45 | cpp_dir = target_dir / "include" / "c++" / gcc.version 46 | for dir in ("bits", "ext"): 47 | for file in (cpp_dir / target / dir).iterdir(): 48 | common.copy(file, cpp_dir / dir / file.name) 49 | case _: 50 | gcc = get_specific_environment(env, env.build, target) 51 | if gcc.toolchain_type.contain(common.toolchain_type.native): 52 | # 复制include和lib64 53 | for dir in ("include", "lib64"): 54 | common.copy(gcc.prefix / dir, target_dir / dir) 55 | # 复制glibc链接库 56 | common.copy(gcc.lib_prefix / "lib", target_dir / "lib") 57 | # 复制glibc头和linux头 58 | for item in (gcc.lib_prefix / "include").iterdir(): 59 | common.copy(item, target_dir / "include" / item.name) 60 | else: 61 | # 复制除libgcc外所有库 62 | for dir in ("include", "lib", "lib64"): 63 | common.copy_if_exist(gcc.lib_prefix / dir, target_dir / dir) 64 | # 复制libgcc 65 | common.copy(gcc.prefix / "lib" / "gcc" / target, libgcc_prefix / target) 66 | 67 | common.toolchains_print(common.toolchains_success("Build sysroot successfully.")) 68 | 69 | 70 | def build_specific_llvm(env: llvm_environment) -> None: 71 | """构建指定llvm 72 | 73 | Args: 74 | env (llvm_environment): llvm构建环境 75 | """ 76 | 77 | modifier_list.modify(env, [*env.runtime_build_options]) 78 | build_llvm_environment.build(env) 79 | 80 | common.toolchains_print(common.toolchains_success("Build LLVM successfully.")) 81 | 82 | 83 | __all__ = ["modifier_list", "llvm_support_platform_list", "llvm_configure", "llvm_environment", "sysroot_config", "sysroot"] 84 | 85 | 86 | def main() -> int: 87 | default_config = llvm_configure() 88 | 89 | parser = argparse.ArgumentParser(description="Build LLVM toolchain to specific platform.") 90 | subparsers = parser.add_subparsers(dest="command", required=True, help="Available commands.") 91 | sysroot_parser = subparsers.add_parser( 92 | "sysroot", 93 | help="Build sysroot for llvm toolchain using installed gcc toolchains. This will remove existed sysroot.", 94 | formatter_class=common.arg_formatter, 95 | ) 96 | build_parser = subparsers.add_parser("build", help="Build the LLVM toolchain.", formatter_class=common.arg_formatter) 97 | 98 | sysroot_config.add_argument(sysroot_parser) 99 | llvm_configure.add_argument(build_parser) 100 | action = build_parser.add_argument("--host", type=str, help="The host platform of the LLVM toolchain.", default=default_config.build) 101 | common.register_completer(action, common.triplet_completer(llvm_support_platform_list.host_list)) 102 | build_parser.add_argument( 103 | "--family", "-f", type=str, help="The runtime family of the LLVM toolchain.", default=runtime_family.gnu, choices=runtime_family 104 | ) 105 | 106 | common.support_argcomplete(parser) 107 | args = parser.parse_args() 108 | 109 | def do_main() -> None: 110 | match (args.command): 111 | case "sysroot": 112 | sysroot_config_v: dict[str, typing.Any] = sysroot_config.parse_args(args).get_public_fields() 113 | sysroot_config_v["jobs"] = 1 114 | sysroot_config_v["compress_level"] = 1 115 | sysroot_config_v["long_distance_match"] = 27 116 | sysroot_config_v["host"] = None 117 | sysroot_config_v["default_generator"] = cmake_generator.ninja 118 | sysroot_config_v["family"] = runtime_family.gnu 119 | sysroot_config_v["runtime_target_list"] = [sysroot_config_v["build"]] 120 | sysroot_config_v["build_tmp"] = Path.home() / "build_tmp" 121 | sysroot(llvm_environment(**sysroot_config_v)) 122 | case "build": 123 | build_config = llvm_configure.parse_args(args) 124 | assert args.host in llvm_support_platform_list.host_list, common.toolchains_error(f"Host {args.host} is not supported.") 125 | env = llvm_environment( 126 | host=args.host, 127 | family=args.family, 128 | runtime_target_list=llvm_support_platform_list.target_list, 129 | **build_config.get_public_fields(), 130 | ) 131 | build_specific_llvm(env) 132 | case _: 133 | pass 134 | 135 | return common.toolchains_main(do_main) 136 | 137 | 138 | __all__ = [ 139 | "modifier_list", 140 | "llvm_support_platform_list", 141 | "llvm_configure", 142 | "llvm_environment", 143 | "build_llvm_environment", 144 | "runtime_family", 145 | "cmake_generator", 146 | "sysroot_config", 147 | "sysroot", 148 | "build_specific_llvm", 149 | ] 150 | -------------------------------------------------------------------------------- /toolchains/build_llvm_source.py: -------------------------------------------------------------------------------- 1 | import typing 2 | from argparse import ArgumentParser 3 | 4 | from . import common 5 | from .build_gcc_source import gcc_support_platform_list 6 | from .llvm_environment import build_llvm_environment, cmake_generator, llvm_environment, runtime_family 7 | 8 | 9 | class modifier_list: 10 | """针对特定平台修改llvm构建环境的回调函数""" 11 | 12 | @staticmethod 13 | def arm_linux_gnueabi(env: llvm_environment) -> None: 14 | env.runtime_build_options["arm-linux-gnueabi"].basic_option.append("-march=armv7-a") 15 | 16 | @staticmethod 17 | def arm_linux_gnueabihf(env: llvm_environment) -> None: 18 | env.runtime_build_options["arm-linux-gnueabihf"].basic_option.append("-march=armv7-a") 19 | 20 | @staticmethod 21 | def loongarch64_loongnix_linux_gnu(env: llvm_environment) -> None: 22 | env.runtime_build_options["loongarch64-loongnix-linux-gnu"].cmake_option.update( 23 | { 24 | "COMPILER_RT_BUILD_SANITIZERS": "OFF", 25 | "COMPILER_RT_BUILD_GWP_ASAN": "OFF", 26 | "COMPILER_RT_BUILD_XRAY": "OFF", 27 | "COMPILER_RT_BUILD_MEMPROF": "OFF", 28 | "COMPILER_RT_BUILD_CTX_PROFILE": "OFF", 29 | } 30 | ) 31 | 32 | @staticmethod 33 | def armv7m_none_eabi(env: llvm_environment) -> None: 34 | target = "armv7m-none-eabi" 35 | env.sysroot_dir[target] = env.prefix_dir / "sysroot" / target 36 | env.generator_list[target] = cmake_generator.make 37 | 38 | @staticmethod 39 | def armv7m_fpv4_none_eabi(env: llvm_environment) -> None: 40 | target = "armv7m-fpv4-none-eabi" 41 | env.sysroot_dir[target] = env.prefix_dir / "sysroot" / target 42 | env.generator_list[target] = cmake_generator.make 43 | env.runtime_build_options[target].basic_option += ["-march=armv7-m", "-mfpu=fpv4-sp-d16", "-mfloat-abi=hard"] 44 | 45 | @staticmethod 46 | def modify(env: llvm_environment, targets: list[str]) -> None: 47 | for target in targets: 48 | target = target.replace("-", "_") 49 | if modifier := getattr(modifier_list, target, None): 50 | modifier(env) 51 | 52 | 53 | def generate_hosted_list_from_gcc() -> list[str]: 54 | """从gcc目标列表中获取目标 55 | 56 | Returns: 57 | list[str]: 宿主平台列表 58 | """ 59 | 60 | phony_triplet = "phony-phony-phony" 61 | hosted_list: list[str] = [] 62 | unsupported_list: list[str] = ["mips64el-linux-gnuabi64"] 63 | 64 | for target in gcc_support_platform_list.target_list: 65 | toolchain_type = common.toolchain_type.classify_toolchain(phony_triplet, phony_triplet, target) 66 | if toolchain_type.contain(common.toolchain_type.hosted) and target not in unsupported_list: 67 | hosted_list.append(target) 68 | 69 | return hosted_list 70 | 71 | 72 | class llvm_support_platform_list: 73 | """受支持的平台列表 74 | 75 | Attributes: 76 | host_list : 支持的LLVM工具链宿主平台 77 | arch_list: 支持的LLVM目标平台 78 | project_list: 支持的子项目 79 | runtime_list: 支持的运行时库 80 | target_list: 支持的runtimes的target列表 81 | """ 82 | 83 | host_list: typing.Final[list[str]] = gcc_support_platform_list.host_list 84 | arch_list: typing.Final[list[str]] = ["X86", "AArch64", "RISCV", "ARM", "LoongArch", "Mips"] 85 | project_list: typing.Final[list[str]] = ["clang", "clang-tools-extra", "lld", "lldb", "bolt", "mlir"] 86 | runtime_list: typing.Final[list[str]] = ["libcxx", "libcxxabi", "libunwind", "compiler-rt", "openmp"] 87 | target_list: typing.Final[list[str]] = [ 88 | *generate_hosted_list_from_gcc(), 89 | "loongarch64-loongnix-linux-gnu", 90 | "armv7m-none-eabi", 91 | "armv7m-fpv4-none-eabi", 92 | ] 93 | 94 | 95 | class llvm_configure(common.basic_build_configure): 96 | """llvm构建配置""" 97 | 98 | toolchain_type: str = "LLVM" 99 | default_generator: cmake_generator 100 | 101 | def __init__(self, default_generator: str = cmake_generator.ninja, **kwargs: typing.Any) -> None: 102 | """设置llvm构建配置 103 | 104 | Args: 105 | 106 | """ 107 | 108 | super().__init__(**kwargs) 109 | self.default_generator = cmake_generator[default_generator] 110 | 111 | @classmethod 112 | def add_argument(cls, parser: ArgumentParser) -> None: 113 | super().add_argument(parser) 114 | 115 | default_config = llvm_configure() 116 | parser.add_argument( 117 | "--generator", 118 | "-g", 119 | type=str, 120 | help="The generator to use when build projects with cmake.", 121 | dest="default_generator", 122 | default=default_config.default_generator, 123 | choices=cmake_generator, 124 | ) 125 | 126 | 127 | sysroot_config = common.basic_prefix_build_configure 128 | 129 | 130 | __all__ = [ 131 | "modifier_list", 132 | "llvm_support_platform_list", 133 | "llvm_configure", 134 | "llvm_environment", 135 | "build_llvm_environment", 136 | "runtime_family", 137 | "cmake_generator", 138 | "sysroot_config", 139 | ] 140 | -------------------------------------------------------------------------------- /toolchains/download.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # PYTHON_ARGCOMPLETE_OK 4 | 5 | import argparse 6 | import os 7 | import pathlib 8 | import tempfile 9 | 10 | from . import common 11 | from .download_source import * 12 | 13 | 14 | def _exist_echo(lib: str) -> None: 15 | """包已存在时显示提示""" 16 | common.toolchains_print(common.toolchains_note(f"Lib {lib} exists, skip download.")) 17 | 18 | 19 | def _up_to_date_echo(lib: str) -> None: 20 | """包已是最新时显示提示""" 21 | common.toolchains_print(common.toolchains_note(f"Lib {lib} is up to date, skip update.")) 22 | 23 | 24 | def _check_version_echo(lib: str, result: int) -> bool: 25 | """根据包版本检查结果显示提示,并返回是否需要更新 26 | 27 | Args: 28 | lib (str): 包名称 29 | result (int): 版本检查结果 30 | 31 | Returns: 32 | bool: 是否需要更新 33 | """ 34 | if result == 1: 35 | common.toolchains_print(common.toolchains_note(f"Lib {lib} is newer than default version, skip update.")) 36 | return False 37 | elif result == 0: 38 | _up_to_date_echo(lib) 39 | return False 40 | else: 41 | return True 42 | 43 | 44 | def download_gcc_contrib(config: configure) -> None: 45 | """下载gcc的依赖包 46 | 47 | Args: 48 | config (configure): 源代码下载环境 49 | """ 50 | with common.chdir_guard(config.home / "gcc"): 51 | common.run_command("contrib/download_prerequisites", echo=not common.command_quiet.get()) 52 | common.status_counter.add_success() 53 | 54 | 55 | def download_specific_extra_lib(config: configure, lib: str) -> None: 56 | """下载指定的非git托管包 57 | 58 | Args: 59 | config (configure): 源代码下载环境 60 | lib (str): 要下载的包名 61 | """ 62 | assert lib in all_lib_list.extra_lib_list, common.toolchains_error(f"Unknown extra lib: {lib}") 63 | extra_lib_v = all_lib_list.extra_lib_list[lib] 64 | for file, url in extra_lib_v.url_list.items(): 65 | common.run_command(f"wget {url} {common.command_quiet.get_option()} -c -t {config.network_try_times} -O {config.home / file}") 66 | 67 | 68 | def download(config: configure) -> None: 69 | """下载不存在的源代码,不会更新已有源代码 70 | 71 | Args: 72 | config (configure): 源代码下载环境 73 | """ 74 | # 下载git托管的源代码 75 | for lib, url_fields in all_lib_list.get_prefer_git_lib_list(config).items(): 76 | lib_dir = config.home / lib 77 | if not lib_dir.exists(): 78 | url = url_fields.get_url(config.git_use_ssh) 79 | extra_options: list[str] = [*extra_git_options_list.get_option(config, lib), git_clone_type.get_clone_option(config)] 80 | extra_option = " ".join(extra_options) 81 | # 首先从源上克隆代码,但不进行签出 82 | for _ in range(config.network_try_times): 83 | try: 84 | common.run_command( 85 | f"git clone {url} {common.command_quiet.get_option()} {extra_option} --no-checkout {lib_dir}", add_counter=False 86 | ) 87 | break 88 | except KeyboardInterrupt: 89 | common.remove_if_exists(lib_dir) 90 | common.keyboard_interpret_received() 91 | except: 92 | common.remove_if_exists(lib_dir) 93 | common.toolchains_print(common.toolchains_warning(f"Clone {lib} failed, retrying.")) 94 | else: 95 | raise RuntimeError(common.toolchains_error(f"Clone {lib} failed.")) 96 | # 从git储存库中签出HEAD 97 | for _ in range(config.network_try_times): 98 | try: 99 | common.run_command(f"git -C {lib_dir} checkout HEAD", add_counter=False) 100 | break 101 | except KeyboardInterrupt: 102 | common.keyboard_interpret_received() 103 | except: 104 | common.toolchains_print(common.toolchains_warning(f"Checkout {lib} failed, retrying.")) 105 | else: 106 | raise RuntimeError(common.toolchains_error(f"Checkout {lib} failed.")) 107 | after_download_list.after_download_specific_lib(config, lib) 108 | common.status_counter.add_success() 109 | else: 110 | _exist_echo(lib) 111 | 112 | # 下载非git托管代码 113 | for lib in config.extra_lib_list: 114 | assert lib in all_lib_list.extra_lib_list, common.toolchains_error(f"Unknown extra lib: {lib}") 115 | if not all_lib_list.extra_lib_list[lib].check_exist(config): 116 | download_specific_extra_lib(config, lib) 117 | after_download_list.after_download_specific_lib(config, lib) 118 | else: 119 | _exist_echo(lib) 120 | for lib in ("gmp", "mpfr", "isl", "mpc"): 121 | if not (config.home / "gcc" / lib).exists(): 122 | download_gcc_contrib(config) 123 | break 124 | else: 125 | _exist_echo("gcc_contrib") 126 | common.toolchains_print(common.toolchains_success("Download libs successfully.")) 127 | 128 | 129 | def update(config: configure) -> None: 130 | """更新所有源代码,要求所有包均已下载 131 | 132 | Args: 133 | config (configure): 源代码下载环境 134 | """ 135 | 136 | # 更新git托管的源代码 137 | for lib in all_lib_list.get_prefer_git_lib_list(config): 138 | lib_dir = config.home / lib 139 | assert lib_dir.exists(), common.toolchains_error(f"Cannot find lib: {lib}") 140 | with tempfile.TemporaryFile("r+") as file: 141 | for _ in range(config.network_try_times): 142 | try: 143 | common.run_command(f"git -C {lib_dir} fetch --dry-run", capture=(file, file), add_counter=False) 144 | break 145 | except KeyboardInterrupt: 146 | common.keyboard_interpret_received() 147 | except: 148 | common.toolchains_print(common.toolchains_warning(f"Fetch {lib} failed, retrying.")) 149 | file.truncate(0) 150 | file.seek(0, os.SEEK_SET) 151 | else: 152 | raise RuntimeError(common.toolchains_error(f"Fetch {lib} failed.")) 153 | 154 | if file.tell(): 155 | for _ in range(config.network_try_times): 156 | try: 157 | common.run_command(f"git -C {lib_dir} pull {common.command_quiet.get_option()}", add_counter=False) 158 | break 159 | except KeyboardInterrupt: 160 | common.keyboard_interpret_received() 161 | except: 162 | common.toolchains_print(common.toolchains_warning(f"Pull {lib} failed, retrying.")) 163 | else: 164 | raise RuntimeError(common.toolchains_error(f"Pull {lib} failed.")) 165 | after_download_list.after_download_specific_lib(config, lib) 166 | common.status_counter.add_success() 167 | else: 168 | _up_to_date_echo(lib) 169 | 170 | # 更新非git包 171 | for lib in config.extra_lib_list: 172 | lib_version = extra_lib_version[lib if lib != "python-embed" else "python"] 173 | need_download = _check_version_echo( 174 | lib, lib_version.check_version(config.home / all_lib_list.get_prefer_extra_lib_list(config, lib).version_dir) 175 | ) 176 | if need_download: 177 | download_specific_extra_lib(config, lib) 178 | after_download_list.after_download_specific_lib(config, lib) 179 | 180 | common.toolchains_print(common.toolchains_success("Update libs successfully.")) 181 | 182 | 183 | def auto_download(config: configure) -> None: 184 | """首先下载缺失的包,然后更新已有的包 185 | 186 | Args: 187 | config (configure): 源代码下载环境 188 | """ 189 | download(config) 190 | update(config) 191 | 192 | 193 | def get_system_lib_list() -> list[str]: 194 | """获取系统包列表 195 | 196 | Returns: 197 | list[str]: 系统包列表 198 | """ 199 | return all_lib_list.system_lib_list 200 | 201 | 202 | def remove_specific_lib(config: configure, lib: str) -> None: 203 | """删除指定包 204 | 205 | Args: 206 | config (configure): 源代码下载环境 207 | lib (str): 要删除的包 208 | 209 | Raises: 210 | RuntimeError: 删除未知包时抛出异常 211 | """ 212 | 213 | if lib in all_lib_list.extra_lib_list: 214 | install_item: list[pathlib.Path] = all_lib_list.get_prefer_extra_lib_list(config, lib).install_dir 215 | elif lib in all_lib_list.git_lib_list_github: 216 | install_item = [config.home / lib] 217 | elif lib == "gcc_contrib": 218 | gcc_dir = config.home / "gcc" 219 | if not gcc_dir.exists(): 220 | common.toolchains_print(common.toolchains_note(f"Lib {lib} does not exist, skip remove.")) 221 | return 222 | install_item = [ 223 | gcc_dir / item for item in filter(lambda x: x.name.startswith(("gettext", "gmp", "mpc", "mpfr", "isl")), gcc_dir.iterdir()) 224 | ] 225 | else: 226 | raise RuntimeError(common.toolchains_error(f"Unknown lib {lib}.")) 227 | 228 | removed = False 229 | for dir in install_item: 230 | try: 231 | if dir.exists(): 232 | common.remove(dir) 233 | removed = True 234 | except Exception as e: 235 | raise RuntimeError(common.toolchains_error(f"Remove lib {lib} failed: {e}")) 236 | if not removed: 237 | common.toolchains_print(common.toolchains_warning(f"Lib {lib} does not exist, skip remove.")) 238 | 239 | 240 | def remove(config: configure, libs: list[str]) -> None: 241 | """删除指定包 242 | 243 | Args: 244 | config (configure): 源代码下载环境 245 | libs (list[str]): 要删除的包列表 246 | 247 | Raises: 248 | RuntimeError: 删除未知包时抛出异常 249 | """ 250 | for lib in libs: 251 | remove_specific_lib(config, lib) 252 | common.toolchains_print(common.toolchains_success("Remove libs successfully.")) 253 | 254 | 255 | def _check_input(args: argparse.Namespace) -> None: 256 | """检查输入是否正确""" 257 | if args.command in ("download", "auto"): 258 | assert args.glibc_version, common.toolchains_error(f"Invalid glibc version: {args.glibc_version}") 259 | assert args.depth > 0, common.toolchains_error(f"Invalid shallow clone depth: {args.depth}.") 260 | if args.command in ("update", "download", "auto"): 261 | assert args.retry >= 0, common.toolchains_error(f"Invalid network try times: {args.retry}.") 262 | 263 | 264 | __all__ = [ 265 | "extra_lib_version", 266 | "git_clone_type", 267 | "git_prefer_remote", 268 | "all_lib_list", 269 | "configure", 270 | "after_download_list", 271 | "extra_git_options_list", 272 | "download_gcc_contrib", 273 | "download_specific_extra_lib", 274 | "download", 275 | "update", 276 | "auto_download", 277 | "get_system_lib_list", 278 | "remove_specific_lib", 279 | "remove", 280 | ] 281 | 282 | 283 | def main() -> int: 284 | """cli主函数""" 285 | 286 | default_config = configure() 287 | 288 | parser = argparse.ArgumentParser(description="Download or update needy libs for building gcc and llvm.") 289 | 290 | subparsers = parser.add_subparsers(dest="command", required=True, help="Available commands.") 291 | update_parser = subparsers.add_parser( 292 | "update", help="Update installed libs. All libs should be installed before update.", formatter_class=common.arg_formatter 293 | ) 294 | download_parser = subparsers.add_parser( 295 | "download", help="Download missing libs. This would not update existing libs.", formatter_class=common.arg_formatter 296 | ) 297 | auto_parser = subparsers.add_parser( 298 | "auto", 299 | help="Download missing libs, then update installed libs. This may take more time because of twice check.", 300 | formatter_class=common.arg_formatter, 301 | ) 302 | subparsers.add_parser("system", help="Print needy system libs and exit.", formatter_class=common.arg_formatter) 303 | remove_parser = subparsers.add_parser( 304 | "remove", 305 | help="Remove installed libs. Use without specific lib name to remove all installed libs.", 306 | formatter_class=common.arg_formatter, 307 | ) 308 | 309 | # 添加公共选项 310 | for subparser in (update_parser, download_parser, auto_parser, remove_parser): 311 | configure.add_argument(subparser) 312 | for subparser in (update_parser, download_parser, auto_parser): 313 | subparser.add_argument( 314 | "--retry", type=int, help="The number of retries when a network operation failed.", default=default_config.network_try_times - 1 315 | ) 316 | subparser.add_argument( 317 | "--extra-libs", 318 | action="extend", 319 | nargs="*", 320 | help="Extra non-git libs to install.", 321 | choices=all_lib_list.optional_extra_lib_list, 322 | ) 323 | subparser.add_argument( 324 | "--remote", 325 | type=str, 326 | help="The remote repository preferred to use. The preferred remote will be used to accelerate download when possible.", 327 | default=default_config.git_remote, 328 | choices=git_prefer_remote, 329 | ) 330 | for subparser in (download_parser, auto_parser): 331 | subparser.add_argument( 332 | "--glibc", dest="glibc_version", type=str, help="The version of glibc of target platform.", default=default_config.glibc_version 333 | ) 334 | subparser.add_argument( 335 | "--clone-type", 336 | type=str, 337 | help="How to clone the git repository.", 338 | default=default_config.clone_type, 339 | choices=git_clone_type, 340 | ) 341 | subparser.add_argument("--depth", type=int, help="The depth of shallow clone.", default=default_config.shallow_clone_depth) 342 | subparser.add_argument( 343 | "--ssh", type=bool, help="Whether to use ssh when cloning git repositories from github.", default=default_config.git_use_ssh 344 | ) 345 | 346 | # 添加各个子命令专属选项 347 | remove_parser.add_argument( 348 | "remove", 349 | action="extend", 350 | nargs="*", 351 | help="Remove installed libs. Use without specific lib name to remove all installed libs.", 352 | choices=all_lib_list.all_lib_list, 353 | ) 354 | 355 | common.support_argcomplete(parser) 356 | args = parser.parse_args() 357 | 358 | def do_main() -> None: 359 | if args.command == "system": 360 | print(common.toolchains_info(f"Please install following system libs: \n{' '.join(get_system_lib_list())}")) 361 | common.status_counter.set_quiet(True) 362 | return 363 | 364 | # 检查输入是否合法 365 | _check_input(args) 366 | current_config = configure.parse_args(args) 367 | # 检查合并配置后环境是否正确 368 | current_config.check(args.command == "download") 369 | current_config.save_config() 370 | 371 | match (args.command): 372 | case "update": 373 | update(current_config) 374 | case "download": 375 | download(current_config) 376 | case "auto": 377 | auto_download(current_config) 378 | case "remove": 379 | remove(current_config, args.remove or all_lib_list.all_lib_list) 380 | case _: 381 | pass 382 | 383 | return common.toolchains_main(do_main) 384 | -------------------------------------------------------------------------------- /toolchains/download_source.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import typing 3 | from collections.abc import Sequence 4 | from pathlib import Path 5 | 6 | import packaging.version as version 7 | 8 | from . import common 9 | 10 | 11 | class extra_lib_version(enum.StrEnum): 12 | python = "3.13.2" 13 | iconv = "1.18" 14 | loongnix_linux = "4.19.190.8.22" 15 | loongnix_glibc = "2.28" 16 | loongnix = loongnix_linux 17 | gmp = "6.3.0" 18 | mpfr = "4.2.2" 19 | 20 | def _save_version_echo(self, dir: Path) -> str: 21 | """在保存包信息时回显信息 22 | 23 | Args: 24 | dir (Path): 要保存版本信息文件的目录 25 | 26 | Returns: 27 | str: 回显信息 28 | """ 29 | 30 | return common.toolchains_info(f"Save version of {self.name} -> {dir / ".version"}.") 31 | 32 | @common.support_dry_run(_save_version_echo) 33 | def save_version(self, dir: Path) -> None: 34 | """将包版本信息保存到dir/.version文件中 35 | 36 | Args: 37 | dir (Path): 要保存版本信息文件的目录 38 | """ 39 | 40 | (dir / ".version").write_text(self) 41 | 42 | def check_version(self, dir: Path) -> int: 43 | """检查包版本 44 | 45 | Args: 46 | dir (Path): 包根目录 47 | 48 | Returns: 49 | int: 三路比较结果,1为存在更新版本,0为版本一致,-1为需要更新 50 | """ 51 | 52 | try: 53 | with (dir / ".version").open() as file: 54 | current_version = version.Version(file.readline()) 55 | target_version = version.Version(self) 56 | if current_version > target_version: 57 | return 1 58 | elif current_version == target_version: 59 | return 0 60 | else: 61 | return -1 62 | except Exception: 63 | return -1 64 | 65 | 66 | def get_current_glibc_version() -> str | None: 67 | """获取当前glibc版本 68 | 69 | Returns: 70 | (str | None): 当前平台glibc版本,获取失败返回None 71 | """ 72 | 73 | result = common.run_command("getconf GNU_LIBC_VERSION", ignore_error=True, capture=True, echo=False, dry_run=False) 74 | if result: 75 | return result.stdout.strip().split(" ", 1)[1] 76 | else: 77 | return None 78 | 79 | 80 | class git_clone_type(enum.StrEnum): 81 | """git克隆类型 82 | 83 | Attributes: 84 | partial: 使用部分克隆,仅克隆提交树,然后签出最新提交。在一些较老的git服务器上不受支持。 85 | shallow: 使用浅克隆,仅克隆depth中指定数量的提交,可以加速克隆,但可能减慢git pull等操作。 86 | full: 使用完全克隆,克隆完整的git仓库,消耗较多流量和时间。 87 | """ 88 | 89 | partial = "partial" # 部分克隆 90 | shallow = "shallow" # 浅克隆 91 | full = "full" # 完全克隆 92 | 93 | @staticmethod 94 | def get_clone_option(config: "configure") -> str: 95 | match (config.clone_type): 96 | case git_clone_type.partial: 97 | return "--filter=blob:none" 98 | case git_clone_type.shallow: 99 | return f"--depth={config.shallow_clone_depth}" 100 | case git_clone_type.full: 101 | return "" 102 | 103 | 104 | class git_url: 105 | remote: str 106 | path: str 107 | default_protocol: str 108 | 109 | def __init__(self, remote: str, path: str, default_protocol: str = "https") -> None: 110 | """配置一个git包的远程源 111 | 112 | Args: 113 | remote (str): 托管平台名称 114 | path (str): git包在托管平台下的路径 115 | default_protocol (str, optional): 非ssh模式下默认的网络协议. 默认为https. 116 | """ 117 | 118 | self.remote = remote 119 | self.path = path 120 | self.default_protocol = default_protocol 121 | 122 | def get_url(self, prefer_ssh: bool) -> str: 123 | """获取git仓库的url 124 | 125 | Args: 126 | prefer_ssh (bool): 是否倾向于使用ssh 127 | """ 128 | 129 | use_ssh = prefer_ssh and self.remote == "github.com" 130 | return f"git@{self.remote}:{self.path}" if use_ssh else f"{self.default_protocol}://{self.remote}/{self.path}" 131 | 132 | 133 | class git_prefer_remote(enum.StrEnum): 134 | """git远程托管平台 135 | 136 | Attributes: 137 | github: 使用GitHub上的仓库,部分为镜像源 138 | native: 使用项目的原git仓库作为源 139 | nju : 在可能时使用南京大学镜像源,否则退回到使用GitHub源 140 | tuna : 在可能时使用清华大学镜像源,否则退回到使用GitHub源 141 | bfsu : 在可能时使用北京外国语大学镜像源,否则退回到使用GitHub源 142 | nyist : 在可能时使用南阳理工学院镜像源,否则退回到使用GitHub源 143 | cernet: 在可能时使用校园网联合镜像源,否则退回到使用GitHub源 144 | """ 145 | 146 | github = "github" 147 | native = "native" 148 | nju = "nju" 149 | tuna = "tuna" 150 | bfsu = "bfsu" 151 | nyist = "nyist" 152 | cernet = "cernet" 153 | 154 | 155 | class extra_lib: 156 | """非git包的配置""" 157 | 158 | url_list: dict[Path, str] 159 | install_dir: list[Path] 160 | version_dir: Path 161 | 162 | def __init__(self, url_list: dict[str, str], install_dir: Sequence[str | Path], version_dir: str | Path) -> None: 163 | """描述一个非git包的配置 164 | 165 | Args: 166 | url_list (dict[str, str]): 各个资源列表,dict[下载后文件名, url] 167 | install_dir (list[str | Path]): 安装路径 168 | version_dir (str): 包含版本文件的目录 169 | """ 170 | 171 | self.url_list = {Path(file): url for file, url in url_list.items()} 172 | self.install_dir = [Path(dir) for dir in install_dir] 173 | self.version_dir = Path(version_dir) 174 | 175 | def create_mirror(self, url_list: dict[str, str]) -> "extra_lib": 176 | """在本包配置的基础上创建一个镜像 177 | 178 | Args: 179 | url_list (dict[str, str]): 镜像使用的url列表,dict[下载后文件名, url] 180 | """ 181 | 182 | return extra_lib(url_list, self.install_dir, self.version_dir) 183 | 184 | def check_exist(self, config: "configure") -> bool: 185 | """检查包是否存在 186 | 187 | Args: 188 | config (configure): 源代码下载环境 189 | """ 190 | 191 | for path in self.install_dir: 192 | if not (config.home / path).exists(): 193 | return False 194 | else: 195 | return True 196 | 197 | 198 | class all_lib_list: 199 | """所有包源列表 200 | 201 | Attributes: 202 | system_lib_list : 系统包表 203 | git_lib_list_github: git包的GitHub源 204 | git_lib_list_native: git包的原仓库源 205 | git_lib_list_nju : git包的南京大学镜像源 206 | git_lib_list_tuna : git包的清华大学镜像源 207 | git_lib_list_bfsu : git包的北京外国语大学镜像源 208 | git_lib_list_nyist : git包的南阳理工学院镜像源 209 | git_lib_list_cernet: git包的校园网联合镜像源 210 | extra_lib_list : 非git包的信息列表,默认为南京大学镜像 211 | extra_lib_list_native: 非git包的信息列表,不使用镜像 212 | necessary_extra_lib_list: 必须的非git包列表 213 | optional_extra_lib_list : 可选的非git包列表 214 | all_lib_list : 所有受支持的包列表 215 | """ 216 | 217 | system_lib_list: typing.Final[list[str]] = [ 218 | "bison", 219 | "flex", 220 | "texinfo", 221 | "make", 222 | "automake", 223 | "autoconf", 224 | "libtool", 225 | "git", 226 | "gcc", 227 | "g++", 228 | "tar", 229 | "unzip", 230 | "libgmp-dev", 231 | "libmpfr-dev", 232 | "zlib1g-dev", 233 | "libexpat1-dev", 234 | "gawk", 235 | "bzip2", 236 | "cmake", 237 | "ninja-build", 238 | "clang", 239 | "lld", 240 | "libxml2-dev", 241 | "wget", 242 | "swig", 243 | "rsync", 244 | "libncurses5-dev", # ncurses5-compat-libs for ArchLinux 245 | ] 246 | git_lib_list_github: typing.Final[dict[str, git_url]] = { 247 | "gcc": git_url("github.com", "gcc-mirror/gcc.git"), 248 | "binutils": git_url("github.com", "bminor/binutils-gdb.git"), 249 | "mingw": git_url("github.com", "mirror/mingw-w64.git"), 250 | "expat": git_url("github.com", "libexpat/libexpat.git"), 251 | "linux": git_url("github.com", "torvalds/linux.git"), 252 | "glibc": git_url("github.com", "bminor/glibc.git"), 253 | "pexports": git_url("github.com", "bocke/pexports.git"), 254 | "zlib": git_url("github.com", "madler/zlib.git"), 255 | "libxml2": git_url("github.com", "GNOME/libxml2.git"), 256 | "newlib": git_url("github.com", "bminor/newlib.git"), 257 | "llvm": git_url("github.com", "llvm/llvm-project.git"), 258 | } 259 | git_lib_list_native: typing.Final[dict[str, git_url]] = { 260 | **git_lib_list_github, 261 | "gcc": git_url("gcc.gnu.org", "git/gcc.git", "git"), 262 | "binutils": git_url("sourceware.org", "git/binutils-gdb.git"), 263 | "mingw": git_url("git.code.sf.net", "p/mingw-w64/mingw-w64 mingw-w64-mingw-w64.git", "git"), 264 | "glibc": git_url("sourceware.org", "git/glibc.git"), 265 | "pexports": git_url("git.osdn.net", "gitroot/mingw/pexports.git", "git"), 266 | "libxml2": git_url("gitlab.gnome.org", "GNOME/libxml2.git"), 267 | "newlib": git_url("sourceware.org", "git/newlib-cygwin.git"), 268 | } 269 | git_lib_list_nju: typing.Final[dict[str, git_url]] = { 270 | **git_lib_list_github, 271 | "gcc": git_url("mirror.nju.edu.cn", "git/gcc.git"), 272 | "binutils": git_url("mirror.nju.edu.cn", "git/binutils-gdb.git"), 273 | "glibc": git_url("mirror.nju.edu.cn", "git/glibc.git"), 274 | "linux": git_url("mirror.nju.edu.cn", "git/linux.git"), 275 | "llvm": git_url("mirror.nju.edu.cn", "git/llvm-project.git"), 276 | } 277 | git_lib_list_tuna: typing.Final[dict[str, git_url]] = { 278 | **git_lib_list_github, 279 | "gcc": git_url("mirrors.tuna.tsinghua.edu.cn", "git/gcc.git"), 280 | "binutils": git_url("mirrors.tuna.tsinghua.edu.cn", "git/binutils-gdb.git"), 281 | "glibc": git_url("mirrors.tuna.tsinghua.edu.cn", "git/glibc.git"), 282 | "linux": git_url("mirrors.tuna.tsinghua.edu.cn", "git/linux.git"), 283 | "llvm": git_url("mirrors.tuna.tsinghua.edu.cn", "git/llvm-project.git"), 284 | } 285 | git_lib_list_bfsu: typing.Final[dict[str, git_url]] = { 286 | **git_lib_list_github, 287 | "gcc": git_url("mirrors.bfsu.edu.cn", "git/gcc.git"), 288 | "binutils": git_url("mirrors.bfsu.edu.cn", "git/binutils-gdb.git"), 289 | "glibc": git_url("mirrors.bfsu.edu.cn", "git/glibc.git"), 290 | "linux": git_url("mirrors.bfsu.edu.cn", "git/linux.git"), 291 | "llvm": git_url("mirrors.bfsu.edu.cn", "git/llvm-project.git"), 292 | } 293 | git_lib_list_nyist: typing.Final[dict[str, git_url]] = { 294 | **git_lib_list_github, 295 | "gcc": git_url("mirror.nyist.edu.cn", "git/gcc.git"), 296 | "binutils": git_url("mirror.nyist.edu.cn", "git/binutils-gdb.git"), 297 | "glibc": git_url("mirror.nyist.edu.cn", "git/glibc.git"), 298 | "linux": git_url("mirror.nyist.edu.cn", "git/linux.git"), 299 | "llvm": git_url("mirror.nyist.edu.cn", "git/llvm-project.git"), 300 | } 301 | git_lib_list_cernet: typing.Final[dict[str, git_url]] = { 302 | **git_lib_list_github, 303 | "gcc": git_url("mirrors.cernet.edu.cn", "gcc.git"), 304 | "binutils": git_url("mirrors.cernet.edu.cn", "binutils-gdb.git"), 305 | "glibc": git_url("mirrors.cernet.edu.cn", "glibc.git"), 306 | "linux": git_url("mirrors.cernet.edu.cn", "linux.git"), 307 | "llvm": git_url("mirrors.cernet.edu.cn", "llvm-project.git"), 308 | } 309 | 310 | # 额外包列表,由于native网络性能不佳,默认使用南京大学镜像 311 | extra_lib_list: typing.Final[dict[str, extra_lib]] = { 312 | "python-embed": extra_lib( 313 | { 314 | "python-embed.zip": f"https://mirrors.nju.edu.cn/python/{extra_lib_version.python}/python-{extra_lib_version.python}-embed-amd64.zip", 315 | "python_source.tar.xz": f"https://mirrors.nju.edu.cn/python/{extra_lib_version.python}/Python-{extra_lib_version.python}.tar.xz", 316 | }, 317 | ["python-embed"], 318 | "python-embed", 319 | ), 320 | "loongnix": extra_lib( 321 | { 322 | "linux-loongnix.tar.gz": f"https://mirrors.nju.edu.cn/loongnix/pool/main/l/linux/linux_{extra_lib_version.loongnix_linux}.orig.tar.gz", 323 | "glibc-loongnix.tar.zst": f"https://github.com/24bit-xjkp/toolchains/releases/download/vendor-packages/glibc-loongnix-{extra_lib_version.loongnix_glibc}.tar.zst", 324 | }, 325 | ["linux-loongnix", "glibc-loongnix"], 326 | "linux-loongnix", 327 | ), 328 | "iconv": extra_lib( 329 | {"iconv.tar.gz": f"https://ftp.gnu.org/pub/gnu/libiconv/libiconv-{extra_lib_version.iconv}.tar.gz"}, 330 | [Path("binutils", "gdb", "libiconv")], 331 | Path("binutils", "gdb", "libiconv"), 332 | ), 333 | "gmp": extra_lib({"gmp.tar.xz": f"https://gmplib.org/download/gmp/gmp-{extra_lib_version.gmp}.tar.xz"}, ["gmp"], "gmp"), 334 | "mpfr": extra_lib({"mpfr.tar.xz": f"https://www.mpfr.org/mpfr-current/mpfr-{extra_lib_version.mpfr}.tar.xz"}, ["mpfr"], "mpfr"), 335 | } 336 | extra_lib_list_native: typing.Final[dict[str, extra_lib]] = { 337 | **extra_lib_list, 338 | "python-embed": extra_lib_list["python-embed"].create_mirror( 339 | { 340 | "python-embed.zip": f"https://www.python.org/ftp/python/{extra_lib_version.python}/python-{extra_lib_version.python}-embed-amd64.zip", 341 | "python_source.tar.xz": f"https://www.python.org/ftp/python/{extra_lib_version.python}/Python-{extra_lib_version.python}.tar.xz", 342 | } 343 | ), 344 | "loongnix": extra_lib_list["loongnix"].create_mirror( 345 | { 346 | "linux-loongnix.tar.gz": f"https://pkg.loongnix.cn/loongnix/pool/main/l/linux/linux_{extra_lib_version.loongnix_linux}.orig.tar.gz", 347 | "glibc-loongnix.tar.zst": f"https://gitee.com/xjkp-24bit/toolchains/releases/download/vendor-packages/glibc-loongnix-{extra_lib_version.loongnix_glibc}.tar.zst", 348 | } 349 | ), 350 | } 351 | necessary_extra_lib_list: typing.Final[set[str]] = {"python-embed", "gmp", "mpfr"} 352 | optional_extra_lib_list: typing.Final[set[str]] = {lib for lib in extra_lib_list} - necessary_extra_lib_list 353 | all_lib_list: typing.Final[list[str]] = [*git_lib_list_github, *extra_lib_list, "gcc_contrib"] 354 | 355 | @staticmethod 356 | def get_prefer_git_lib_list(config: "configure") -> dict[str, git_url]: 357 | """根据配置选择使用合适git源的git包列表 358 | 359 | Args: 360 | config (configure): 当前下载配置 361 | 362 | Returns: 363 | dict[str, git_url]: git包列表 364 | """ 365 | 366 | return typing.cast(dict[str, git_url], getattr(all_lib_list, f"git_lib_list_{config.git_remote}")) 367 | 368 | @staticmethod 369 | def get_prefer_extra_lib_list(config: "configure", lib: str) -> extra_lib: 370 | """根据配置选择使用合适镜像源的非git包 371 | 372 | Args: 373 | config (configure): 当前下载配置 374 | lib (str): 包名称 375 | 376 | Returns: 377 | extra_lib: 非git包对象 378 | """ 379 | 380 | return typing.cast(dict[str, extra_lib], getattr(all_lib_list, f"extra_lib_list_{config.git_remote}", all_lib_list.extra_lib_list))[ 381 | lib 382 | ] 383 | 384 | 385 | class configure(common.basic_configure): 386 | """源代码下载配置信息""" 387 | 388 | glibc_version: str | None 389 | clone_type: git_clone_type 390 | shallow_clone_depth: int 391 | git_use_ssh: bool 392 | extra_lib_list: set[str] 393 | network_try_times: int 394 | git_remote: git_prefer_remote 395 | 396 | _origin_extra_lib_list: set[str] # 用户输入的其他非git托管包列表 397 | _origin_retry: int # 用户输入的重试的次数 398 | 399 | def __init__( 400 | self, 401 | glibc_version: str | None = get_current_glibc_version(), 402 | clone_type: str = git_clone_type.partial, 403 | depth: int = 1, 404 | ssh: bool = False, 405 | extra_libs: list[str] | None = None, 406 | retry: int = 5, 407 | remote: str = git_prefer_remote.github, 408 | **kwargs: typing.Any, 409 | ) -> None: 410 | """设置源代码配置信息,可默认构造以提供默认配置 411 | 412 | Args: 413 | glibc_version (str | None, optional): glibc版本号. 默认为当前平台的glibc版本. 414 | clone_type (str, optional): git克隆类型. 默认为部分克隆. 415 | depth (int, optional): git浅克隆深度. 默认为1. 416 | ssh (bool, optional): 是否使用ssh克隆GitHub上的git包. 默认为不用ssh,即使用https. 417 | extra_libs (list[str] | None, optional): 额外的非git包列表. 默认不启用额外包. 418 | retry (int, optional): 进行网络操作时重试的次数. 默认为5次. 419 | remote (str, optional): 倾向于使用的git源. 默认为GitHub源. 420 | """ 421 | 422 | super().__init__(**kwargs) 423 | self.glibc_version = glibc_version 424 | self.clone_type = git_clone_type[clone_type] 425 | self.shallow_clone_depth = depth 426 | self.register_encode_name_map("depth", "shallow_clone_depth") 427 | self.git_use_ssh = ssh 428 | self.register_encode_name_map("ssh", "git_use_ssh") 429 | self._origin_extra_lib_list = {*(extra_libs or [])} 430 | self.register_encode_name_map("extra_libs", "_origin_extra_lib_list") 431 | self.extra_lib_list = {*all_lib_list.necessary_extra_lib_list, *self._origin_extra_lib_list} 432 | self._origin_retry = retry 433 | self.register_encode_name_map("retry", "_origin_retry") 434 | self.network_try_times = self._origin_retry + 1 435 | self.git_remote = git_prefer_remote[remote] 436 | self.register_encode_name_map("remote", "git_remote") 437 | 438 | def check(self, need_glibc: bool) -> None: 439 | """检查各个参数是否合法""" 440 | 441 | common.check_home(self.home) 442 | if need_glibc: 443 | assert self.glibc_version, f"Invalid glibc version: {self.glibc_version}" 444 | assert self.shallow_clone_depth > 0, f"Invalid shallow clone depth: {self.shallow_clone_depth}." 445 | assert self.network_try_times >= 1, f"Invalid network try times: {self.network_try_times}." 446 | 447 | 448 | class after_download_list: 449 | """在包下载完成后执行的回调函数""" 450 | 451 | @staticmethod 452 | def expat(config: configure) -> None: 453 | """通过autoconf生成expat的configure文件 454 | 455 | Args: 456 | config (configure): 当前源代码下载配置 457 | """ 458 | 459 | with common.chdir_guard(config.home / "expat" / "expat"): 460 | common.run_command("./buildconf.sh") 461 | 462 | @staticmethod 463 | def pexports(config: configure) -> None: 464 | """通过autoconf生成pexports的configure文件 465 | 466 | Args: 467 | config (configure): 当前源代码下载配置 468 | """ 469 | 470 | with common.chdir_guard(config.home / "pexports"): 471 | common.run_command("autoreconf -if") 472 | 473 | @staticmethod 474 | def python_embed(config: configure) -> None: 475 | """解压python embed package和python源代码,提取出dll和include文件,并合并到python-embed文件夹中 476 | 477 | Args: 478 | config (configure): 当前源代码下载配置 479 | """ 480 | 481 | python_version = extra_lib_version.python 482 | python_embed_zip = config.home / "python-embed.zip" 483 | python_source_txz = config.home / "python_source.tar.xz" 484 | python_source = config.home / "python_source" 485 | python_embed = config.home / "python-embed" 486 | # 删除已安装包 487 | common.remove_if_exists(python_embed) 488 | 489 | # 解压embed包 490 | common.run_command(f"unzip -o {python_embed_zip} python3*.dll python3*.zip *._pth -d {python_embed} -x python3.dll") 491 | common.remove(python_embed_zip) 492 | # 解压源代码包 493 | common.run_command(f"tar -xaf {python_source_txz}") 494 | common.rename(Path(f"Python-{python_version}"), python_source) 495 | common.remove(python_source_txz) 496 | 497 | # 复制头文件 498 | include_dir = python_embed / "include" 499 | common.copy(python_source / "Include", include_dir) 500 | common.copy(python_source / "PC" / "pyconfig.h.in", include_dir / "pyconfig.h") 501 | common.remove(python_source) 502 | 503 | # 记录python版本号 504 | python_version.save_version(python_embed) 505 | 506 | @staticmethod 507 | def loongnix(config: configure) -> None: 508 | """解压loongnix的linux和glibc源代码到linux-loongnix和glibc-loongnix文件夹下 509 | 510 | Args: 511 | config (configure): 当前源代码下载配置 512 | """ 513 | 514 | linux_tgz = config.home / "linux-loongnix.tar.gz" 515 | glibc_tzst = config.home / "glibc-loongnix.tar.zst" 516 | linux_dir = config.home / "linux-loongnix" 517 | glibc_dir = config.home / "glibc-loongnix" 518 | # 删除已安装包 519 | common.remove_if_exists(linux_dir) 520 | common.remove_if_exists(glibc_dir) 521 | # 解压linux 522 | common.mkdir(linux_dir) 523 | common.run_command(f"tar -xaf {linux_tgz} -C {linux_dir}") 524 | common.remove(linux_tgz) 525 | # 解压glibc 526 | common.mkdir(glibc_dir) 527 | common.run_command(f"tar -xaf {glibc_tzst} -C {glibc_dir}") 528 | common.remove(glibc_tzst) 529 | 530 | extra_lib_version.loongnix.save_version(linux_dir) 531 | 532 | @staticmethod 533 | def iconv(config: configure) -> None: 534 | """解压iconv包到gdb目录下 535 | 536 | Args: 537 | config (configure): 当前源代码下载配置 538 | """ 539 | 540 | iconv_version = extra_lib_version.iconv 541 | gdb_dir = config.home / "binutils" / "gdb" 542 | iconv_tgz = config.home / "iconv.tar.gz" 543 | iconv_dir = gdb_dir / "libiconv" 544 | # 删除已安装包 545 | common.remove_if_exists(iconv_dir) 546 | common.run_command(f"tar -xaf {iconv_tgz} -C {gdb_dir}") 547 | common.rename(gdb_dir / f"libiconv-{iconv_version}", iconv_dir) 548 | common.remove(iconv_tgz) 549 | iconv_version.save_version(iconv_dir) 550 | 551 | @staticmethod 552 | def gmp(config: configure) -> None: 553 | """解压gmp到home下 554 | 555 | Args: 556 | config (configure): 当前源代码下载配置 557 | """ 558 | 559 | gmp_version = extra_lib_version.gmp 560 | gmp_dir = config.home / "gmp" 561 | gmp_txz = config.home / "gmp.tar.xz" 562 | # 删除已安装包 563 | common.remove_if_exists(gmp_dir) 564 | common.run_command(f"tar -xaf {gmp_txz} -C {config.home}") 565 | common.rename(config.home / f"gmp-{gmp_version}", gmp_dir) 566 | common.remove(gmp_txz) 567 | gmp_version.save_version(gmp_dir) 568 | 569 | @staticmethod 570 | def mpfr(config: configure) -> None: 571 | """解压mpfr到home下 572 | 573 | Args: 574 | config (configure): 当前源代码下载配置 575 | """ 576 | 577 | mpfr_version = extra_lib_version.mpfr 578 | mpfr_dir = config.home / "mpfr" 579 | mpfr_txz = config.home / "mpfr.tar.xz" 580 | # 删除已安装包 581 | common.remove_if_exists(mpfr_dir) 582 | common.run_command(f"tar -xaf {mpfr_txz} -C {config.home}") 583 | common.rename(config.home / f"mpfr-{mpfr_version}", mpfr_dir) 584 | common.remove(mpfr_txz) 585 | mpfr_version.save_version(mpfr_dir) 586 | 587 | @staticmethod 588 | def after_download_specific_lib(config: configure, lib: str) -> None: 589 | """根据包名执行对应的回调函数 590 | 591 | Args: 592 | config (configure): 当前源代码下载配置 593 | lib (str): 下载好的包名 594 | """ 595 | 596 | lib = lib.replace("-", "_") 597 | if lib in vars(after_download_list) and not common.command_dry_run.get(): 598 | getattr(after_download_list, lib)(config) 599 | 600 | 601 | class extra_git_options_list: 602 | """克隆git包时要使用的额外git选项表""" 603 | 604 | @staticmethod 605 | def glibc(config: configure) -> list[str]: 606 | """获取glibc的额外克隆选项,需要设置指定的分支 607 | 608 | Args: 609 | config (configure): 当前源代码下载配置 610 | """ 611 | return [f"-b release/{config.glibc_version}/master"] 612 | 613 | @staticmethod 614 | def get_option(config: configure, lib: str) -> list[str]: 615 | """根据要克隆的git包名获取额外选项 616 | 617 | Args: 618 | config (configure): 当前源代码下载配置 619 | lib (str): git包名 620 | 621 | Returns: 622 | list[str]: 额外选项表 623 | """ 624 | if lib in vars(extra_git_options_list): 625 | return typing.cast(list[str], getattr(extra_git_options_list, lib)(config)) 626 | return [] 627 | 628 | 629 | __all__ = [ 630 | "extra_lib_version", 631 | "git_clone_type", 632 | "git_prefer_remote", 633 | "all_lib_list", 634 | "configure", 635 | "after_download_list", 636 | "extra_git_options_list", 637 | ] 638 | -------------------------------------------------------------------------------- /toolchains/gcc_environment.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Callable 2 | from pathlib import Path 3 | 4 | from . import common 5 | 6 | lib_list = ("expat", "gcc", "binutils", "gmp", "mpfr", "linux", "mingw", "pexports", "python-embed", "glibc", "newlib") 7 | 8 | # 带newlib的独立环境需禁用的特性列表 9 | disable_hosted_option = ( 10 | "--disable-threads", 11 | "--disable-libstdcxx-verbose", 12 | "--disable-shared", 13 | "--with-headers", 14 | "--disable-libsanitizer", 15 | "--disable-libssp", 16 | "--disable-libquadmath", 17 | "--disable-libgomp", 18 | "--with-newlib", 19 | ) 20 | # 无newlib的独立环境需禁用的特性列表 21 | disable_hosted_option_pure = ( 22 | "--disable-threads", 23 | "--disable-hosted-libstdcxx", 24 | "--disable-libstdcxx-verbose", 25 | "--disable-shared", 26 | "--without-headers", 27 | "--disable-libvtv", 28 | "--disable-libsanitizer", 29 | "--disable-libssp", 30 | "--disable-libquadmath", 31 | "--disable-libgomp", 32 | ) 33 | 34 | # 32位架构,其他32位架构需自行添加 35 | arch_32_bit_list = ("arm", "armeb", "i486", "i686", "risc32", "risc32be") 36 | 37 | 38 | def get_specific_environment(self: common.basic_environment, host: str | None = None, target: str | None = None) -> "gcc_environment": 39 | """在一个basic_environment的配置基础上获取指定配置的gcc环境 40 | 41 | Args: 42 | self (common.basic_environment): 基环境 43 | host (str | None, optional): 指定的host平台. 默认使用self中的配置. 44 | target (str | None, optional): 指定的target平台. 默认使用self中的配置. 45 | 46 | Returns: 47 | gcc_environment: 指定配置的gcc环境 48 | """ 49 | 50 | return gcc_environment( 51 | self.build, 52 | host, 53 | target, 54 | self.home, 55 | self.jobs, 56 | self.prefix_dir, 57 | self.compress_level, 58 | self.long_distance_match, 59 | self.build_tmp, 60 | True, 61 | ) 62 | 63 | 64 | class gcc_environment(common.basic_environment): 65 | """gcc构建环境""" 66 | 67 | build: str # build平台 68 | host: str # host平台 69 | target: str # target平台 70 | toolchain_type: "common.toolchain_type" # 工具链类别 71 | cross_compiler: bool # 是否是交叉编译器 72 | prefix: Path # 工具链安装位置 73 | lib_prefix: Path # 安装后库目录的前缀] 74 | share_dir: Path # 安装后share目录 75 | gdbinit_path: Path # 安装后.gdbinit文件所在路径 76 | lib_dir_list: dict[str, Path] # 所有库所在目录 77 | tool_prefix: str # 工具的前缀,如x86_64-w64-mingw32- 78 | python_config_path: Path # python_config.sh所在路径 79 | host_32_bit: bool # host平台是否是32位的 80 | target_32_bit: bool # target平台是否是32位的 81 | rpath_option: str # 设置rpath的链接选项 82 | rpath_dir: Path # rpath所在目录 83 | freestanding: bool # 是否为独立工具链 84 | host_field: common.triplet_field # host平台各个域 85 | target_field: common.triplet_field # target平台各个域 86 | 87 | def __init__( 88 | self, 89 | build: str, 90 | host: None | str, 91 | target: None | str, 92 | home: Path, 93 | jobs: int, 94 | prefix_dir: Path, 95 | compress_level: int, 96 | long_distance_match: int, 97 | build_tmp: Path, 98 | simple: bool = False, 99 | ) -> None: 100 | self.build = build 101 | self.host = host or build 102 | self.target = target or self.host 103 | # 鉴别工具链类别 104 | self.toolchain_type = common.toolchain_type.classify_toolchain(self.build, self.host, self.target) 105 | self.freestanding = self.toolchain_type.contain(common.toolchain_type.freestanding) 106 | self.cross_compiler = self.toolchain_type.contain(common.toolchain_type.cross | common.toolchain_type.canadian_cross) 107 | 108 | name_without_version = (f"{self.host}-host-{self.target}-target" if self.cross_compiler else f"{self.host}-native") + "-gcc" 109 | super().__init__(build, "16.0.0", name_without_version, home, jobs, prefix_dir, compress_level, long_distance_match, build_tmp) 110 | 111 | self.prefix = self.prefix_dir / self.name 112 | self.lib_prefix = self.prefix / self.target if not self.toolchain_type.contain(common.toolchain_type.canadian) else self.prefix 113 | self.share_dir = self.prefix / "share" 114 | self.gdbinit_path = self.share_dir / ".gdbinit" 115 | self.host_32_bit = self.host.startswith(arch_32_bit_list) 116 | self.target_32_bit = self.target.startswith(arch_32_bit_list) 117 | lib_name = f'lib{"32" if self.host_32_bit else "64"}' 118 | self.rpath_dir = self.prefix / lib_name 119 | lib_path = Path("'$ORIGIN'") / ".." / lib_name 120 | self.rpath_option = f"-Wl,-rpath={lib_path}" 121 | self.host_field = common.triplet_field(self.host, True) 122 | self.target_field = common.triplet_field(self.target, True) 123 | 124 | if simple: 125 | return 126 | 127 | common.mkdir(self.build_tmp, False) 128 | self.lib_dir_list = {} 129 | for lib in lib_list: 130 | lib_dir = self.home / lib 131 | match lib: 132 | # 支持使用厂商修改过的源代码 133 | case "glibc" | "linux" if self.target_field.vendor != "unknown": 134 | vendor = self.target_field.vendor 135 | custom_lib_dir = self.home / f"{lib}-{vendor}" 136 | if common.check_lib_dir(lib, custom_lib_dir, False): 137 | lib_dir = custom_lib_dir 138 | else: 139 | common.toolchains_print( 140 | common.toolchains_warning(f'Can\'t find custom lib "{lib}" in "{custom_lib_dir}", fallback to use common lib.') 141 | ) 142 | common.check_lib_dir(lib, lib_dir) 143 | case _: 144 | common.check_lib_dir(lib, lib_dir) 145 | self.lib_dir_list[lib] = lib_dir 146 | self.lib_dir_list["gdbserver"] = self.lib_dir_list["binutils"] 147 | self.tool_prefix = f"{self.target}-" if self.cross_compiler else "" 148 | 149 | self.python_config_path = self.root_dir.parent / "script" / "python_config.sh" 150 | # 加载工具链 151 | if self.toolchain_type.contain(common.toolchain_type.cross | common.toolchain_type.canadian | common.toolchain_type.canadian_cross): 152 | get_specific_environment(self).register_in_env() 153 | if self.toolchain_type.contain(common.toolchain_type.canadian | common.toolchain_type.canadian_cross): 154 | get_specific_environment(self, target=self.host).register_in_env() 155 | if self.toolchain_type.contain(common.toolchain_type.canadian_cross): 156 | get_specific_environment(self, target=self.target).register_in_env() 157 | # 将自身注册到环境变量中 158 | self.register_in_env() 159 | 160 | # 从LD_LIBRARY_PATH中过滤掉空路径 161 | ld_library_path = [*filter(lambda path: path != "", common.get_environ_list("LD_LIBRARY_PATH", True))] 162 | common.set_environ_list("LD_LIBRARY_PATH", ld_library_path) 163 | 164 | def enter_build_dir(self, lib: str, remove_if_exist: bool = False) -> None: 165 | """进入构建目录 166 | 167 | Args: 168 | lib (str): 要构建的库 169 | remove_if_exist(bool): 是否删除已经存在的目录 170 | """ 171 | 172 | assert lib in self.lib_dir_list 173 | need_make_build_dir = True # 是否需要建立build目录 174 | match lib: 175 | case "python-embed" | "linux": 176 | need_make_build_dir = False # 跳过python-embed和linux,python-embed仅需要生成静态库,linux有独立的编译方式 177 | build_dir = self.lib_dir_list[lib] 178 | case "glibc" | "mingw" | "newlib": 179 | build_dir = self.build_tmp / f"{self.target}-{lib}" 180 | case _: 181 | build_dir = self.build_tmp / f"{self.host}-host-{self.target}-target-{lib}" 182 | 183 | if need_make_build_dir: 184 | common.mkdir(build_dir, remove_if_exist) 185 | 186 | common.chdir(build_dir) 187 | # 添加构建gdb所需的环境变量 188 | if lib == "binutils": 189 | common.add_environ("ORIGIN", "$$ORIGIN") 190 | common.add_environ("PYTHON_EMBED_PACKAGE", self.lib_dir_list["python-embed"]) # mingw下编译带python支持的gdb需要 191 | 192 | def configure(self, lib: str, *option: str) -> None: 193 | """自动对库进行配置 194 | 195 | Args: 196 | lib (str): 要构建的库 197 | option (tuple[str, ...]): 配置选项 198 | """ 199 | 200 | options = " ".join(("", *option)) 201 | configure_prefix = self.lib_dir_list[lib] if lib != "expat" else self.lib_dir_list[lib] / "expat" 202 | common.run_command(f"{configure_prefix / 'configure'} {common.command_quiet.get_option()} {options}") 203 | 204 | def make(self, *target: str, ignore_error: bool = False) -> None: 205 | """自动对库进行编译 206 | 207 | Args: 208 | target (tuple[str, ...]): 要编译的目标 209 | """ 210 | 211 | targets = " ".join(("", *target)) 212 | common.run_command(f"make {common.command_quiet.get_option()} {targets} -j {self.jobs}", ignore_error) 213 | 214 | def install(self, *target: str, ignore_error: bool = False) -> None: 215 | """自动对库进行安装 216 | 217 | Args: 218 | target (tuple[str, ...]): 要安装的目标 219 | """ 220 | 221 | if target != (): 222 | targets = " ".join(("", *target)) 223 | else: 224 | targets = "install-strip" 225 | common.run_command(f"make {common.command_quiet.get_option()} {targets} -j {self.jobs}", ignore_error) 226 | 227 | def copy_gdbinit(self) -> None: 228 | """复制.gdbinit文件""" 229 | 230 | gdbinit_src_path = self.root_dir.parent / "script" / ".gdbinit" 231 | common.copy(gdbinit_src_path, self.gdbinit_path) 232 | 233 | def build_libpython(self) -> None: 234 | """创建libpython.a""" 235 | 236 | lib_dir = self.lib_dir_list["python-embed"] 237 | lib_path = lib_dir / "libpython.a" 238 | def_path = lib_dir / "libpython.def" 239 | if not lib_path.exists(): 240 | dll_list = list(filter(lambda dll: dll.name.startswith("python") and dll.name.endswith(".dll"), lib_dir.iterdir())) 241 | assert dll_list != [], common.toolchains_error(f'Cannot find python*.dll in "{lib_dir}" directory.') 242 | assert len(dll_list) == 1, common.toolchains_error(f'Find too many python*.dll in "{lib_dir}" directory.') 243 | dll_path = lib_dir / dll_list[0] 244 | # 工具链最后运行在宿主平台上,故而应该使用宿主平台的工具链从.lib文件制作.a文件 245 | common.run_command(f"{self.host}-pexports {dll_path} > {def_path}") 246 | common.run_command(f"{self.host}-dlltool -D {dll_path} -d {def_path} -l {lib_path}") 247 | 248 | def copy_python_embed_package(self) -> None: 249 | """复制python embed package到安装目录""" 250 | 251 | for file in filter(lambda x: x.name.startswith("python"), self.lib_dir_list["python-embed"].iterdir()): 252 | common.copy( 253 | file, 254 | self.bin_dir / file.name, 255 | ) 256 | 257 | def package(self, need_gdbinit: bool = True, need_python_embed_package: bool = False) -> None: 258 | """打包工具链 259 | 260 | Args: 261 | need_gdbinit (bool, optional): 是否需要打包.gdbinit文件. 默认需要. 262 | need_python_embed_package (bool, optional): 是否需要打包python embed package. 默认不需要. 263 | """ 264 | 265 | if self.toolchain_type.contain(common.toolchain_type.native): 266 | # 本地工具链需要添加cc以代替系统提供的cc 267 | common.symlink(Path("gcc"), self.bin_dir / "cc") 268 | if need_gdbinit: 269 | self.copy_gdbinit() 270 | if need_python_embed_package: 271 | self.copy_python_embed_package() 272 | self.compress() 273 | 274 | def remove_unused_glibc_file(self) -> None: 275 | """移除不需要的glibc文件""" 276 | 277 | for dir in ( 278 | "etc", 279 | "libexec", 280 | "sbin", 281 | "share", 282 | "var", 283 | "lib/gconv", 284 | "lib/audit", 285 | ): 286 | common.remove_if_exists(self.lib_prefix / dir) 287 | 288 | def strip_glibc_file(self) -> None: 289 | """剥离调试符号""" 290 | 291 | strip = f"{self.tool_prefix}strip" 292 | common.run_command(f"{strip} {self.lib_prefix / 'lib' / '*.so.*'}", True) 293 | 294 | def change_glibc_ldscript(self, arch: str | None = None) -> None: 295 | """替换带有绝对路径的链接器脚本 296 | 297 | Args: 298 | arch (str | None, optional): glibc链接器脚本的arch字段,若为None则从target中推导. 默认为 None. 299 | 手动设置arch可以用于需要额外字段来区分链接器脚本的情况 300 | """ 301 | 302 | arch = arch or self.target_field.arch 303 | dst_dir = self.lib_prefix / "lib" 304 | for file in filter(lambda file: file.name.startswith(f"{arch}-lib"), self.script_dir.iterdir()): 305 | if file.suffix == ".py": 306 | with common.dynamic_import_module(file) as module: 307 | generate_ldscript: Callable[[Path], None] = common.dynamic_import_function("main", module) 308 | generate_ldscript(dst_dir) 309 | else: 310 | common.copy(file, dst_dir / file.name[len(f"{arch}-") :]) 311 | 312 | def adjust_glibc(self, arch: str | None = None) -> None: 313 | """调整glibc 314 | Args: 315 | arch (str | None, optional): glibc链接器脚本的arch字段,若为None则自动推导. 默认为 None. 316 | """ 317 | 318 | self.remove_unused_glibc_file() 319 | self.strip_glibc_file() 320 | self.change_glibc_ldscript(arch) 321 | symlink_path = self.lib_prefix / "lib" / "libmvec_nonshared.a" 322 | if not symlink_path.exists(): 323 | common.symlink_if_exist(Path("libmvec.a"), symlink_path) 324 | 325 | def solve_libgcc_limits(self) -> None: 326 | """解决libgcc的limits.h中提供错误MB_LEN_MAX的问题""" 327 | 328 | libgcc_prefix = self.prefix / "lib" / "gcc" / self.target 329 | include_path = next(libgcc_prefix.iterdir()) / "include" / "limits.h" 330 | with include_path.open("a") as file: 331 | file.write("#undef MB_LEN_MAX\n" "#define MB_LEN_MAX 16\n") 332 | 333 | def copy_from_other_toolchain(self, need_gdbserver: bool) -> bool: 334 | """从交叉工具链或本地工具链中复制libc、libstdc++、libgcc、linux头文件、gdbserver等到本工具链中 335 | 336 | Args: 337 | need_gdbserver (bool): 是否需要复制gdbserver 338 | 339 | Returns: 340 | bool: gdbserver是否成功复制 341 | """ 342 | 343 | # 复制libc、libstdc++、linux头文件等到本工具链中 344 | toolchain = get_specific_environment(self, target=self.target) 345 | for dir in filter(lambda x: x.name != "bin", toolchain.lib_prefix.iterdir()): 346 | common.copy(dir, self.lib_prefix / dir.name) 347 | 348 | # 复制libgcc到本工具链中 349 | common.copy(toolchain.prefix / "lib" / "gcc", self.prefix / "lib" / "gcc") 350 | 351 | # 复制gdbserver 352 | if need_gdbserver: 353 | gdbserver = "gdbserver" if self.target_field.os == "linux" else "gdbserver.exe" 354 | return bool(common.copy_if_exist(toolchain.bin_dir / gdbserver, self.bin_dir / gdbserver)) 355 | else: 356 | return False 357 | 358 | 359 | def get_mingw_lib_prefix_list(env: gcc_environment) -> dict[str, Path]: 360 | """获取mingw平台下gdb所需包的安装路径 361 | 362 | Args: 363 | env (gcc_environment): gcc环境 364 | 365 | Returns: 366 | dict[str,Path]: {包名:安装路径} 367 | """ 368 | 369 | return {lib: env.build_tmp / f"{env.host}-{lib}-install" for lib in ("gmp", "expat", "mpfr")} 370 | 371 | 372 | def build_mingw_gdb_requirements(env: gcc_environment) -> None: 373 | """编译安装libgmp, libexpat, libmpfr""" 374 | 375 | lib_prefix_list = get_mingw_lib_prefix_list(env) 376 | for lib, prefix in lib_prefix_list.items(): 377 | host_file = prefix / ".host" 378 | try: 379 | host = host_file.read_text() 380 | except: 381 | host = "" 382 | if host == env.host: 383 | continue # 已经存在则跳过构建 384 | env.enter_build_dir(lib) 385 | env.configure( 386 | lib, 387 | f"--host={env.host} --disable-shared --enable-static", 388 | f"--prefix={prefix}", 389 | f"--with-gmp={lib_prefix_list['gmp']}" if lib == "mpfr" else "", 390 | 'CFLAGS="-O3 -std=c11"', 391 | 'CXXFLAGS="-O3"', 392 | ) 393 | env.make() 394 | env.install() 395 | host_file.write_text(env.host) 396 | 397 | 398 | def get_mingw_gdb_lib_options(env: gcc_environment) -> list[str]: 399 | """获取mingw平台下gdb所需包配置选项 400 | 401 | Args: 402 | env (gcc_environment): gcc环境 403 | """ 404 | 405 | lib_prefix_list = get_mingw_lib_prefix_list(env) 406 | prefix_selector: Callable[[str], str] = lambda lib: f"--with-{lib}=" if lib in ("gmp", "mpfr") else f"--with-lib{lib}-prefix=" 407 | return [prefix_selector(lib) + f"{lib_prefix_list[lib]}" for lib in ("gmp", "mpfr", "expat")] 408 | 409 | 410 | def copy_pretty_printer(env: gcc_environment) -> None: 411 | """从x86_64-linux-gnu本地工具链中复制pretty-printer到不带newlib的独立工具链""" 412 | 413 | native_gcc = get_specific_environment(env) 414 | for src_dir in native_gcc.share_dir.iterdir(): 415 | if src_dir.name.startswith("gcc") and src_dir.is_dir(): 416 | common.copy(src_dir, env.share_dir / src_dir.name) 417 | return 418 | 419 | 420 | class build_gcc_environment: 421 | """gcc工具链构建环境""" 422 | 423 | env: gcc_environment # gcc构建环境 424 | host_os: str # gcc环境的host操作系统 425 | target_os: str # gcc环境的target操作系统 426 | target_arch: str # gcc环境的target架构 427 | basic_option: list[str] # 基本选项 428 | libc_option: list[str] # libc相关选项 429 | gcc_option: list[str] # gcc相关选项 430 | gdb_option: list[str] # gdb相关选项 431 | linux_option: list[str] # linux相关选项 432 | gdbserver_option: list[str] # gdbserver相关选项 433 | full_build: bool # 是否进行完整自举流程 434 | glibc_phony_stubs_path: Path # glibc占位文件所在路径 435 | adjust_glibc_arch: str # 调整glibc链接器脚本时使用的架构名 436 | need_gdb: bool # 是否需要编译gdb 437 | need_gdbserver: bool # 是否需要编译gdbserver 438 | need_newlib: bool # 是否需要编译newlib,仅对独立工具链有效 439 | native_or_canadian = common.toolchain_type.native | common.toolchain_type.canadian # host == target 440 | 441 | def __init__( 442 | self, 443 | build: str, 444 | host: str, 445 | target: str, 446 | gdb: bool, 447 | gdbserver: bool, 448 | newlib: bool, 449 | home: Path, 450 | jobs: int, 451 | prefix_dir: Path, 452 | nls: bool, 453 | compress_level: int, 454 | long_distance_match: int, 455 | build_tmp: Path, 456 | ) -> None: 457 | """gcc交叉工具链对象 458 | 459 | Args: 460 | build (str): 构建平台 461 | host (str): 宿主平台 462 | target (str): 目标平台 463 | gdb (bool): 是否启用gdb 464 | gdbserver (bool): 是否启用gdbserver 465 | newlib (bool): 是否启用newlib, 仅对独立工具链有效 466 | home (Path): 源代码树搜索主目录 467 | jobs (int): 并发构建数 468 | prefix_dir (Path): 安装根目录 469 | nls (bool): 是否启用nls 470 | compress_level (int): zstd压缩等级 471 | long_distance_match (int): 长距离匹配窗口大小 472 | build_tmp (Path): 构建工具链时存放临时文件的路径 473 | """ 474 | 475 | self.env = gcc_environment(build, host, target, home, jobs, prefix_dir, compress_level, long_distance_match, build_tmp) 476 | self.host_os = self.env.host_field.os 477 | self.target_os = self.env.target_field.os 478 | self.target_arch = self.env.target_field.arch 479 | self.basic_option = [ 480 | "--disable-werror", 481 | " --enable-nls" if nls else "--disable-nls", 482 | f"--build={self.env.build}", 483 | f"--target={self.env.target}", 484 | f"--prefix={self.env.prefix}", 485 | f"--host={self.env.host}", 486 | "CFLAGS=-O3", 487 | "CXXFLAGS=-O3", 488 | ] 489 | self.need_gdb, self.need_gdbserver, self.need_newlib = gdb, gdbserver, newlib 490 | assert not self.env.freestanding or not self.need_gdbserver, common.toolchains_error( 491 | "Cannot build gdbserver for freestanding platform.\n" "You should use other server implementing the gdb protocol like OpenOCD." 492 | ) 493 | 494 | libc_option_list = { 495 | "linux": [f"--prefix={self.env.lib_prefix}", f"--host={self.env.target}", f"--build={self.env.build}", "--disable-werror"], 496 | "w64": [ 497 | f"--host={self.env.target}", 498 | f"--prefix={self.env.lib_prefix}", 499 | "--with-default-msvcrt=ucrt", 500 | "--disable-werror", 501 | ], 502 | # newlib会自动设置安装路径的子目录 503 | "unknown": [f"--prefix={self.env.prefix}", f"--target={self.env.target}", f"--build={self.env.build}", "--disable-werror"], 504 | } 505 | self.libc_option = libc_option_list[self.target_os] 506 | 507 | gcc_option_list = { 508 | "linux": ["--disable-bootstrap"], 509 | "w64": ["--disable-sjlj-exceptions", "--enable-threads=win32"], 510 | "unknown": [*disable_hosted_option] if self.need_newlib else [*disable_hosted_option_pure], 511 | } 512 | self.gcc_option = [ 513 | *gcc_option_list[self.target_os], 514 | "--enable-languages=c,c++", 515 | "--disable-multilib", 516 | ] 517 | 518 | w64_gdbsupport_option = 'CXXFLAGS="-O3 -D_WIN32_WINNT=0x0600"' 519 | gdb_option_list = { 520 | "linux": [f'LDFLAGS="{self.env.rpath_option}"', "--with-python=/usr/bin/python3"], 521 | "w64": [ 522 | f"--with-python={self.env.python_config_path}", 523 | w64_gdbsupport_option, 524 | "--with-expat", 525 | *get_mingw_gdb_lib_options(self.env), 526 | ], 527 | } 528 | enable_gdbserver_when_build_gdb = gdbserver and self.env.toolchain_type.contain(self.native_or_canadian) 529 | gdbserver_option = "--enable-gdbserver" if enable_gdbserver_when_build_gdb else "--disable-gdbserver" 530 | self.gdb_option = ( 531 | [ 532 | *gdb_option_list[self.host_os], 533 | f"--with-system-gdbinit={self.env.gdbinit_path}", 534 | gdbserver_option, 535 | "--enable-gdb", 536 | "--disable-unit-tests", 537 | ] 538 | if gdb 539 | else [gdbserver_option, "--disable-gdb"] 540 | ) 541 | # 创建libpython.a 542 | if gdb and self.host_os == "w64": 543 | self.env.build_libpython() 544 | 545 | linux_arch_list = { 546 | "i686": "x86", 547 | "x86_64": "x86", 548 | "arm": "arm", 549 | "aarch64": "arm64", 550 | "loongarch64": "loongarch", 551 | "riscv64": "riscv", 552 | "mips64el": "mips", 553 | } 554 | self.linux_option = [f"ARCH={linux_arch_list[self.target_arch]}", f"INSTALL_HDR_PATH={self.env.lib_prefix}", "headers_install"] 555 | 556 | self.gdbserver_option = ["--disable-gdb", f"--host={self.env.target}", "--enable-gdbserver", "--disable-binutils"] 557 | if self.target_os == "w64": 558 | self.gdbserver_option.append(w64_gdbsupport_option) 559 | 560 | # 本地工具链和交叉工具链需要完整编译 561 | self.full_build = self.env.toolchain_type.contain(common.toolchain_type.native | common.toolchain_type.cross) 562 | # 编译不完整libgcc时所需的stubs.h所在路径 563 | self.glibc_phony_stubs_path = self.env.lib_prefix / "include" / "gnu" / "stubs.h" 564 | # 由相关函数自动推动架构名 565 | self.adjust_glibc_arch = "" 566 | 567 | def after_build_gcc(self, skip_gdbserver: bool = False) -> None: 568 | """在编译完gcc后完成收尾工作 569 | 570 | Args: 571 | skip_gdbserver (bool, optional): 跳过gdbserver构建. 默认为False. 572 | """ 573 | 574 | self.need_gdbserver = self.need_gdbserver and not skip_gdbserver 575 | # 从完整工具链复制文件 576 | if not self.full_build: 577 | copy_success = self.env.copy_from_other_toolchain(self.need_gdbserver) 578 | self.need_gdbserver = self.need_gdbserver and not copy_success 579 | if self.need_gdb and self.env.freestanding and not self.need_newlib: 580 | copy_pretty_printer(self.env) 581 | 582 | # 编译gdbserver 583 | if self.need_gdbserver: 584 | self.env.solve_libgcc_limits() 585 | self.env.enter_build_dir("gdbserver", True) 586 | self.env.configure("gdbserver", *self.basic_option, *self.gdbserver_option) 587 | self.env.make() 588 | self.env.install("install-strip-gdbserver") 589 | 590 | # 复制gdb所需运行库 591 | if self.need_gdb and not self.env.toolchain_type.contain(self.native_or_canadian): 592 | gcc = get_specific_environment(self.env, target=self.env.host) 593 | if self.host_os == "linux": 594 | for dll in ("libstdc++.so.6", "libgcc_s.so.1"): 595 | common.copy(gcc.rpath_dir / dll, self.env.rpath_dir / dll, follow_symlinks=True) 596 | else: 597 | for dll in ("libstdc++-6.dll", "libgcc_s_seh-1.dll"): 598 | common.copy(gcc.lib_prefix / "lib" / dll, self.env.bin_dir / dll) 599 | 600 | # 打包工具链 601 | self.env.package(self.need_gdb, self.need_gdb and self.host_os == "w64") 602 | 603 | @staticmethod 604 | def native_build_linux(build_env: "build_gcc_environment") -> None: 605 | """编译linux本地工具链 606 | 607 | Args: 608 | build_env (build_environment): gcc构建环境 609 | """ 610 | 611 | env = build_env.env 612 | # 编译gcc 613 | env.enter_build_dir("gcc") 614 | env.configure("gcc", *build_env.basic_option, *build_env.gcc_option) 615 | env.make() 616 | env.install() 617 | 618 | # 安装Linux头文件 619 | env.enter_build_dir("linux") 620 | env.make(*build_env.linux_option) 621 | 622 | # 编译安装glibc 623 | env.enter_build_dir("glibc") 624 | env.configure("glibc", *build_env.libc_option) 625 | env.make() 626 | env.install("install") 627 | env.adjust_glibc(build_env.adjust_glibc_arch) 628 | 629 | # 编译binutils,如果启用gdb和gdbserver则一并编译 630 | env.enter_build_dir("binutils") 631 | env.configure("binutils", *build_env.basic_option, *build_env.gdb_option) 632 | env.make() 633 | env.install() 634 | # 完成后续工作 635 | build_env.after_build_gcc(True) 636 | 637 | @staticmethod 638 | def make_with_libbacktrace_patch(env: gcc_environment, target: str | None = None) -> None: 639 | """在构建时修正libbacktrace构建流程 640 | 641 | gcc会删除构建完成的libbacktrace,然后只将stat.o打包为libbacktrace.a,进而导致链接错误 642 | 该函数首先尝试make,构建失败后重新编译libbacktrace,然后继续make流程 643 | 644 | Args: 645 | env (gcc_environment): gcc构建环境 646 | target (str | None, optional): 传递给env.make函数的目标,为None表示调用env.make(). 默认为None. 647 | """ 648 | 649 | make: Callable[[], None] = lambda: env.make(*([target] if target is not None else [])) 650 | try: 651 | make() 652 | except: 653 | with common.chdir_guard(Path("libbacktrace")): 654 | env.make("clean") 655 | env.make() 656 | make() 657 | 658 | @staticmethod 659 | def full_build_linux(build_env: "build_gcc_environment") -> None: 660 | """完整自举target为linux的gcc 661 | 662 | Args: 663 | build_env (build_environment): gcc构建环境 664 | """ 665 | 666 | env = build_env.env 667 | # 编译binutils,如果启用gdb则一并编译 668 | env.enter_build_dir(lib="binutils") 669 | env.configure("binutils", *build_env.basic_option, *build_env.gdb_option) 670 | env.make() 671 | env.install() 672 | 673 | # 编译gcc 674 | env.enter_build_dir("gcc") 675 | env.configure("gcc", *build_env.basic_option, *build_env.gcc_option, "--disable-shared", "--disable-gcov") 676 | build_gcc_environment.make_with_libbacktrace_patch(env, "all-gcc") 677 | env.install("install-strip-gcc") 678 | 679 | # 安装Linux头文件 680 | env.enter_build_dir("linux") 681 | env.make(*build_env.linux_option) 682 | 683 | # 安装glibc头文件 684 | env.enter_build_dir("glibc") 685 | env.configure("glibc", *build_env.libc_option, "libc_cv_forced_unwind=yes") 686 | env.make("install-headers") 687 | # 为了跨平台,不能使用mknod 688 | with build_env.glibc_phony_stubs_path.open("w"): 689 | pass 690 | 691 | # 编译安装libgcc 692 | env.enter_build_dir("gcc") 693 | env.make("all-target-libgcc") 694 | env.install("install-target-libgcc") 695 | 696 | # 编译安装glibc 697 | env.enter_build_dir("glibc") 698 | env.configure("glibc", *build_env.libc_option) 699 | env.make() 700 | env.install("install") 701 | env.adjust_glibc(build_env.adjust_glibc_arch) 702 | 703 | # 编译完整gcc 704 | env.enter_build_dir("gcc") 705 | env.configure("gcc", *build_env.basic_option, *build_env.gcc_option) 706 | build_gcc_environment.make_with_libbacktrace_patch(env) 707 | env.install() 708 | 709 | # 完成后续工作 710 | build_env.after_build_gcc() 711 | 712 | def build_pexports(self) -> None: 713 | # 编译pexports 714 | self.env.enter_build_dir("pexports") 715 | self.env.configure( 716 | "pexports", 717 | f"--prefix={self.env.prefix} --host={self.env.host}", 718 | "CFLAGS=-O3", 719 | "CXXFLAGS=-O3", 720 | ) 721 | self.env.make() 722 | self.env.install() 723 | # 为交叉工具链添加target前缀 724 | if not self.env.toolchain_type.contain(self.native_or_canadian): 725 | pexports = "pexports.exe" if self.host_os == "w64" else "pexports" 726 | common.rename(self.env.bin_dir / pexports, self.env.bin_dir / f"{self.env.target}-{pexports}") 727 | 728 | @staticmethod 729 | def full_build_mingw(build_env: "build_gcc_environment") -> None: 730 | """完整自举target为mingw的gcc 731 | 732 | Args: 733 | build_env (build_environment): gcc构建环境 734 | """ 735 | 736 | env = build_env.env 737 | # 编译binutils,如果启用gdb则一并编译 738 | env.enter_build_dir("binutils") 739 | env.configure("binutils", *build_env.basic_option, *build_env.gdb_option) 740 | env.make() 741 | env.install() 742 | 743 | # 编译安装mingw-w64头文件 744 | env.enter_build_dir("mingw") 745 | env.configure("mingw", *build_env.libc_option, "--without-crt") 746 | env.make() 747 | env.install() 748 | 749 | # 编译gcc和libgcc 750 | env.enter_build_dir("gcc") 751 | env.configure("gcc", *build_env.basic_option, *build_env.gcc_option, "--disable-shared") 752 | build_gcc_environment.make_with_libbacktrace_patch(env, "all-gcc all-target-libgcc") 753 | env.install("install-strip-gcc install-target-libgcc") 754 | 755 | # 编译完整mingw-w64 756 | env.enter_build_dir("mingw") 757 | env.configure("mingw", *build_env.libc_option) 758 | env.make() 759 | env.install() 760 | 761 | # 编译完整的gcc 762 | env.enter_build_dir("gcc") 763 | env.configure("gcc", *build_env.basic_option, *build_env.gcc_option) 764 | build_gcc_environment.make_with_libbacktrace_patch(env) 765 | env.install() 766 | 767 | build_env.build_pexports() 768 | # 完成后续工作 769 | build_env.after_build_gcc() 770 | 771 | @staticmethod 772 | def full_build_freestanding(build_env: "build_gcc_environment") -> None: 773 | """完整自举target为独立平台的gcc 774 | 775 | Args: 776 | build_env (build_environment): gcc构建环境 777 | """ 778 | 779 | env = build_env.env 780 | # 编译binutils,如果启用gdb则一并编译 781 | env.enter_build_dir("binutils") 782 | env.configure("binutils", *build_env.basic_option, *build_env.gdb_option) 783 | env.make() 784 | env.install() 785 | 786 | if build_env.need_newlib: 787 | # 编译安装gcc 788 | env.enter_build_dir("gcc") 789 | env.configure("gcc", *build_env.basic_option, *build_env.gcc_option) 790 | env.make("all-gcc") 791 | env.install("install-strip-gcc") 792 | 793 | # 编译安装newlib 794 | env.enter_build_dir("newlib") 795 | env.configure("newlib", *build_env.libc_option) 796 | env.make() 797 | env.install() 798 | 799 | # 编译安装完整gcc 800 | env.enter_build_dir("gcc") 801 | env.make() 802 | env.install() 803 | else: 804 | # 编译安装完整gcc 805 | env.enter_build_dir("gcc") 806 | env.configure("gcc", *build_env.basic_option, *build_env.gcc_option) 807 | env.make() 808 | env.install("install-strip") 809 | 810 | # 完成后续工作 811 | build_env.after_build_gcc() 812 | 813 | @staticmethod 814 | def partial_build(build_env: "build_gcc_environment") -> None: 815 | """编译gcc而无需自举 816 | 817 | Args: 818 | build_env (build_environment): gcc构建环境 819 | """ 820 | 821 | env = build_env.env 822 | # 编译binutils,如果启用gdb则一并编译 823 | env.enter_build_dir("binutils") 824 | env.configure("binutils", *build_env.basic_option, *build_env.gdb_option) 825 | env.make() 826 | env.install() 827 | 828 | # 编译安装gcc 829 | env.enter_build_dir("gcc") 830 | env.configure("gcc", *build_env.basic_option, *build_env.gcc_option) 831 | build_gcc_environment.make_with_libbacktrace_patch(env, "all-gcc") 832 | env.install("install-strip-gcc") 833 | 834 | # 有需要则编译安装pexports 835 | if build_env.target_os == "w64": 836 | build_env.build_pexports() 837 | 838 | # 完成后续工作 839 | build_env.after_build_gcc() 840 | 841 | def build(self) -> None: 842 | """构建gcc工具链""" 843 | 844 | # 编译gdb依赖库 845 | if self.need_gdb and self.host_os == "w64": 846 | build_mingw_gdb_requirements(self.env) 847 | if self.env.toolchain_type.contain(common.toolchain_type.native): 848 | self.native_build_linux(self) 849 | elif self.full_build: 850 | assert self.target_os in ("linux", "w64", "unknown"), common.toolchains_error( 851 | f"Unknown os: {self.target_os}.", common.message_type.toolchain_internal 852 | ) 853 | match (self.target_os): 854 | case "linux": 855 | self.full_build_linux(self) 856 | case "w64": 857 | self.full_build_mingw(self) 858 | case "unknown": 859 | self.full_build_freestanding(self) 860 | else: 861 | self.partial_build(self) 862 | 863 | 864 | assert __name__ != "__main__", "Import this file instead of running it directly." 865 | -------------------------------------------------------------------------------- /toolchains/llvm_environment.py: -------------------------------------------------------------------------------- 1 | import typing 2 | from collections.abc import Callable 3 | from copy import deepcopy 4 | from enum import StrEnum 5 | from pathlib import Path 6 | 7 | from . import common 8 | from .gcc_environment import get_specific_environment 9 | 10 | lib_list = ("zlib", "libxml2") 11 | subproject_list = ("llvm", "runtimes") 12 | 13 | 14 | def get_cmake_option(options: dict[str, str]) -> list[str]: 15 | """将字典转化为cmake选项列表 16 | 17 | Returns: 18 | list[str]: cmake选项列表 19 | """ 20 | 21 | option_list: list[str] = [] 22 | for key, value in options.items(): 23 | option_list.append(f"-D{key}={value}") 24 | return option_list 25 | 26 | 27 | def gnu_to_llvm(target: str) -> str: 28 | """将gnu风格triplet转化为llvm风格 29 | 30 | Args: 31 | target (str): 目标平台 32 | 33 | Returns: 34 | str: llvm风格triplet 35 | """ 36 | 37 | triplet_filed = common.triplet_field(target, False) 38 | if triplet_filed.os == "w64": 39 | triplet_filed.os = "windows" 40 | if triplet_filed.abi == "mingw32": 41 | triplet_filed.abi = "gnu" 42 | return str(triplet_filed) 43 | 44 | 45 | class runtime_family(StrEnum): 46 | """运行库类型 47 | 48 | Attributes: 49 | llvm: LLVM系运行库 50 | gnu : GCC系运行库 51 | """ 52 | 53 | llvm = "llvm" 54 | gnu = "gnu" 55 | 56 | 57 | class cmake_generator(StrEnum): 58 | """cmake生成工具 59 | 60 | Attributes: 61 | make: Linux默认的生成工具 62 | ninja : ninja可以提供更快的生成速度 63 | """ 64 | 65 | make = "make" 66 | ninja = "ninja" 67 | 68 | def get_cmake_option(self) -> str: 69 | """获取cmake生成器选项中的名称 70 | 71 | Returns: 72 | str: 生成器名称 73 | """ 74 | 75 | option_map = {"make": '"Unix Makefiles"', "ninja": "Ninja"} 76 | return option_map[self] 77 | 78 | 79 | class build_options: 80 | basic_option: list[str] 81 | cmake_option: dict[str, str] 82 | system_name: str 83 | 84 | def __init__(self, basic_option: list[str], cmake_option: dict[str, str], system_name: str) -> None: 85 | """构建相关选项 86 | 87 | Args: 88 | basic_option (list[str]): 传递给工具链的基本选项 89 | cmake_option (dict[str, str]): 传递给cmake的configure选项 90 | system_name (str): CMAKE_SYSTEM_NAME选项 91 | """ 92 | 93 | self.basic_option = basic_option 94 | self.cmake_option = cmake_option 95 | self.system_name = system_name 96 | 97 | def __repr__(self) -> str: 98 | return vars(self).__repr__() 99 | 100 | 101 | class llvm_environment(common.basic_environment): 102 | host: str # host平台 103 | family: runtime_family # 使用的运行库类型 104 | prefix: dict[str, Path] = {} # 工具链安装位置 105 | lib_dir_list: dict[str, Path] # 所有库所在目录 106 | source_dir: dict[str, Path] = {} # 源代码所在目录 107 | build_dir: dict[str, Path] = {} # 构建时所在目录 108 | compiler_list = ("C", "CXX", "ASM") # 编译器列表 109 | sysroot_dir: dict[str, Path] # sysroot所在路径 110 | generator_list: dict[str, cmake_generator] # 构建指定目标平台的工具时使用的生成器 111 | llvm_build_options: build_options # llvm构建选项 112 | runtime_build_options: dict[str, build_options] # 运行库构建选项 113 | dylib_option_list: typing.Final[dict[str, str]] = { # llvm动态链接选项 114 | "LLVM_LINK_LLVM_DYLIB": "ON", 115 | "LLVM_BUILD_LLVM_DYLIB": "ON", 116 | "CLANG_LINK_CLANG_DYLIB": "ON", 117 | } 118 | # 如果符号过多则Windows下需要改用该选项 119 | # dylib_option_list_windows: dict[str, str] = {"BUILD_SHARED_LIBS": "ON"} 120 | llvm_option_list: typing.Final[dict[str, str]] = { # 第1阶段编译选项,同时构建工具链和运行库 121 | "CMAKE_BUILD_TYPE": "Release", # 设置构建类型 122 | "LLVM_BUILD_DOCS": "OFF", # 禁用llvm文档构建 123 | "LLVM_BUILD_EXAMPLES": "OFF", # 禁用llvm示例构建 124 | "LLVM_INCLUDE_BENCHMARKS": "OFF", # 禁用llvm基准测试构建 125 | "LLVM_INCLUDE_EXAMPLES": "OFF", # llvm不包含示例 126 | "LLVM_INCLUDE_TESTS": "OFF", # llvm不包含单元测试 127 | "LLVM_TARGETS_TO_BUILD": '"X86;AArch64;RISCV;ARM;LoongArch;Mips"', # 设置需要构建的目标 128 | "LLVM_ENABLE_PROJECTS": '"clang;clang-tools-extra;lld;lldb;bolt"', # 设置一同构建的子项目 129 | "LLVM_ENABLE_RUNTIMES": '"libcxx;libcxxabi;libunwind;compiler-rt;openmp"', # 设置一同构建的运行时项目 130 | "LLVM_ENABLE_WARNINGS": "OFF", # 禁用警告 131 | "LLVM_ENABLE_LTO": "Thin", # 启用lto 132 | "LLVM_INCLUDE_TESTS": "OFF", # llvm不包含单元测试 133 | "CLANG_INCLUDE_TESTS": "OFF", # clang不包含单元测试 134 | "LLVM_INCLUDE_BENCHMARKS": "OFF", # llvm不包含基准测试 135 | "CLANG_DEFAULT_LINKER": "lld", # 使用lld作为clang默认的链接器 136 | "LLVM_ENABLE_LLD": "ON", # 使用lld链接llvm以加速链接 137 | "CMAKE_BUILD_WITH_INSTALL_RPATH": "ON", # 在linux系统上设置rpath以避免动态库环境混乱 138 | "LIBCXX_INCLUDE_BENCHMARKS": "OFF", # libcxx不包含测试 139 | "LIBCXX_USE_COMPILER_RT": "ON", # 使用compiler-rt构建libcxx 140 | "LIBCXX_CXX_ABI": "libcxxabi", # 使用libcxxabi构建libcxx 141 | "LIBCXXABI_USE_LLVM_UNWINDER": "ON", # 使用libunwind构建libcxxabi 142 | "LIBCXXABI_USE_COMPILER_RT": "ON", # 使用compiler-rt构建libcxxabi 143 | "COMPILER_RT_DEFAULT_TARGET_ONLY": "ON", # compiler-rt只需构建默认目标即可,禁止自动构建multilib 144 | "COMPILER_RT_CXX_LIBRARY": "libcxx", # 使用libcxx构建compiler-rt 145 | "LIBCXXABI_ENABLE_ASSERTIONS": "OFF", # 禁用断言 146 | "LIBCXXABI_INCLUDE_TESTS": "OFF", # 禁用libcxxabi测试 147 | "LLDB_INCLUDE_TESTS": "OFF", # 禁用lldb测试 148 | "LLDB_ENABLE_PYTHON": "ON", # 启用python支持 149 | "LIBOMP_OMPD_GDB_SUPPORT": "OFF", # 禁用openmpd的gdb支持,该支持需要python,而交叉编译时无法提供 150 | } 151 | lib_option: typing.Final[dict[str, str]] = { # llvm依赖库编译选项 152 | "BUILD_SHARED_LIBS": "OFF", 153 | "LIBXML2_WITH_ICONV": "OFF", 154 | "LIBXML2_WITH_LZMA": "OFF", 155 | "LIBXML2_WITH_PYTHON": "OFF", 156 | "LIBXML2_WITH_ZLIB": "OFF", 157 | "LIBXML2_WITH_THREADS": "OFF", 158 | "LIBXML2_WITH_CATALOG": "OFF", 159 | "LIBXML2_WITH_TESTS": "OFF", 160 | "CMAKE_RC_COMPILER": "llvm-windres", 161 | "CMAKE_BUILD_WITH_INSTALL_RPATH": "ON", 162 | } 163 | win32_options: typing.Final[dict[str, str]] = { 164 | "LIBCXXABI_ENABLE_THREADS": "ON", 165 | "LIBCXXABI_HAS_WIN32_THREAD_API": "ON", 166 | "LIBCXXABI_ENABLE_SHARED": "OFF", 167 | "LIBCXX_ENABLE_STATIC_ABI_LIBRARY": "ON", 168 | "LIBCXX_ENABLE_THREADS": "ON", 169 | "LIBCXX_HAS_WIN32_THREAD_API": "ON", 170 | "CMAKE_RC_COMPILER": "llvm-windres", 171 | "LLVM_ENABLE_RUNTIMES": '"libcxx;libcxxabi;libunwind;compiler-rt"', # 交叉编译下没有ml编译不了openmp 172 | } 173 | freestanding_option: typing.Final[dict[str, str]] = { 174 | "LIBUNWIND_IS_BAREMETAL": "ON", 175 | "LIBUNWIND_ENABLE_SHARED": "OFF", 176 | "LIBUNWIND_ENABLE_THREADS": "OFF", 177 | "COMPILER_RT_BUILD_SANITIZERS": "OFF", 178 | "COMPILER_RT_BUILD_XRAY": "OFF", 179 | "COMPILER_RT_BUILD_PROFILE": "OFF", 180 | "COMPILER_RT_BUILD_CTX_PROFILE": "OFF", 181 | "COMPILER_RT_BUILD_MEMPROF": "OFF", 182 | "COMPILER_RT_BUILD_GWP_ASAN": "OFF", 183 | "COMPILER_RT_BAREMETAL_BUILD": "ON", 184 | "LIBCXXABI_ENABLE_THREADS": "OFF", 185 | "LIBCXXABI_ENABLE_SHARED": "OFF", 186 | "LIBCXXABI_ENABLE_EXCEPTIONS": "OFF", 187 | "LIBCXXABI_BAREMETAL": "ON", 188 | "LLVM_ENABLE_RUNTIMES": '"libcxx;libcxxabi;libunwind;compiler-rt"', 189 | "CMAKE_TRY_COMPILE_TARGET_TYPE": "STATIC_LIBRARY", 190 | "BUILD_SHARED_LIBS": "OFF", 191 | "LIBCXX_ENABLE_SHARED": "OFF", 192 | "LIBCXX_ENABLE_THREADS": "OFF", 193 | "LIBCXX_ENABLE_MONOTONIC_CLOCK": "OFF", 194 | "LIBCXX_ENABLE_FILESYSTEM": "OFF", 195 | "LIBCXX_ENABLE_EXCEPTIONS": "OFF", 196 | "LIBCXX_ENABLE_RTTI": "OFF", 197 | "LIBCXX_ENABLE_LOCALIZATION": "OFF", 198 | "LIBCXX_ENABLE_RANDOM_DEVICE": "OFF", 199 | } 200 | 201 | compiler_rt_dir: Path # compiler-rt所在路径 202 | after_build_sysroot: dict[str, Callable[["llvm_environment"], None]] 203 | 204 | def __init__( 205 | self, 206 | build: str, 207 | host: str | None, 208 | family: runtime_family, 209 | runtime_target_list: list[str], 210 | home: Path, 211 | jobs: int, 212 | prefix_dir: Path, 213 | compress_level: int, 214 | long_distance_match: int, 215 | build_tmp: Path, 216 | default_generator: cmake_generator, 217 | ) -> None: 218 | """llvm构建环境 219 | 220 | Args: 221 | build (str): 构建平台 222 | host (str): 宿主平台 223 | family (runtime_family): 运行库类型 224 | runtime_target_list (list[str]): llvm运行时目标平台列表 225 | home (Path): 源代码树搜索主目录 226 | jobs (int): 并发构建数 227 | prefix_dir (str): 安装根目录 228 | compress_level (int): zstd压缩等级 229 | long_distance_match (int): 长距离匹配窗口大小 230 | build_tmp (Path): 构建工具链时存放临时文件的路径 231 | default_generator (cmake_generator): 默认的cmake生成工具 232 | """ 233 | 234 | self.build = build 235 | self.host = host or self.build 236 | self.family = family 237 | name_without_version = f"{self.host}-clang" 238 | super().__init__(build, "21.0.0", name_without_version, home, jobs, prefix_dir, compress_level, long_distance_match, build_tmp) 239 | # 设置prefix 240 | self.prefix["llvm"] = self.prefix_dir / self.name 241 | self.compiler_rt_dir = self.prefix["llvm"] / "lib" / "clang" / self.major_version / "lib" 242 | common.mkdir(self.build_tmp, False) 243 | 244 | self.llvm_build_options = build_options( 245 | ( 246 | ["-stdlib=libstdc++", "-unwindlib=libgcc", "-rtlib=libgcc"] 247 | if self.family == runtime_family.gnu 248 | else ["-stdlib=libc++", "-unwindlib=libunwind", "-rtlib=compiler-rt"] 249 | ), 250 | {**self.llvm_option_list, **self.dylib_option_list, "LLVM_PARALLEL_LINK_JOBS": str(self.jobs // 6)}, 251 | "Linux" if common.triplet_field(self.host).os == "linux" else "Windows", 252 | ) 253 | if self.family == runtime_family.llvm: 254 | self.llvm_build_options.cmake_option["LIBUNWIND_USE_COMPILER_RT"] = "ON" 255 | 256 | self.runtime_build_options = {} 257 | self.sysroot_dir = {} 258 | self.generator_list = {} 259 | for target in runtime_target_list: 260 | self.runtime_build_options[target] = deepcopy(self.llvm_build_options) 261 | gcc = get_specific_environment(self, target=target) 262 | if gcc.freestanding: 263 | self.runtime_build_options[target].system_name = "Generic" 264 | self.runtime_build_options[target].cmake_option.update(self.freestanding_option) 265 | elif gcc.target_field.os == "linux": 266 | self.runtime_build_options[target].system_name = "Linux" 267 | else: 268 | self.runtime_build_options[target].system_name = "Windows" 269 | self.runtime_build_options[target].cmake_option.update(self.win32_options) 270 | self.build_dir[f"{target}-runtimes"] = self.build_tmp / f"{target}-runtimes" 271 | self.prefix[f"{target}-runtimes"] = self.build_tmp / f"{target}-runtimes-install" 272 | self.sysroot_dir[target] = self.prefix_dir / "sysroot" 273 | self.generator_list[target] = default_generator 274 | for lib in lib_list: 275 | self.prefix[lib] = self.build_tmp / f"{self.host}-{lib}-install" 276 | # 设置源目录和构建目录 277 | for project in subproject_list: 278 | self.source_dir[project] = self.home / "llvm" / project 279 | common.check_lib_dir(project, self.source_dir[project]) 280 | self.build_dir["llvm"] = self.build_tmp / f"{self.host}-llvm" 281 | for lib in lib_list: 282 | self.source_dir[lib] = self.home / lib 283 | self.build_dir[lib] = self.build_tmp / f"{self.host}-{lib}" 284 | common.check_lib_dir(lib, self.source_dir[lib]) 285 | if self.build != self.host: 286 | # 交叉编译时runtimes已经编译过了 287 | del self.llvm_build_options.cmake_option["LLVM_ENABLE_RUNTIMES"] 288 | # 设置llvm依赖库编译选项 289 | zlib = f'"{self.prefix["zlib"] / "lib" / "libzlibstatic.a"}"' 290 | self.llvm_build_options.cmake_option.update( 291 | { 292 | "LIBXML2_INCLUDE_DIR": f'"{self.prefix["libxml2"] / "include" / "libxml2"}"', 293 | "LIBXML2_LIBRARY": f'"{self.prefix["libxml2"] / "lib" / "libxml2.a"}"', 294 | "CLANG_ENABLE_LIBXML2": "ON", 295 | "ZLIB_INCLUDE_DIR": f'"{self.prefix["zlib"] / "include"}"', 296 | "ZLIB_LIBRARY": zlib, 297 | "ZLIB_LIBRARY_RELEASE": zlib, 298 | "LLVM_NATIVE_TOOL_DIR": f'"{self.prefix["llvm"] / f'{self.build}-clang{self.major_version}' / "bin"}"', 299 | } 300 | ) 301 | # 将自身注册到环境变量中 302 | self.register_in_env() 303 | self.after_build_sysroot = {} 304 | 305 | def get_compiler(self, target: str, command_list_in: list[str]) -> list[str]: 306 | """获取编译器选项 307 | 308 | Args: 309 | target (str): 目标平台 310 | command_list_in (list[str]): 编译器选项 311 | 312 | Returns: 313 | list[str]: 编译选项 314 | """ 315 | 316 | assert target in [*self.runtime_build_options, self.host] 317 | command_list: list[str] = [] 318 | compiler_path = {"C": "clang", "CXX": "clang++", "ASM": "clang"} 319 | sysroot_dir = self.sysroot_dir[target] 320 | gcc_toolchain = f"--gcc-toolchain={sysroot_dir}" if target == self.build else "" 321 | for compiler in self.compiler_list: 322 | command_list.append(f'-DCMAKE_{compiler}_COMPILER="{compiler_path[compiler]}"') 323 | command_list.append(f"-DCMAKE_{compiler}_COMPILER_TARGET={target}") 324 | command_list.append(f'-DCMAKE_{compiler}_FLAGS="-Wno-unused-command-line-argument {gcc_toolchain} {" ".join(command_list_in)}"') 325 | command_list.append(f"-DCMAKE_{compiler}_COMPILER_WORKS=ON") 326 | for linker in ("EXE", "MODULE", "SHARED"): 327 | command_list.append(f'-DCMAKE_{linker}_LINKER_FLAGS="{gcc_toolchain} {" ".join(command_list_in)}"') 328 | if target != self.build: 329 | system_name = ( 330 | self.runtime_build_options[target].system_name 331 | if target in self.runtime_build_options 332 | else self.llvm_build_options.system_name 333 | ) 334 | command_list.append(f"-DCMAKE_SYSTEM_NAME={system_name}") 335 | command_list.append(f"-DCMAKE_SYSTEM_PROCESSOR={common.triplet_field(target).arch}") 336 | command_list.append("-DCMAKE_CROSSCOMPILING=TRUE") 337 | command_list.append(f'-DCMAKE_SYSROOT="{sysroot_dir}"') 338 | command_list.append(f"-DLLVM_RUNTIMES_TARGET={target}") 339 | command_list.append(f"-DLLVM_DEFAULT_TARGET_TRIPLE={gnu_to_llvm(target)}") 340 | command_list.append(f"-DLLVM_HOST_TRIPLE={gnu_to_llvm(self.host)}") 341 | return command_list 342 | 343 | def config(self, project: str, target: str, command_list: list[str], cmake_option_list: dict[str, str]) -> None: 344 | """配置项目 345 | 346 | Args: 347 | project (str): 子项目 348 | target (str): 目标平台 349 | command_list (list[str]): 附加编译选项 350 | cmake_option_list (dict[str, str]): 附加cmake配置选项 351 | """ 352 | 353 | assert project in self.build_dir 354 | source_dir = self.source_dir[project] if "runtimes" not in project else self.source_dir["runtimes"] 355 | command = f"cmake -G {self.generator_list[target].get_cmake_option()} --install-prefix {self.prefix[project]} -B {self.build_dir[project]} -S {source_dir} " 356 | command += " ".join(self.get_compiler(target, command_list) + get_cmake_option(cmake_option_list)) 357 | common.run_command(command) 358 | 359 | def make(self, project: str, target: str) -> None: 360 | """构建项目 361 | 362 | Args: 363 | project (str): 目标项目 364 | target (str): 目标平台 365 | """ 366 | 367 | assert project in self.build_dir 368 | common.run_command(f"{self.generator_list[target]} -C {self.build_dir[project]} -j{self.jobs}") 369 | 370 | def install(self, project: str, target: str) -> None: 371 | """安装项目 372 | 373 | Args: 374 | project (str): 目标项目 375 | target (str): 目标平台 376 | """ 377 | 378 | assert project in self.build_dir 379 | common.run_command(f"{self.generator_list[target]} -C {self.build_dir[project]} install/strip -j{self.jobs}") 380 | 381 | def build_sysroot(self, target: str) -> None: 382 | """构建sysroot 383 | 384 | Args: 385 | target (str): 目标平台 386 | """ 387 | 388 | prefix = self.prefix[f"{target}-runtimes"] 389 | triplet_field = common.triplet_field(target) 390 | arch = triplet_field.arch 391 | # 针对i686的修正 392 | if arch == "i686": 393 | arch = "i386" 394 | # 针对arm-linux-gnueabihf的修正 395 | if arch == "arm" and triplet_field.abi == "gnueabihf": 396 | arch = "armhf" 397 | sysroot_dir = self.sysroot_dir[target] 398 | target_sysroot = sysroot_dir if sysroot_dir.name == target else sysroot_dir / target 399 | for src_dir in prefix.iterdir(): 400 | match src_dir.name: 401 | case "bin": 402 | # 复制dll 403 | dst_dir = target_sysroot / "lib" 404 | for file in src_dir.iterdir(): 405 | if file.suffix == ".dll": 406 | common.copy(file, dst_dir / file.name) 407 | case "lib": 408 | dst_dir = target_sysroot / "lib" 409 | common.mkdir(dst_dir, False) 410 | for item in src_dir.iterdir(): 411 | # 复制compiler-rt 412 | if item.name == self.runtime_build_options[target].system_name.lower(): 413 | if target == self.build: 414 | continue 415 | rt_dir = self.compiler_rt_dir / gnu_to_llvm(target) 416 | common.mkdir(rt_dir, False) 417 | for file in item.iterdir(): 418 | name = file.name 419 | pos = name.find(f"-{arch}") 420 | if pos != -1: 421 | name = name[:pos] + name[pos + len(f"-{arch}") :] 422 | common.copy(file, rt_dir / name) 423 | else: 424 | # 复制其他库 425 | common.copy(item, dst_dir / item.name) 426 | case "include": 427 | # 复制__config_site 428 | dst_dir = target_sysroot / "include" 429 | common.copy(src_dir / "c++" / "v1" / "__config_site", dst_dir / "__config_site") 430 | # 对于Windows目标,需要在sysroot/include下准备一份头文件 431 | dst_dir = sysroot_dir / "include" / "c++" 432 | common.copy(self.prefix["llvm"] / "include" / "c++", dst_dir, False) 433 | case "share": 434 | dst_dir = target_sysroot / "share" 435 | common.mkdir(dst_dir, False) 436 | for item in src_dir.iterdir(): 437 | common.copy(item, dst_dir / item.name) 438 | case _: 439 | pass 440 | # 为mingw目标建立软链接 441 | match (target): 442 | case "x86_64-w64-mingw32": 443 | common.symlink(Path("x86_64-unknown-windows-gnu"), self.compiler_rt_dir / "x86_64-w64-windows-gnu") 444 | case "i686-w64-mingw32": 445 | common.symlink(Path("i686-unknown-windows-gnu"), self.compiler_rt_dir / "i686-w64-windows-gnu") 446 | case _: 447 | pass 448 | 449 | if callback := self.after_build_sysroot.get(target, None): 450 | callback(self) 451 | 452 | def copy_llvm_libs(self) -> None: 453 | """复制工具链所需库""" 454 | 455 | src_prefix = self.sysroot_dir[self.build] / self.host / "lib" 456 | dst_prefix = self.prefix["llvm"] / ("bin" if common.triplet_field(self.host).os == "w64" else "lib") 457 | native_dir = self.prefix_dir / f"{self.build}-clang{self.major_version}" 458 | native_bin_dir = native_dir / "bin" 459 | native_compiler_rt_dir = native_dir / "lib" / "clang" / self.major_version / "lib" 460 | # 复制libc++和libunwind运行库 461 | if self.family == runtime_family.llvm: 462 | for file in filter( 463 | lambda file: file.name.startswith(("libc++", "libunwind")) and not file.name.endswith((".a", ".json")), src_prefix.iterdir() 464 | ): 465 | common.copy(file, dst_prefix / file.name) 466 | # 复制公用libc++和libunwind头文件 467 | src_prefix = native_bin_dir.parent / "include" 468 | dst_prefix = self.prefix["llvm"] / "include" 469 | for item in filter(lambda item: "unwind" in item.name or item.name == "c++", src_prefix.iterdir()): 470 | common.copy(item, dst_prefix / item.name) 471 | 472 | if self.build != self.host: 473 | # 从build下的本地工具链复制compiler-rt 474 | # 其他库在sysroot中,无需复制 475 | src_prefix = native_compiler_rt_dir 476 | dst_prefix = self.compiler_rt_dir 477 | common.copy(src_prefix, dst_prefix, True) 478 | 479 | def package(self) -> None: 480 | """打包工具链""" 481 | 482 | self.compress() 483 | # 编译本地工具链时才需要打包sysroot 484 | if self.build == self.host: 485 | self.compress("sysroot") 486 | 487 | 488 | class build_llvm_environment: 489 | 490 | @staticmethod 491 | def _build_linux(env: llvm_environment) -> None: 492 | """构建host为linux的llvm 493 | 494 | Args: 495 | env (llvm_environment): llvm构建环境 496 | """ 497 | 498 | # 构建llvm 499 | env.config("llvm", env.host, env.llvm_build_options.basic_option, env.llvm_build_options.cmake_option) 500 | env.make("llvm", env.host) 501 | env.install("llvm", env.host) 502 | # 构建运行库 503 | for target, option in env.runtime_build_options.items(): 504 | runtimes_name = f"{target}-runtimes" 505 | env.config(runtimes_name, target, option.basic_option, option.cmake_option) 506 | env.make(runtimes_name, target) 507 | env.install(runtimes_name, target) 508 | env.build_sysroot(target) 509 | # 打包 510 | env.package() 511 | 512 | @staticmethod 513 | def _build_mingw(env: llvm_environment) -> None: 514 | """构建host为mingw的llvm 515 | 516 | Args: 517 | env (llvm_environment): llvm构建环境 518 | """ 519 | 520 | # 构建依赖库 521 | lib_basic_command = [*env.llvm_build_options.basic_option, "-lws2_32", "-lbcrypt"] 522 | for lib in lib_list: 523 | env.config(lib, env.host, lib_basic_command, env.lib_option) 524 | env.make(lib, env.host) 525 | env.install(lib, env.host) 526 | # 构建llvm 527 | env.config("llvm", env.host, env.llvm_build_options.basic_option, env.llvm_build_options.cmake_option) 528 | env.make("llvm", env.host) 529 | env.install("llvm", env.host) 530 | env.copy_llvm_libs() 531 | env.package() 532 | 533 | @staticmethod 534 | def build(env: llvm_environment) -> None: 535 | if env.llvm_build_options.system_name == "Linux": 536 | build_llvm_environment._build_linux(env) 537 | else: 538 | build_llvm_environment._build_mingw(env) 539 | -------------------------------------------------------------------------------- /toolchains/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # PYTHON_ARGCOMPLETE_OK 4 | 5 | import argparse 6 | import functools 7 | import multiprocessing 8 | from pathlib import Path 9 | 10 | from . import common 11 | from .utils_source import * 12 | 13 | 14 | def compress(config: compress_configure) -> None: 15 | """压缩工具链 16 | 17 | Args: 18 | config (compress_configure): 工具链压缩环境 19 | """ 20 | 21 | output_dir, dir_list, env = config._output_dir, config._item_list, config.to_environment() 22 | common.mkdir(output_dir, False) 23 | with common.chdir_guard(env.prefix_dir): 24 | for dir in dir_list or filter(lambda dir: common.toolchains_dir(dir), env.prefix_dir.iterdir()): 25 | env.compress_path(str(dir.relative_to(env.prefix_dir)), output_dir, False) 26 | 27 | common.toolchains_print(common.toolchains_success("Compress toolchains successfully.")) 28 | 29 | 30 | def _decompress_worker(env: common.compress_environment, output_dir: Path, mutex: common.optional_lock, file: Path) -> None: 31 | """执行解压缩操作 32 | 33 | Args: 34 | env (common.compress_environment): 工具链压缩环境 35 | output_dir (Path): 输出目录 36 | mutex (common.optional_lock): 并行环境下的互斥锁 37 | file (Path): 要解压的文件 38 | """ 39 | 40 | env.decompress_path(str(file.relative_to(env.prefix_dir)), output_dir, False, mutex) 41 | 42 | 43 | def decompress(config: compress_configure) -> None: 44 | """解压缩打包的工具链 45 | 46 | Args: 47 | config (compress_configure): 工具链压缩环境 48 | """ 49 | 50 | output_dir, file_list, env = config._output_dir, config._item_list, config.to_environment() 51 | common.mkdir(output_dir, False) 52 | with common.chdir_guard(output_dir): 53 | file_list = file_list or [*filter(lambda file: common.toolchains_package(file), env.prefix_dir.iterdir())] 54 | 55 | if env.jobs > 1: 56 | with multiprocessing.Manager() as manager, multiprocessing.Pool(config.jobs) as pool: 57 | mutex = manager.Lock() 58 | pool.map(functools.partial(_decompress_worker, env, output_dir, mutex), file_list) 59 | else: 60 | for file in file_list: 61 | _decompress_worker(env, output_dir, None, file) 62 | 63 | common.toolchains_print(common.toolchains_success("Decompress toolchains successfully.")) 64 | 65 | 66 | __all__ = ["compress_configure", "compress", "decompress"] 67 | 68 | 69 | def main() -> int: 70 | default_config = compress_configure() 71 | 72 | parser = argparse.ArgumentParser(description="Utilities for toolchains.") 73 | subparsers = parser.add_subparsers(dest="command", required=True, help="Available commands.") 74 | 75 | compress_parser = subparsers.add_parser( 76 | "compress", help="Compress installed toolchains under the prefix directory.", formatter_class=common.arg_formatter 77 | ) 78 | decompress_parser = subparsers.add_parser( 79 | "decompress", help="Decompress packed toolchains under the prefix directory.", formatter_class=common.arg_formatter 80 | ) 81 | 82 | compress_configure.add_argument(compress_parser) 83 | action = compress_parser.add_argument( 84 | "--dir", 85 | "-d", 86 | dest="item_list", 87 | action="extend", 88 | nargs="*", 89 | help="Directories of toolchains to compress. This is a path relative to the prefix directory.", 90 | ) 91 | common.register_completer(action, common.item_with_prefix_completer("prefix_dir", common.toolchains_dir)) 92 | compress_configure.add_argument(decompress_parser) 93 | action = decompress_parser.add_argument( 94 | "--file", 95 | "-f", 96 | dest="item_list", 97 | action="extend", 98 | nargs="*", 99 | help="Files of packed toolchains to decompress. This is a path relative to the prefix directory.", 100 | ) 101 | common.register_completer(action, common.item_with_prefix_completer("prefix_dir", common.toolchains_package)) 102 | 103 | common.support_argcomplete(parser) 104 | args = parser.parse_args() 105 | 106 | def do_main() -> None: 107 | match (args.command): 108 | case "compress": 109 | compress(compress_configure.parse_args(args)) 110 | case "decompress": 111 | decompress(compress_configure.parse_args(args)) 112 | case _: 113 | pass 114 | 115 | return common.toolchains_main(do_main) 116 | -------------------------------------------------------------------------------- /toolchains/utils_source.py: -------------------------------------------------------------------------------- 1 | import typing 2 | from argparse import ArgumentParser 3 | from pathlib import Path 4 | 5 | from . import common 6 | 7 | 8 | class compress_configure(common.basic_compress_configure): 9 | _item_list: list[Path] 10 | _output_dir: Path 11 | 12 | def __init__( 13 | self, item_list: list[str] | None = None, output_dir: str | None = None, base_path: Path = Path.cwd(), **kwargs: typing.Any 14 | ) -> None: 15 | super().__init__(base_path=base_path, **kwargs) 16 | self._item_list = [self.prefix_dir / item for item in (item_list or [])] 17 | self._output_dir = common.resolve_path(output_dir or self.prefix_dir, base_path) 18 | 19 | @classmethod 20 | def add_argument(cls, parser: ArgumentParser) -> None: 21 | super().add_argument(parser) 22 | 23 | default_config = compress_configure() 24 | parser.add_argument( 25 | "--output", 26 | "-o", 27 | dest="output_dir", 28 | type=str, 29 | help="The directory to store the operate result.", 30 | default=default_config._output_dir, 31 | ) 32 | 33 | def check(self, need_dir: bool = True) -> None: 34 | """检查压缩环境配置是否合法""" 35 | 36 | super().check() 37 | for item_path in self._item_list: 38 | if need_dir: 39 | assert common.toolchains_dir(item_path), f'Path "{item_path}" is not a directory.' 40 | else: 41 | assert common.toolchains_package(item_path), f'Path "{item_path}" is not a tar file compressed by zstd.' 42 | 43 | def to_environment(self) -> common.compress_environment: 44 | """将配置信息转化为压缩环境 45 | 46 | Returns: 47 | common.compress_environment: 工具链压缩环境 48 | """ 49 | 50 | return common.compress_environment(self.jobs, self.prefix_dir, self.compress_level, self.long_distance_match) 51 | 52 | 53 | __all__ = ["compress_configure"] 54 | -------------------------------------------------------------------------------- /toolchains/x86_64_w64_mingw32_clang.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | import llvm_environment as llvm 4 | 5 | env = llvm.environment(host="x86_64-w64-mingw32") 6 | 7 | 8 | def build(_: int = env.stage) -> None: 9 | assert not env.bootstrap, "Cannot bootstrap a canadian toolchain since it runs on a different machine." 10 | env.stage = 1 11 | basic_command = ("-stdlib=libc++", "-unwindlib=libunwind", "-rtlib=compiler-rt") 12 | for lib in llvm.lib_list: 13 | command = (*basic_command, "-lws2_32", "-lbcrypt") 14 | env.config(lib, env.host, *command, **env.lib_option) 15 | env.make(lib) 16 | env.install(lib) 17 | 18 | env.config("llvm", env.host, *basic_command, **{**env.dylib_option_list, **env.llvm_option_list_1, **env.llvm_cross_option}) 19 | env.make("llvm") 20 | env.install("llvm") 21 | env.copy_llvm_libs() 22 | env.remove_build_dir("llvm") 23 | env.package() 24 | 25 | 26 | if __name__ == "__main__": 27 | build() 28 | -------------------------------------------------------------------------------- /wrapper/build_gcc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # PYTHON_ARGCOMPLETE_OK 4 | 5 | import pathlib 6 | import sys 7 | 8 | sys.path.append(str(pathlib.Path(__file__).parent.parent)) 9 | from toolchains.build_gcc import main 10 | 11 | sys.exit(main()) 12 | -------------------------------------------------------------------------------- /wrapper/build_llvm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # PYTHON_ARGCOMPLETE_OK 4 | 5 | import pathlib 6 | import sys 7 | 8 | sys.path.append(str(pathlib.Path(__file__).parent.parent)) 9 | from toolchains.build_llvm import main 10 | 11 | sys.exit(main()) 12 | -------------------------------------------------------------------------------- /wrapper/download.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # PYTHON_ARGCOMPLETE_OK 4 | 5 | import pathlib 6 | import sys 7 | 8 | sys.path.append(str(pathlib.Path(__file__).parent.parent)) 9 | from toolchains.download import main 10 | 11 | sys.exit(main()) 12 | -------------------------------------------------------------------------------- /wrapper/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # PYTHON_ARGCOMPLETE_OK 4 | 5 | import pathlib 6 | import sys 7 | 8 | sys.path.append(str(pathlib.Path(__file__).parent.parent)) 9 | from toolchains.utils import main 10 | 11 | sys.exit(main()) 12 | -------------------------------------------------------------------------------- /xmake/option.lua: -------------------------------------------------------------------------------- 1 | option("march", function() 2 | set_description([[Set the "-march" option for gcc and clang.]], 3 | "The option is automatically added if using our toolchain option.", 4 | [[ none: Don't set the "-march" option, use the default march of the toolchain.]], 5 | [[ default: Set the "-march" option as "-march=native" if possible, otherwise don't set the "-march" option and use the default march of the toolchain.]], 6 | [[ arch: Set the "-march" option as "-march=arch". Note that "arch" is any value other than "none" and "default".]]) 7 | set_default("default") 8 | after_check(function(option) 9 | import("utility.utility") 10 | option:add("cxflags", utility.get_march_option()) 11 | end) 12 | end) 13 | 14 | option("sysroot", function() 15 | set_description("Set the `--sysroot` option for gcc and clang.", 16 | "The option is automatically added if using our toolchain option.", 17 | [[ none: Don't set the "--sysroot" option, use the default sysroot of the toolchain.]], 18 | [[ detect: Detect and set the sysroot for clang, use the default sysroot for gcc.]], 19 | [[ path: Set the "--sysroot" option as "--sysroot=path". Note that "path" is an absolute path or a relative path other than "none" and "detect".]]) 20 | set_default("detect") 21 | after_check(function(option) 22 | import("utility.utility") 23 | local sysroot_option = utility.get_sysroot_option() 24 | for flag, opt in pairs(sysroot_option) do 25 | option:add(flag, opt) 26 | end 27 | end) 28 | end) 29 | 30 | option("rtlib", function() 31 | set_description([[Set the "-rtlib" option for clang.]], 32 | "The option is automatically added if using our toolchain option.", 33 | [[ default: Don't set the "rtlib" option, use the default rtlib of clang.]], 34 | [[ libgcc/compiler-rt/platform: Set the "rtlib" option.]]) 35 | set_default("default") 36 | set_values("default", "libgcc", "compiler-rt", "platform") 37 | after_check(function(option) 38 | import("utility.utility") 39 | local rtlib_option = utility.get_rtlib_option() 40 | for _, flag in ipairs({ "ldflags", "shflags" }) do 41 | option:add(flag, rtlib_option) 42 | end 43 | end) 44 | end) 45 | 46 | option("unwindlib", function() 47 | set_description([[Set the "-unwindlib" option for clang."]], 48 | "The option is automatically added if using our toolchain option.", 49 | [[ default: Don't set the "unwindlib" option, use the default unwindlib of clang.]], 50 | [[ libgcc/libunwind/platform: Set the option "unwindlib" if rtlib is "compiler-rt".]], 51 | [[ force_libgcc/force_libunwind/force_platform: Always set the "unwindlib" option.]]) 52 | set_default("default") 53 | set_values("default", "libgcc", "libunwind", "platform", "force_libgcc", "force_libunwind", "force_platform") 54 | add_deps("rtlib") 55 | after_check(function(option) 56 | import("utility.utility") 57 | local unwind_option = utility.get_unwindlib_option() 58 | for _, flag in ipairs({ "ldflags", "shflags" }) do 59 | option:add(flag, unwind_option) 60 | end 61 | end) 62 | end) 63 | 64 | option("debug_strip", function() 65 | set_description("Whether to strip the symbols while building with debug information.", 66 | [[ none: Don't strip the symbols if possible.]], 67 | [[ debug: Strip the debug symbols to a independent symbol file while keeping other symbols in the target file.]], 68 | [[ all: Strip the debug symbols to a independent symbol file then strip all symbols from the target file.]]) 69 | set_default("none") 70 | set_values("none", "debug", "all") 71 | end) 72 | 73 | option("enable_lto", function() 74 | set_description("Whether to enable LTO while building in release/minsizerel/releasedbg mode.", 75 | "LTO is enabled by default, but when use different compiler and linker, " .. 76 | "for example, compiling with clang while linking with bfd, LTO should be disabled.", 77 | [[Errors may be reported as "file not recognized: file format not recognized".]]) 78 | set_default(true) 79 | end) 80 | -------------------------------------------------------------------------------- /xmake/rule.lua: -------------------------------------------------------------------------------- 1 | includes("option.lua") 2 | ---@type string | nil 3 | local debug_strip = get_config("debug_strip") 4 | if debug_strip == "none" then -- 不剥离符号 5 | debug_strip = nil 6 | end 7 | ---@type boolean 8 | local enable_lto = get_config("enable_lto") 9 | 10 | rule("debug", function() 11 | on_load(function(target) 12 | target:set("symbols", "debug") 13 | target:set("optimize", "none") 14 | target:add("defines", "DEBUG", "_DEBUG") 15 | target:set("strip", debug_strip) 16 | end) 17 | end) 18 | 19 | rule("release", function() 20 | on_load(function(target) 21 | target:add("defines", "NDEBUG") 22 | target:set("optimize", "fastest") 23 | target:set("strip", "all") 24 | target:set("policy", "build.optimization.lto", enable_lto) 25 | end) 26 | end) 27 | 28 | rule("minsizerel", function() 29 | on_load(function(target) 30 | target:add("defines", "NDEBUG") 31 | target:set("optimize", "smallest") 32 | target:set("strip", "all") 33 | target:set("policy", "build.optimization.lto", enable_lto) 34 | end) 35 | end) 36 | 37 | rule("releasedbg", function() 38 | on_load(function(target) 39 | target:set("optimize", "fastest") 40 | target:set("symbols", "debug") 41 | target:set("policy", "build.optimization.lto", enable_lto) 42 | target:set("strip", debug_strip) 43 | end) 44 | end) 45 | 46 | 47 | ---受支持的规则表 48 | ---@type string[] 49 | support_rules_table = { "debug", "release", "minsizerel", "releasedbg" } 50 | -------------------------------------------------------------------------------- /xmake/toolchain.lua: -------------------------------------------------------------------------------- 1 | ---@type string | nil 2 | local rcfiles = os.getenv("XMAKE_RCFILES") 3 | ---@type string | nil 4 | local script_dir = nil 5 | ---探测toolchain.lua并根据该文件的绝对路径提取脚本目录 6 | ---@see https://github.com/xmake-io/xmake/issues/6048 7 | if rcfiles then 8 | for _, rcfile in ipairs(path.splitenv(rcfiles)) do 9 | if rcfile:endswith("toolchain.lua") then 10 | script_dir = path.directory(rcfile) 11 | break 12 | end 13 | end 14 | end 15 | 16 | if script_dir then 17 | add_moduledirs(script_dir) 18 | includes(path.join(script_dir, "option.lua"), path.join(script_dir, "utility/target.lua")) 19 | else 20 | includes("option.lua", "utility/target.lua") 21 | end 22 | 23 | ---获取工具链描述文本 24 | ---@param toolchain string --工具链名称 25 | ---@param target string --工具链目标 26 | ---@return string --描述文本 27 | function _get_toolchain_description(toolchain, target) 28 | return format("A %s toolchain for ", toolchain) .. 29 | (target ~= "target" and target or "target detected by arch and plat") 30 | end 31 | 32 | ---注册clang工具链 33 | ---@param target string --clang工具链目标平台 34 | ---@return nil 35 | function register_clang_toolchain(target) 36 | toolchain(target .. "-clang", function() 37 | set_kind("standalone") 38 | set_homepage("https://github.com/24bit-xjkp/toolchains/") 39 | set_description(_get_toolchain_description("clang", target)) 40 | set_runtimes("c++_static", "c++_shared", "stdc++_static", "stdc++_shared") 41 | 42 | set_toolset("cc", "clang") 43 | set_toolset("cxx", "clang++", "clang") 44 | set_toolset("ld", "clang++", "clang") 45 | set_toolset("sh", "clang++", "clang") 46 | set_toolset("as", "clang") 47 | set_toolset("ar", "llvm-ar") 48 | set_toolset("strip", "llvm-strip") 49 | set_toolset("ranlib", "llvm-ranlib") 50 | set_toolset("objcopy", "llvm-objcopy") 51 | set_toolset("mrc", "llvm-rc") 52 | 53 | on_check(function(_) 54 | return import("lib.detect.find_program")("clang") 55 | end) 56 | 57 | on_load(function(toolchain) 58 | import("utility.utility") 59 | if toolchain:is_plat("windows") then 60 | toolchain:add("runtimes", "MT", "MTd", "MD", "MDd") 61 | end 62 | 63 | ---@type string, modifier_t 64 | local target, modifier = utility.get_target_modifier(target, "clang") 65 | 66 | local march_option = utility.get_march_option(target, "clang") 67 | local sysroot_option = utility.get_sysroot_option() 68 | local rtlib_option = utility.get_rtlib_option() 69 | local unwind_option = utility.get_unwindlib_option() 70 | local target_option = target ~= "native" and "--target=" .. target or nil 71 | 72 | local opt = { 73 | march = march_option, 74 | sysroot = sysroot_option, 75 | rtlib = rtlib_option, 76 | unwind = unwind_option, 77 | target = target_option 78 | } 79 | modifier(toolchain, opt) 80 | 81 | toolchain:add("cxflags", march_option) 82 | for flag, option in pairs(sysroot_option) do 83 | toolchain:add(flag, option) 84 | end 85 | for _, flag in ipairs({ "cxflags", "ldflags", "shflags", "asflags" }) do 86 | toolchain:add(flag, target_option) 87 | toolchain:add(flag ~= "cxflags" and flag or nil, rtlib_option, unwind_option) 88 | end 89 | end) 90 | end) 91 | end 92 | 93 | for target, _ in pairs(get_clang_target_list()) do 94 | register_clang_toolchain(target) 95 | end 96 | 97 | ---注册gcc工具链 98 | ---@param target string --gcc工具链目标平台 99 | ---@return nil 100 | function register_gcc_toolchain(target) 101 | toolchain(target .. "-gcc", function() 102 | set_kind("standalone") 103 | set_homepage("https://github.com/24bit-xjkp/toolchains/") 104 | set_description(_get_toolchain_description("gcc", target)) 105 | set_runtimes("stdc++_static", "stdc++_shared") 106 | local prefix 107 | ---@type modifier_t 108 | local modifier 109 | 110 | on_check(function(_) 111 | target, modifier = import("utility.utility").get_target_modifier(target, "gcc") 112 | prefix = target == "native" and "" or target .. "-" 113 | return import("lib.detect.find_program")(prefix .. "gcc") 114 | end) 115 | 116 | on_load(function(toolchain) 117 | target, modifier = import("utility.utility").get_target_modifier(target, "gcc") 118 | prefix = target == "native" and "" or target .. "-" 119 | toolchain:set("toolset", "cc", prefix .. "gcc") 120 | toolchain:set("toolset", "cxx", prefix .. "g++", prefix .. "gcc") 121 | toolchain:set("toolset", "ld", prefix .. "g++", prefix .. "gcc") 122 | toolchain:set("toolset", "sh", prefix .. "g++", prefix .. "gcc") 123 | toolchain:set("toolset", "ar", prefix .. "ar") 124 | toolchain:set("toolset", "strip", prefix .. "strip") 125 | toolchain:set("toolset", "objcopy", prefix .. "objcopy") 126 | toolchain:set("toolset", "ranlib", prefix .. "ranlib") 127 | toolchain:set("toolset", "as", prefix .. "gcc") 128 | 129 | import("utility.utility") 130 | local march_option = utility.get_march_option(target, "gcc") 131 | local sysroot_option = utility.get_sysroot_option() 132 | 133 | local opt = { march = march_option, sysroot = sysroot_option } 134 | modifier(toolchain, opt) 135 | 136 | toolchain:add("cxflags", march_option) 137 | for flag, option in pairs(sysroot_option) do 138 | toolchain:add(flag, option) 139 | end 140 | end) 141 | end) 142 | end 143 | 144 | for target, _ in pairs(get_gcc_target_list()) do 145 | register_gcc_toolchain(target) 146 | end 147 | -------------------------------------------------------------------------------- /xmake/utility/common.lua: -------------------------------------------------------------------------------- 1 | import("core.cache.detectcache") 2 | ---@alias map_t table 3 | 4 | ---判断工具链是否是clang 5 | ---@return boolean --是否是clang工具链 6 | function is_clang() 7 | return string.find(get_config("toolchain") or "", "clang", 1, true) ~= nil 8 | end 9 | 10 | ---本模块使用的缓存键 11 | local cache_key = "toolchain.utility" 12 | 13 | ---获取缓存信息 14 | ---@return table --缓存信息表 15 | function get_cache() 16 | local cache_info = detectcache:get(cache_key) 17 | if not cache_info then 18 | cache_info = {} 19 | detectcache:set(cache_key, cache_info) 20 | end 21 | return cache_info 22 | end 23 | 24 | ---更新缓存信息 25 | ---@param cache_info table --要保存的缓存信息 26 | ---@return nil 27 | function update_cache(cache_info) 28 | detectcache:set(cache_key, cache_info) 29 | detectcache:save() 30 | end 31 | -------------------------------------------------------------------------------- /xmake/utility/target.lua: -------------------------------------------------------------------------------- 1 | ---@alias modifier_t fun(toolchain:unknown, opt:table):nil 2 | ---@alias modifier_table_t table 3 | ---@class sysroot_t 4 | ---@field public ldflags string 5 | ---@field public cxflags string | string[] 6 | ---@field public shflags string 7 | ---@class opt_t 8 | ---@field public march string 9 | ---@field public sysroot sysroot_t 10 | 11 | ---占位符,无效果 12 | ---@return nil 13 | function noop_modifier(_, _) return end 14 | 15 | ---为loongnix定制部分flag 16 | ---@return nil 17 | function loongnix_modifier(toolchain, _) 18 | -- loongnix的glibc版本较老,使用的ld路径与新编译器默认路径不同 19 | toolchain:add("ldflags", "-Wl,-dynamic-linker=/lib64/ld.so.1") 20 | -- loongnix本机gdb仅支持dwarf4格式调试信息 21 | toolchain:add("cxflags", "-gdwarf-4") 22 | end 23 | 24 | ---为独立工具链定制部分flag 25 | ---@return nil 26 | function freestanding_modifier(toolchain, _) 27 | -- freestanding需要禁用标准库 28 | toolchain:add("cxflags", "-ffreestanding") 29 | toolchain:add("ldflags", "-nodefaultlibs", "-lstdc++", "-lgcc") 30 | end 31 | 32 | local note_msg = "${color.warning}NOTE:${default} " 33 | 34 | ---根据target重设sysroot 35 | ---@param target string 36 | ---@param opt opt_t 37 | ---@return nil 38 | function _reset_sysroot(target, opt) 39 | import("common") 40 | local cache_info = common.get_cache() 41 | if cache_info["sysroot_set_by_user"] then 42 | return 43 | end 44 | local sysroot_option = opt.sysroot 45 | local sysroot = sysroot_option.ldflags:sub(11) 46 | local libcxx_option = #sysroot_option.cxflags == 2 and sysroot_option.cxflags[2] or nil 47 | local target_sysroot = path.join(sysroot, target) 48 | if path.filename(sysroot) ~= target and os.isdir(target_sysroot) then 49 | cprint(note_msg .. [[Reset "--sysroot" option to "%s".]], target_sysroot) 50 | sysroot = "--sysroot=" .. target_sysroot 51 | opt.sysroot.cxflags = { sysroot, libcxx_option } 52 | opt.sysroot.ldflags = sysroot 53 | opt.sysroot.shflags = sysroot 54 | end 55 | end 56 | 57 | ---重设march为指定值 58 | ---@param march string 59 | ---@param opt opt_t 60 | ---@return nil 61 | function _reset_march(march, opt) 62 | local march_option = "-march=" .. march 63 | if opt.march ~= march_option then 64 | cprint(note_msg .. [[Reset "-march" option to %s.]], march) 65 | opt.march = march_option 66 | end 67 | end 68 | 69 | ---为armv7m定制部分flag 70 | ---@param opt opt_t 71 | ---@return nil 72 | function armv7m_modifier(_, opt) 73 | _reset_march("armv7-m", opt) 74 | _reset_sysroot("armv7m-none-eabi", opt) 75 | end 76 | 77 | ---为armv7m-fpv4定制部分flag 78 | ---@param opt opt_t 79 | ---@return nil 80 | function armv7m_fpv4_modifier(toolchain, opt) 81 | _reset_march("armv7-m", opt) 82 | _reset_sysroot("armv7m-fpv4-none-eabi", opt) 83 | toolchain:add("cxflags", "-mfpu=fpv4-sp-d16", "-mfloat-abi=hard") 84 | toolchain:add("asflags", "-mfpu=fpv4-sp-d16") 85 | end 86 | 87 | ---只有clang支持的目标 88 | ---@type modifier_table_t 89 | clang_only_target_list = { 90 | ["x86_64-windows-msvc"] = noop_modifier, 91 | ["armv7m-none-eabi"] = armv7m_modifier, 92 | ["armv7m-fpv4-none-eabi"] = armv7m_fpv4_modifier, 93 | } 94 | ---只有gcc支持的目标 95 | ---@type modifier_table_t 96 | gcc_only_target_list = { 97 | ["arm-none-eabi"] = noop_modifier, 98 | ---编译gcc时已经指定过默认选项,此处不再指定 99 | ["arm-fpv4-none-eabi"] = noop_modifier, 100 | ["arm-nonewlib-none-eabi"] = freestanding_modifier, 101 | ["riscv-none-elf"] = noop_modifier, 102 | } 103 | ---gcc和clang均支持的目标 104 | ---@type modifier_table_t 105 | general_target_list = { 106 | ["x86_64-linux-gnu"] = noop_modifier, 107 | ["i686-linux-gnu"] = noop_modifier, 108 | ["x86_64-w64-mingw32"] = noop_modifier, 109 | ["i686-w64-mingw32"] = noop_modifier, 110 | ["loongarch64-linux-gnu"] = noop_modifier, 111 | ["loongarch64-loongnix-linux-gnu"] = loongnix_modifier, 112 | ["riscv64-linux-gnu"] = noop_modifier, 113 | ["aarch64-linux-gnu"] = noop_modifier, 114 | ["arm-linux-gnueabi"] = noop_modifier, 115 | ["arm-linux-gnueabihf"] = noop_modifier, 116 | ["x86_64-elf"] = freestanding_modifier, 117 | ["native"] = noop_modifier, 118 | ["target"] = noop_modifier 119 | } 120 | 121 | ---获取clang支持的目标列表 122 | ---@return modifier_table_t 123 | function get_clang_target_list() 124 | return table.join(general_target_list, clang_only_target_list) 125 | end 126 | 127 | ---获取gcc支持的目标列表 128 | ---@return modifier_table_t 129 | function get_gcc_target_list() 130 | return table.join(general_target_list, gcc_only_target_list) 131 | end 132 | 133 | ---获取所有受支持的目标列表 134 | ---@return modifier_table_t 135 | function get_target_list() 136 | return table.join(general_target_list, clang_only_target_list, gcc_only_target_list) 137 | end 138 | -------------------------------------------------------------------------------- /xmake/utility/utility.lua: -------------------------------------------------------------------------------- 1 | import("common") 2 | 3 | ---根据arch和plat推导target和modifier 4 | ---@param target string --目标平台 5 | ---@param toolchain string --工具链名称 6 | ---@return string --目标平台 7 | ---@return modifier_t --调整函数 8 | function get_target_modifier(target, toolchain) 9 | ---@type modifier_table_t 10 | local target_list = import("target", { anonymous = true }).get_target_list() 11 | if target ~= "target" then 12 | return target, target_list[target] 13 | end 14 | 15 | ---@type table 16 | local cache_info = common.get_cache() 17 | ---@type string, modifier_t | nil 18 | local target, modifier = table.unpack(cache_info["target"] or {}) 19 | if target and modifier then -- 已经探测过,直接返回target和modifier 20 | return target, modifier 21 | end 22 | 23 | ---@type string | nil 24 | local arch = get_config("arch") 25 | ---@type string | nil 26 | local plat = get_config("plat") 27 | ---@type string 28 | local target_os = get_config("target_os") or "none" 29 | local message = [[Unsupported %s "%s". Please select a specific toolchain.]] 30 | 31 | ---将xmake风格arch映射为triplet风格 32 | ---@type map_t 33 | local arch_table = { 34 | x86 = "i686", 35 | i386 = "i686", 36 | i686 = "i686", 37 | x64 = "x86_64", 38 | x86_64 = "x86_64", 39 | loong64 = "loongarch64", 40 | riscv64 = "riscv64", 41 | arm = "arm", 42 | armv7 = "arm", 43 | armv7s = "arm", 44 | ["arm64-v8a"] = "aarch64", 45 | arm64 = "aarch64", 46 | arm64ec = "aarch64" 47 | } 48 | local old_arch = arch 49 | arch = arch_table[arch] 50 | assert(arch, format(message, "arch", old_arch)) 51 | 52 | if plat == "windows" and toolchain == "gcc" then 53 | plat = "mingw" -- gcc不支持msvc目标,但clang支持 54 | end 55 | ---@type map_t 56 | local plat_table = { 57 | mingw = "w64", 58 | msys = "w64", 59 | linux = "linux", 60 | windows = "windows", 61 | cross = target_os 62 | } 63 | local old_plat = plat 64 | ---@type string | nil 65 | plat = plat_table[plat] 66 | assert(plat, format(message, "plat", old_plat)) 67 | 68 | ---@type map_t 69 | local x86_abi_table = { 70 | windows = "msvc", 71 | w64 = "mingw32", 72 | linux = "gnu", 73 | none = "elf" 74 | } 75 | ---@type map_t 76 | local linux_abi_table = { linux = "gnu" } 77 | ---@type table 78 | local abi_table = { 79 | i686 = x86_abi_table, 80 | x86_64 = x86_abi_table, 81 | arm = { 82 | linux = "gnueabihf", 83 | none = "eabi" 84 | }, 85 | aarch64 = linux_abi_table, 86 | riscv64 = linux_abi_table, 87 | loongarch64 = linux_abi_table 88 | } 89 | ---@type string 90 | local abi = (abi_table[arch] or {})[plat] or "unknown" 91 | 92 | ---@type string[] 93 | local field = { arch, plat, abi } 94 | -- 针对arch-elf的特殊处理 95 | if plat == "none" and abi == "elf" then 96 | table.remove(field, 2) 97 | end 98 | target = table.concat(field, "-") 99 | 100 | ---@type modifier_t | nil 101 | modifier = target_list[target] 102 | cprint("detecting for target .. " .. (modifier and "${color.success}" or "${color.failure}") .. target) 103 | assert(modifier, format(message, "target", target)) 104 | 105 | cache_info["target"] = { target, modifier } 106 | common.update_cache(cache_info) 107 | 108 | return target, modifier 109 | end 110 | 111 | ---根据选项或探测结果获取sysroot选项列表 112 | ---@return table | nil --选项列表 113 | function get_sysroot_option() 114 | local cache_info = common.get_cache() 115 | ---sysroot缓存 116 | ---@type string | nil 117 | local sysroot = cache_info["sysroot"] 118 | ---根据sysroot获取选项列表 119 | ---@return table --选项列表 120 | local function get_option_list() 121 | local sysroot_option = "--sysroot=" .. sysroot 122 | -- 判断是不是libc++ 123 | local is_libcxx = (get_config("runtimes") or ""):startswith("c++") 124 | local libcxx_option = is_libcxx and "-isystem" .. path.join(sysroot, "include", "c++", "v1") or nil 125 | return { cxflags = { sysroot_option, libcxx_option }, ldflags = sysroot_option, shflags = sysroot_option } 126 | end 127 | if sysroot == "" then 128 | return nil -- 已经探测过,无sysroot可用 129 | elseif sysroot then 130 | return get_option_list() -- 已经探测过,使用缓存的sysroot 131 | end 132 | 133 | -- 检查给定sysroot或者自动探测sysroot 134 | sysroot = get_config("sysroot") 135 | local detect = sysroot == "detect" 136 | -- sysroot不为"none"或"detect"则为指定的sysroot 137 | sysroot = (sysroot ~= "none" and not detect) and sysroot or nil 138 | -- 若使用clang工具链且未指定sysroot则尝试自动探测 139 | ---@type boolean 140 | detect = detect and common.is_clang() 141 | cache_info["sysroot_set_by_user"] = sysroot and true or false 142 | if sysroot then -- 有指定sysroot则检查合法性 143 | assert(os.isdir(sysroot), string.format([[The sysroot "%s" is not a directory.]], sysroot)) 144 | elseif detect then -- 尝试探测 145 | ---@type string | nil 146 | local prefix 147 | if get_config("bin") then 148 | prefix = path.join(string.trim(get_config("bin")), "..") 149 | else 150 | prefix = try { function() return os.iorunv("llvm-config", { "--prefix" }) end } 151 | prefix = prefix and string.trim(prefix) 152 | end 153 | if prefix then 154 | -- 尝试下列目录:1. prefix/sysroot 2. prefix/../sysroot 优先使用更局部的目录 155 | for _, v in ipairs({ "sysroot", "../sysroot" }) do 156 | local dir = path.join(prefix, v) 157 | if os.isdir(dir) then 158 | sysroot = path.normalize(dir) 159 | cprint("detecting for sysroot ... ${color.success}%s", sysroot) 160 | break 161 | end 162 | end 163 | end 164 | end 165 | 166 | -- 更新缓存 167 | cache_info["sysroot"] = sysroot or "" 168 | common.update_cache(cache_info) 169 | 170 | if sysroot then 171 | return get_option_list() 172 | else 173 | if detect then 174 | cprint("detecting for sysroot ... ${color.failure}no") 175 | end 176 | return nil 177 | end 178 | end 179 | 180 | ---获取march选项 181 | ---@param target string | nil --目标平台 182 | ---@param toolchain string | nil --工具链类型 183 | ---@note 在target和toolchain存在时才检查选项合法性 184 | ---@return string | nil --march选项 185 | function get_march_option(target, toolchain) 186 | local cache_info = common.get_cache() 187 | ---@type string | nil 188 | local option = cache_info["march"] 189 | if option == "" then 190 | return nil -- 已经探测过,-march不受支持 191 | elseif option then 192 | return option -- 已经探测过,支持-march选项 193 | end 194 | 195 | ---探测march是否受支持 196 | ---@type string 197 | local arch = get_config("march") 198 | if arch ~= "none" then 199 | local march = (arch ~= "default" and arch or "native") 200 | local options = { "-march=" .. march } 201 | -- 在target和toolchain存在时才检查选项合法性 202 | if target and toolchain then 203 | import("core.tool.compiler") 204 | if toolchain == "clang" and target ~= "native" then 205 | table.insert(options, "--target=" .. target) 206 | end 207 | ---@type boolean 208 | local support = compiler.has_flags("cxx", table.concat(options, " ")) 209 | local message = "checking for march ... " 210 | cprint(message .. (support and "${color.success}" or "${color.failure}") .. march) 211 | if not support then 212 | if arch ~= "default" then 213 | raise(string.format([[The toolchain doesn't support the arch "%s"]], march)) 214 | end 215 | option = "" 216 | else 217 | option = options[1] 218 | end 219 | else 220 | option = nil -- 未探测,设置为nil在下次进行探测 221 | end 222 | else 223 | option = "" 224 | end 225 | 226 | -- 更新缓存 227 | cache_info["march"] = option 228 | common.update_cache(cache_info) 229 | return option 230 | end 231 | 232 | ---获取rtlib选项 233 | ---@return string | nil --rtlib选项 234 | function get_rtlib_option() 235 | local config = get_config("rtlib") 236 | return (common.is_clang() and config ~= "default") and "-rtlib=" .. config or nil 237 | end 238 | 239 | ---获取unwindlib选项 240 | ---@return string | nil --unwindlib选项 241 | function get_unwindlib_option() 242 | local config = get_config("unwindlib") 243 | local force = config:startswith("force") 244 | local lib = force and string.sub(config, 7, #config) or config 245 | local option = config ~= "default" and "-unwindlib=" .. lib or nil 246 | return (common.is_clang() and (force or get_config("rtlib") == "compiler-rt")) and option or nil 247 | end 248 | 249 | ---将mode映射为cmake风格 250 | ---@param mode string --xmake风格编译模式 251 | ---@return string | nil --cmake风格编译模式 252 | function get_cmake_mode(mode) 253 | ---@type map_t 254 | local table = { debug = "Debug", release = "Release", minsizerel = "MinSizeRel", releasedbg = "RelWithDebInfo" } 255 | return table[mode] 256 | end 257 | --------------------------------------------------------------------------------