1# 开源构建规范 2 3## 概述 4 5为指导OpenHarmony的开发者开展构建工作,提升构建系统的可重复性、可维护性,提高产品构建质量,构建规范工作组分析总结了各种典型的构建问题,提炼相应的构建规则和建议,制订了本规范。 6 7## 构建总体原则 8 9**P01 构建过程自动化,从构建启动开始到构建最终结束,中间过程不能手工干预。** 10手工操作容易出错,且浪费时间。将所有的构建操作变成自动化的,从而使构建变得高效、可靠。 11 12**P02 构建工程和构建环境代码化。** 13使用高阶构建框架CMake/Maven/Gradle等描述构建工程;使用Ansible/Dockerfile等描述构建环境。 14使用高阶构建框架的目的是向构建人员隐藏构建系统的复杂性。 15 16**P03 构建过程可重复、可追溯。** 17管理构建依赖,始终显式指定固定依赖版本号,确保构建依赖版本一致;将构建环境信息/构建工程作为配置项纳入配置管理,确保构建工程可追溯。 18 19**P04 构建脚本简洁清晰,易于维护。** 20构建脚本也是代码,构建脚本首先是为阅读它的人而编写的,好的脚本应当可以像故事一样发声朗诵出来。 21 22**P05 构建标准化** 23构建目录结构、构建依赖、构建初始化、构建入口、命名等进行标准化约束,使得公司所有产品、平台和组件的构建风格一致,便于构建管理和维护。 24 25## 构建工程 26 27### 公共规则 28 29#### 一键式构建 30 31##### G.COM.01 采用构建脚本,按照交付单元实现一键式自动化构建。 32 33一键式自动化构建是指同一个构建环境下,从构建启动开始到最终结束(最终交付的包生成),中间过程禁止人工干预。 34人工干预活动包括但不限于:构建过程中,使用IDE界面进行手工设置、创建或者删除文件目录、创建文件、复制文件、移动文件、删除或者重命名文件、手工设置文件属性、压缩/解压缩文件等。 35交付单元是指可独立编译、加载、部署和运行的产品/平台/组件。 36 37【级别】要求 38 39【描述】一键式构建大幅降低构建人员操作复杂度。 40 41【错误示例】某组件的一键式构建只能通过CI系统触发,没有一键式的本地构建。 42 43【错误示例】某组件需要在Xplorer IDE界面手工设置内存映射地址后,再手工编译。 44 45【错误示例】某组件需要手工创建r6c03_view\r6c03_client_view目录。 46 47【正确示例】使用python脚本自动化创建目录: 48 49```python 50dir_src = os.getcwd() 51dir_client_view = r"r6c03_client_view" 52# 处理路径使用os.path可以屏蔽系统差异 53dir_mk = os.path.join(dir_src, dir_client_view) 54 55cmd = "{0} {1}".format("mkdir", dir_mk) 56cmd_re = subprocess.run(cmd) 57``` 58 59#### 构建目录 60 61##### G.COM.02 构建过程中禁止删除或修改源代码文件及其目录结构。 62 63【级别】禁止 64 65【描述】 66 67- 构建过程中删除或修改源代码目录结构,会导致构建过程不可重复。 68 69- 构建过程中,构建输出(包括目标文件、临时文件和构建日志)不能污染源码目录; 70 71- 构建过程中,避免修改源文件,包括但不限于拷贝、移动、执行dos2unix进行了源代码的格式转换等,源文件的修改应该在构建前的代码准备阶段完成; 72 73- 工具自动生成源代码应该在构建前的准备阶段完成,如果构建过程中使用工具自动生成源代码,工具自动生成的源代码必须和已有源代码目录隔离,以便区分高价值的源代码和低价值的可重新生成的代码,降低构建系统的复杂性。 74 75【例外】构建补丁时,可能会新增或调整部分源代码。 76 77##### G.COM.12 构建过程中创建的文件和目录应提供合适的权限。 78 79【级别】要求 80 81【描述】构建过程中可能需要创建目标系统的目录或文件,这些目录和文件应符合权限最小化的设计。 82例如,构建过程中应尽量避免在Linux系统中创建“777”权限的目录或文件。 83 84Linux系统中常见的目录文件和权限可以参考《Linux安全配置操作规范》。 85 86#### 构建初始化 87 88##### G.COM.03 每个组件提供clean命令。 89 90- 当clean不带任何参数时,清除该层级构建工程下的所有目标文件、临时文件和构建日志,并递归调用下层构建工程的clean,使该构建工程恢复到初始状态; 91- 当clean带参数时,只清除与之对应的构建生成的目标文件、临时文件和构建日志。 92 93【级别】要求 94 95【描述】构建前进行clean是为了避免本次构建受到历史构建残留文件和构建日志的影响,确保构建可重复。必须支持不带任何参数clean;带参数的clean,是为了满足日常交付过程和开发人员本地构建的诉求,不作强制要求。 96 97【正确示例】 98 99``` 100base_dir 101 |---build.suffix 102 |---logs 103 |---component_depository_1 104 |---build.suffix 105 |---logs 106 |---component_depository_2 107 |---build.suffix 108 |---logs 109 110#不带参数 111base_dir/build.suffix clean 112#....分别调用component_depository_1和component_depository_2的clean 113 114#带参数:组件名 115base_dir/build.suffix clean component_depository_1 116#....调用component_depository_1的clean 117 118#带参数 119component_depository_1/build.suffix clean makebin hert umpt 120#....调用component_depository_1的umpt单板链接任务的clean,支持详细参数的clean主要应用于内部开发和构建。 121``` 122 123##### G.COM.04 每个组件发布构建,必须保证构建环境中没有历史构建遗留件。 124 125【级别】要求 126 127【描述】首次下载代码,构建环境已经初始化,构建环境本身确保没有历史构建遗留件,可以不用执行clean命令;如果执行过构建的,必须使用clean命令清除历史构建遗留件。 128 129#### 全量构建 130 131##### G.COM.05 对于版本发布构建,归档的产品全量交付件(含所依赖的所有平台和组件)必须全部重新编译,禁止使用增量编译,禁止使用手工替换文件等方式修改安装盘。 132 133版本发布构建是指产品(含所依赖的所有平台和组件)对外正式发布版本的构建。 134 135【级别】要求 136 137【描述】修改文件后增量编译,会导致部分二进制文件没有更新,造成新的安全编译选项未集成到版本,编译结果不一致。手工替换文件可能会造成构建不可重复、不一致。 138 139 140#### 构建配置 141 142构建配置数据和构建脚本分离,避免构建工程架构腐化。源码路径、编译选项、目标文件路径等配置与构建脚本放到不同的文件,降低构建脚本维护成本。 143 144##### G.COM.06 禁止使用与操作系统强绑定的文件(如excel)作为构建配置文件。建议使用跨平台的标准配置文件(如XML)来存放配置选项。 145 146【级别】要求 147 148【描述】使用excel作为配置文件带来的问题: 149 150- 产品和平台编译过程中,使用excel作为配置文件,都将调用微软的OfficeAPI,每次访问excel表格都会在后台打开excel,处理速度慢。 151 152- 大量的excel配置需要手动点界面进行操作,可管理性差。 153 154#### 构建日志 155 156##### G.COM.07 构建输出的日志简洁明晰,构建日志的格式为时间戳+模块名(可选)+日志信息等级+日志内容。 157【级别】要求 158 159【描述】建议时间戳格式采取“日期和时间”,如"MM/dd/yyyy HH:mm:ss"。 160 161日志信息等级分为error/warning/informational,级别可以全写,也可以简写;对应的简写为: 162 163| 级别(大小写都可以)| 简写(大小写都可以)| 164| :---------: | :--------------------------: | 165| error | ERROR | 166| warning | WARN | 167| information | INFO | 168 169建议使用“[]”作分隔符。 170 171【正确示例】 172[05/21/2020 00:12:40] [ERROR] mkdir: cannot create directory Permission denied. 173 174【例外】整个日志由工具自动输出的,可用使用以下方式跳过整个日志文件:在日志的最前方(尽可能靠前)输出"This project is built using "+工具名,如"This project is built using CMake."。 175 176##### G.COM.08 构建日志出现error信息表示构建失败,必须终止构建。 177 178【级别】要求 179 180【描述】出现error信息一般是需要人工干预的构建错误,例如配置的环境变量错误,工具的版本错误,操作系统错误等等;或者软件源代码不对。对于版本发布构建,必须消除构建过程中所有的error消息,不允许屏蔽构建error信息。 181 182【错误示例】某组件构建成功,但构建日志中包含大量的fail、Critical、cannot、not found、missing、no input files等异常信息,令人困惑。 183 184##### G.COM.09 构建日志文件只保留本次构建的日志,避免本次构建的日志与历史构建的日志混淆。 185 186【级别】要求 187 188【描述】构建日志文件保留历史构建日志会导致混淆错误,比如:最新构建是失败的,由于保留有历史成功构建日志,会误认为最新这次构建是成功的。 189 190##### G.COM.10 每条日志建议增加对应的模块名,用于问题的快速定界。 191 192【级别】建议 193 194【描述】在日志量较大时,很难快速锁定问题责任模块,需要在日志上加以区分。 195 196【例外】CMake等工具的原生日志,因为输出带有对应模块路径,可以界定问题边界,不用特殊增加模块名维测信息。 197 198#### 构建用户 199 200##### G.COM.11 禁止使用超级管理员用户root和系统用户执行构建,应该使用普通user账户执行构建。 201 202【级别】要求 203 204【描述】超级管理员用户root和系统用户具有比较高的系统权限,使用此类账户执行构建可能导致构建环境被篡改。 205 206安装态可以使用root用户;执行态使用普通user账户,如果需要使用sudo提升权限的,请遵守《身份和访问管理安全设计规范》。 207 208#### 构建输出文件 209 210##### G.COM.12 构建输出文件命名后缀遵守业界约定。 211 212【级别】要求 213 214【描述】错误的后缀命名令人误解。 215 216对lib库、obj等构建输出文件的文件缀,应遵从构建工具默认的命名规则。 217 218【错误示例】某文本文件命名为XXX.lib。 219 220【错误示例】某object文件命名为XXX.a。 221 222【错误示例】某静态库命名无后缀,命名为libxxx。 223 224【正确示例】业界如下网址可以查询常见的文件后缀命名约定:http://www.fileextension.org/ , https://fileinfo.com/ , https://www.file-extensions.org/, http://file-extension.net/ 。 225 226下面是一些常见的文件后缀的命名约定: 227 228| 文件后缀名 | 类型约定 | 文件后缀名 | 类型约定 | 229| ---------- | -------------------- | ---------- | --------------- | 230| .a | 静态库 | .so | 动态库 | 231| .o | object文件 | .7z | 7zip压缩文件 | 232| .tar | tar存档文件 | .gz/.gzip | GNU压缩存档文件 | 233| .pack | java pack200压缩文件 | .rar/.rar5 | rar压缩包 | 234 235### C/C++构建工程 236 237#### 构建目录 238 239##### G.C&C++.01 构建目录结构标准化。 240 241构建目录按用途分为源树Source Tree、构建中间件树Build Tree、构建安装树Install Tree三种。 242 243- Source Tree是保存源码和构建脚本的目录。 244- Build Tree是保存构建中间件的目录,目录名称一般为"build"。 245- Install Tree是保存构建发布件的目录,目录名称固定为"output"。 246 247Source Tree、Build Tree和Install Tree目录隔离,互相不重叠,没有交集,即不允许一个目录同时承担两种及以上的用途,譬如一个目录既作为Source Tree存放源码,又作为Build Tree存放编译中间件,这是不允许的。 248 249Source Tree包含下列文件和目录: 250 251- 构建工具入口文件,如CMakeLists.txt,CMakeLists.txt中通过add_subdirectory()命令添加子目录,CMake将自动迭代调用子目录中的CMakeLists.txt,并逐级向下展开。 252- build.suffix脚本文件,该文件是一键式构建入口,仅调用该脚本即可完成构建。".suffix"表示对应的构建脚本语言后缀,譬如".bat",".sh",".py"等。 253- config.suffix配置文件,该文件用于存放构建配置项,是唯一的配置文件入口。 254- 构建脚本目录,可选,如cmake目录,用于保存CMake脚本文件。CMake脚本文件包括宏、函数、toolchain等, CMakeLists.txt通过include()命令包含CMake脚本文件,并调用其中的宏、函数等。 255- 组件代码目录,用于存放各组件的源码及构建脚本。 256- 上述文件和目录,只有CMakeLists.txt、build.suffix、config.suffix这三个文件是必需的,其它文件或者目录仅用作示例,不强制要求。 257 258Build Tree包含下列目录: 259 260- build目录,用于存放构建中间件。该目录可能在构建过程中创建,在git库上可能没有该目录。 261- 有的工程已经将build目录用于保存构建脚本,可以创建别的目录作为Build Tree。 262 263Install Tree包含下列目录: 264 265- output目录,用于存放交付件。该目录可能在构建过程中创建,在git库上可能没有该目录。 266 267【级别】要求 268 269【描述】 270 271典型目录结构如下: 272 273``` 274base_dir 275 |---CMakeLists.txt ---| 276 |---build.suffix | 277 |---config.suffix | 278 |---cmake |--> Source Tree 279 |---component_1 | 280 |---component_2 | 281 |---...... | 282 |---component_n ---| 283 |---build ------> Build Tree 284 |---output ------> Install Tree 285``` 286 287各组件的目录结构与顶层的目录结构类似,譬如: 288 289``` 290component_1 291 |---CMakeLists.txt ---| 292 |---build.suffix | 293 |---config.suffix | 294 |---cmake |--> Source Tree 295 |---module_1 | 296 |---module_2 | 297 |---...... | 298 |---module_n ---| 299 |---build ------> Build Tree 300 |---output ------> Install Tree 301``` 302 303##### G.C&C++.02 构建过程中禁止以任何形式修改Source Tree。 304 305【级别】建议 306 307【描述】构建过程中修改Source Tree会导致构建过程不可重复。 308 309常见的修改Source Tree的操作有: 3101)打补丁 3112)打点 3123)裁剪 3134)自动生成源码 3145)先修改源码然后还原 3156)增加/修改/删除临时文件或者目录 3167)修改文件/目录属性或者格式,譬如修改文件可执行权限、dos2unix等 317 318建议解决方案如下: 3191)将代码拷贝到Build Tree,然后打补丁,编译。 3202)打点工具修改源码,使得构建过程不可信,因此禁止在构建过程中使用打点工具。应将打点后的代码上传到代码库,使用打点后的代码进行构建。 3213)裁剪是独立的源码交付需求,裁剪可以看做是代码准备阶段。裁剪前的版本和裁剪后的版本都必须满足在构建过程中不修改Source Tree。 3224)自动生成的源码应放在Build Tree下。 3235)先修改源码然后还原是掩耳盗铃,构建过程中源码已经发生了变更。 3246)临时文件或者目录都应该放在Build Tree下。 3257)必须保证代码库中的文件属性和格式是正确的,而不是构建时修改。 326 327检验Source Tree是否发生变化的方法之一:编译完成后在源码目录下执行git status命令,不能有任何变更。先修改后还原导致的Source Tree变更,通过git status可能检测不出来。 328 329【例外】 3301)git status检测到Build Tree和Install Tree这两个目录的变更是允许的。 3312)git status检测到由于裁剪导致的变更是允许的。 332 333##### G.C&C++.03 Windows构建根目录建议为D:\交付单元的名称+版本号(可选);Linux构建根目录建议为/usr1/交付单元的名称+版本号(可选)。 334 335【级别】建议 336 337【描述】构建根目录按交付单元的名称+版本号命名,禁止使用build或code等无法区分交付单元的目录名称。 338清晰的构建目录结构,便于测试人员配置构建参数、执行一键式构建入口和对比构建结果。 339根目录示例如下: 340 341``` 342D:\Offering [Version,可选]或/usr1/Offering [Version,可选] 343``` 344 345##### G.C&C++.04 构建过程中生成的所有中间件保存在Build Tree中。 346 347【级别】要求 348 349【描述】构建过程中产生的中间件包括构建工具CMake自动生成的makefile、构建脚本自动生成的源码、构建脚本拷贝的源码及补丁、编译产生的object文件、库文件、可执行程序、构建日志等等。如果中间件放在Build Tree以外的目录,势必污染Source Tree或者Install Tree。因此,所有中间件都要保存在Build Tree中。Build Tree仅用于保存构建中间件,不能将Source Tree下某个放置源码或者构建脚本的目录用作Build Tree。 350Build Tree下创建构建日志子目录logs,构建日志后缀文件命名为.log。 351 352##### G.C&C++.05 支持指定Source Tree和Install Tree以外的任意目录作为Build Tree。 353 354【级别】要求 355 356【描述】支持指定Source Tree和Install Tree以外的任意目录作为Build Tree,做到构建过程与目录无关。在哪个目录下执行构建,哪个目录就是Build Tree,编译中间件就保存在哪个目录下。Build Tree的目录名称一般为“build”,也可以使用其它名称。 357 358【正确示例】使用CMake系统变量CMAKE_BINARY_DIR和CMAKE_CURRENT_BINARY_DIR访问Build Tree,避免Build Tree与Source Tree产生耦合。 359 360##### G.C&C++.06 所有发布件保存在Install Tree中。 361 362【级别】要求 363 364【描述】本地编译场景下,发布件直接"install"到HOST Computer上并运行。交叉编译场景下,发布件并不在HOST Computer上运行,而是在TARGET Computer上运行。 365 366发布件包括库文件、可执行程序、包文件、头文件等,是组件对外的二进制接口。所有发布件都保存在Install Tree中,不应将发布件放在Install Tree以外的目录下。 367 368Install Tree只用于保存发布件,不应将编译中间件放在Install Tree中。 369 370##### G.C&C++.07 支持指定Source Tree和Build Tree以外的任意目录作为Install Tree。 371 372【级别】要求 373 374【描述】支持指定Source Tree和Build Tree以外的任意目录作为Install Tree,做到构建过程与目录无关。Install Tree的目录名称固定为“output”。 375 376【正确示例】CMake构建工程应支持通过系统变量CMAKE_INSTALL_PREFIX指定Install Tree的根目录。 377 378#### 构建入口 379 380##### G.C&C++.08 每个交付单元的构建入口单一。构建脚本入口名称统一命名为build.suffix,并且路径要求在构建根目录下。 381 382【级别】要求 383 384【描述】通过使用一致的构建入口点,构建过程可以变得更加高效和可自动执行。每个交付单元只有单一构建入口,便于一键式自动构建。 385 386【错误示例】如下构建有多个入口点,如果没有说明文档,无法确认哪一个入口是正确的,造成选择困难。 387build.bat 388build_all.sh 389build_v6.sh 390 391【正确示例】一键式构建脚本build.sh的典型写法如下: 392 393```bash 394#!/bin/bash 395 396if [ -d "build" ]; then 397 rm -fr build/* 398else 399 mkdir build 400fi 401 402if [ -d "output" ]; then 403 rm -fr output/* 404else 405 mkdir output 406fi 407 408cd build 409cmake .. 410 411cpu_processor_num=$(grep processor /proc/cpuinfo | wc -l) 412job_num=$(expr "$cpu_processor_num" \* 2) 413echo Parallel job num is "$job_num" 414make -j"$job_num" 415``` 416 417##### G.C&C++.09 支持指定target进行构建。 418 419【级别】要求 420 421【描述】日常开发场景下,通过指定target编译,开发人员只需要编译修改了的代码,不需要编译全部代码,达到快速验证的目的。编译工程应支持指定target进行构建,从而满足灵活多变的编译调试需求。 422 423【正确示例】典型命令如下: 424 425``` 426base_dir # cd build 427base_dir/build # cmake .. 428# 编译全部目标 429base_dir/build # make 430# 编译特定目标 431base_dir/build # make target_name 432``` 433 434##### G.C&C++.10 支持重复编译。 435 436【级别】要求 437 438【描述】编译成功后,不对源代码做任何修改,不清理上次编译的中间件和发布件,不修改编译环境,再次执行编译,必须能重复编译成功。 439 440##### G.C&C++.11 支持增量编译。 441 442【级别】建议 443 444【描述】日常开发场景下,增量编译可以缩短编译时间,提高开发效率,因此建议支持增量编译。 445 446##### G.C&C++.12 支持并行编译。 447 448【级别】要求 449 450【描述】通过"make -jN"命令进行并行编译,可以提高编译速度。本规则仅适用于使用make工具的工程。 451 452支持jobserver统一调度,使整个工程的负载最优。不能出现下面两个告警: 453 454``` 455warning: jobserver unavailable: using -j1. Add '+' to parent make rule. 456warning: -jN forced in submake: disabling jobserver mode. 457``` 458 459支持jobserver的方法如下: 460 4611. 通过$(MAKE)直接调用make命令 462 463 ```cmake 464 ExternalProject_Add(foo 465 SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/foo 466 CONFIGURE_COMMAND sh configure_ext.sh 467 BUILD_COMMAND $(MAKE) 468 ) 469 ``` 470 4712. 通过shell脚本调用make命令 472 473 ```cmake 474 ExternalProject_Add(foo 475 SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/foo 476 CONFIGURE_COMMAND sh configure_ext.sh 477 BUILD_COMMAND sh build_ext.sh $(MAKE) 478 ) 479 ``` 480 481 build_ext.sh内容如下: 482 483 ```bash 484 #!/bin/bash 485 486 make 487 ``` 488 489 注意:build_ext.sh不需要解析和使用参数$(MAKE)。 490 4913. 通过python脚本调用make命令 492 493 ```cmake 494 ExternalProject_Add(foo 495 SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/foo 496 CONFIGURE_COMMAND sh configure_ext.sh 497 BUILD_COMMAND python build_ext.py $(MAKE) 498 ) 499 ``` 500 501 build_ext.py内容如下: 502 503 ```bash 504 #!/usr/bin/python 505 # -*- coding: UTF-8 -*- 506 507 import subprocess 508 509 def main(): 510 child = subprocess.Popen("make", close_fds=False) 511 ret = child.wait() 512 return 513 514 if __name__ == '__main__': 515 main() 516 ``` 517 518 注意:build_ext.py不需要解析和使用参数$(MAKE)。 519 520 521#### 构建依赖 522 523##### G.C&C++.13 定义一个构建依赖文件dependence.xml,文件中描述构建依赖的所有组件。构建脚本自动读取该依赖文件,用于制作最终的软件包。 524 525【级别】建议 526 527【描述】按照依赖文件进行软件包制作,避免在构建脚本中定义依赖组件,提高构建过程可维护性。 528 529#### 构建配置 530 531##### G.C&C++.14 构建根目录的config.suffix配置文件是整个交付项目唯一的配置入口。 532 533【级别】要求 534 535【描述】顶层的config.suffix中,应暴露最少的配置项,只需要用户配置的构建环境、构建工具相关的信息。 536 537【例外】如果构建配置的内容非常少,采取系统键值对配置项,配置文件可以命名成config.conf。 538 539### GN 编写规范 540 541#### 编译规范 542 543##### 规则1.1 禁止在gn中调用外部编译工具编译软件模块 544 545【级别】禁止 546 547【描述】需要将外部组件移植成gn的编译形式,避免编译过程对环境产生不必要的依赖,而且可获得编译框架提供的公共能力,包括不限于:安全编译选项,ASAN等。 548 549【反例】在gn中使用action调用automake和Make来编译三方组件。 550 551【例外】Linux Kernel 编译框架实际完成的用户态程序编译,内核完全可以在编译框架之外完成独立编译。某些平台实现为了实现一键编译,使用gn将内核编译加在编译过程中,是可以接受的。 552 553##### 规则1.2 禁止在模块的gn文件中,再次添加编译系统已经添加的安全编译选项 554 555【级别】禁止 556 557【描述】对于全局已经添加的默认选项,模块开发者应当知晓,不需要为了满足内外部规则再次添加。 558 559| 编译选项 | 编译参数 | 默认值 | 560|---------|------------|------------| 561| 栈保护 | -fstack-protector-strong| 开 | 562| Fortify Source | -D_FORTIFY_SOURCE=2 -O2 | 开 | 563 564【反例】在模块的编译添加 -fstack-protector-strong 565 566##### 规则1.3 禁止在gn中添加和默认编译选项相反的编译选项 567 568【级别】禁止 569 570【描述】默认的编译选项代表了系统的默认能力,自研模块有特殊情况需要去掉部分能力,必须有足有的理由。 571 572【反例】在自研模块中添加 -wno-unused 以消除编译告警。 573 574【例外】移植三方组件,或者使用因为三方组件时,可根据三方组件的要求覆盖默认的编译选项。 575 576##### 规则 2.1 使用gn format 对添加或者修改的gn文件进行格式化,满足格式和排版的需求 577 578【级别】要求 579 580##### 规则 2.2 编写action时,使用python而不是shell 581 582【级别】建议 583 584【描述】python 环境更容易保持统一,可以比较容易的多重操作系统上运行,并且扩展性可读性可测试更好。 585 586##### 规则 2.3 禁止在gn和ninja执行过程修改源码目录的内容 587 588【级别】禁止 589 590【描述】包括但不限于给源码目录打patch,向源码目录中拷贝,在源码目录中执行编译,在源码目录生成中间文件等。 591 592##### 规则 2.4 编译脚本的编码格式设置为utf-8,换行符设置为unix格式 593 594【级别】要求 595 596【反例】在windows上编写脚本后,使用了中文注释并保存为本地编码。 597 598