【详细深入分析OpenHarmony编译流程】

详细深入分析OpenHarmony编译流程

  • 前言
  • 编译入口
    • 源码根目录
  • 编译命令
  • 记录源码根目录位置
  • 开始构建
    • 获取python解释器
  • hb主程序
    • build模块初始化
    • build模块执行
  • build和set参数解析
    • 参数配置文件读写
  • 配置对象的初始化
    • 配置文件位置
    • 产品配置信息的获取
  • prebuild
    • 产品名称解析
    • 目标CPU解析
    • ccache参数解析
    • pycache参数解析
    • 构建目标解析
    • ninja配置参数解析
    • 全量编译开关
  • preload
    • preloader初始化
      • 记录输入目录和文件
      • 记录各输出文件的路径
      • 记录产品相关信息
        • 记录系统能力信息
        • 记录设备信息
        • 生成部件信息列表
          • 继承合并一些其它部件集合
          • 获取产品部件集合
          • 获取本产品额外部件
          • 增加本设备额外部件
        • 生成build var列表
        • 移除不需要的部件
    • 生成build.prop
    • 生成build_config.json
    • 生成部件json
    • 生成部件配置json
    • 生成gn参数属性文件
    • 生成特性集合
    • 生成系统能力记录
    • 生成排除模块记录
    • 生成子系统配置json
    • 生成系统能力配置json
  • load
    • 严格模式
    • 增量构建
    • 构建范例
    • 构建平台名称
    • 构建测试用例
    • 忽略API检查
    • 加载测试配置
    • loader
      • loader初始化
        • 扫描已知子系统中的部件
          • 根据子系统路径配置读取子系统信息
          • 检查非法路径
          • 寻找部件
        • 获取存在部件的子系统列表
          • 保存部件信息
        • 替换冗余部件
          • 替换一个部件
        • 获取平台信息
          • 获取平台和工具链的映射
      • 显示loader参数
      • 检查部件配置信息
      • 生成子系统配置
      • 生成目标平台部件
      • 生成系统能力
      • 生成桩目标
      • 基于源码生成平台部件
      • 生成目标gn
      • 生成伪目标构建文件
      • 生成必要的部件目标
      • 生成必要的部件目标列表
      • 生成源码对应的标识
      • 生成自动安装部件
      • 生成平台列表
      • 生成部件差异化信息
      • 生成测试框架信息
      • 检查产品部件特性
      • 生成系统能力文件
  • gn前处理
  • gn处理
    • 构建类型
    • 日志等级
    • 测试集选择
    • gn参数
    • gn flag
    • 快速重建
    • gn执行
  • gn后处理
  • ninja前处理
  • ninja处理
    • ninja错误后继续
    • 只生成gn
    • ninja执行
  • ninja后处理
    • root或user构建
    • 设备类型
    • 打包镜像
    • rom尺寸统计
    • ccache性能度量
    • 汇总编译警告
    • ninja性能度量
    • 计算overlap rate
    • 依赖分析清理
  • postbuild
    • 清理构建参数

前言

OpenHarmony做为复杂的操作系统软件,其编译构建也比较复杂。本文主要记录作者自己在阅读编译脚本代码后对其的认识,不代表官方观点,不足之处还请批评指正。

本文顺着编译流程阅读和解释编译脚本代码,即执行时间靠前的代码先解释,执行时间靠后的代码后解释。

本文参考代码为2023年3月30日master分支代码。

为了方便查看OpenHarmony代码,作者搭建了一个服务器,欢迎使用https://www.lengqinjie.xyz。
对于master分支代码,此服务器上的代码每日更新一次,所以当你看到时,可能和本文有差异,属于正常情况。

你也可以自行从gitee下载此版本代码,使用如下命令,git工具下载方法请参考互联网。

git clone https://gitee.com/openharmony/build.git -b master
cd build
git reset --hard 9574463e6f568f81b8d89134306052782c1f4c14

编译入口

共有3种形式的编译入口

  1. hb build
  2. ./build.sh
  3. ./build.py

3种形式的编译入口最终都汇总到hb build。即内部实际上是统一的。下面以build.py为例进行讲解。

源码根目录

源码根目录下有很多子目录,这些子目录大多数为OpenHarmony各软件子系统的实现,其中1个目录为build目录,即本文重点讨论的内容,此目录内主要为编译构建脚本,主要是python语言实现。

为了提供方便,源码根目录下有2个符号链接,引用了编译构建的脚本文件

build.py -> build/lite/build.py
build.sh -> build/build_scripts/build.sh

因此在源码根目录下执行这2个文件中的一个就能够启动编译构建过程。还有一种启动方式是hb命令,稍后再讲解。

编译命令

以build.py为例,在源码根目录下执行

./build.py -p rk3568

这里-p rk3568意思是编译构建产品(rk3568)。当然还支持很多其它参数,为了简化,这里只列出这个最重要的参数

记录源码根目录位置

build/lite/build.py

0071 def main():
0072     root_path = os.path.dirname(os.path.abspath(__file__))
0073     ret_code = set_root_path(root_path)
0074     if ret_code != 0:
0075         return ret_code
0076     return build(root_path, sys.argv[1:])
0077 
0078 
0079 if __name__ == "__main__":
0080     sys.exit(main())

注意72行代码中的__file__指的是源码根目录下的build.py,而不是build/lite/build.py
因为是在源码根目录下执行的,所以root_path就是源码根目录。
build/lite/build.py

0053 def set_root_path(path):
0054     sys.path.insert(0, os.path.join(path, 'build/lite'))
0055     module = importlib.import_module('hb_internal.set.set')
0056     return module.set_root_path(root_path=path)

然后将build/lite加入python程序搜索路径,方便后续python脚本查找自定义模块,55行代码就用上了54行代码的结果,在build/lite子目录内查找hb_internal.set.set模块并导入,最后执行此模块的设置源码根目录的方法
build/lite/hb_internal/set/set.py

0048 def set_root_path(root_path=None):
0049     config = Config()
0050     if root_path is None:
0051         try:
0052             hb_info(f'hb root path: {config.root_path}')
0053             return 0
0054         except OHOSException:
0055             root_path = get_input('[OHOS INFO] Input code path: ')
0056     config.root_path = root_path
0057     return 0

这里49行为一个配置对象,整个编译构建系统只有1个config对象(后续再解释)。这里就把root_path记录到config对象中(56行)。

开始构建

build/lite/build.py

0059 def build(path, args_list):
0060     python_executable = get_python()
0061     cmd = [python_executable, 'build/hb/main.py', 'build'] + args_list
0062     for args in args_list:
0063         if "using_hb_new=false" in args:
0064             cmd = [python_executable, 'build/lite/hb/__main__.py', 'build'] + args_list
0065             break
0066     if not os.path.exists(os.path.join(path,'build/hb/main.py')):
0067         cmd = [python_executable, 'build/lite/hb/__main__.py', 'build'] + args_list
0068     return check_output(cmd, cwd=path)

源码根目录记录成功以后,开始进行系统构建(build)。首先获取python解释器(60行), 然后再构造命令行参数(61行)。紧接着进行新老版本构建的判断,分别构造不同的命令行。本文不解释老版本构建方式,即不考虑(62到67行之间的内容)。最后执行构建命令并检查执行结果(68行)。

从61行代码可以看出,这里等价于执行

hb build args_list

前提条件是对build/hb目录中python代码进行了安装动作

pip install build/hb

获取python解释器

build/lite/build.py

0025 def get_python():
0026     hb_main = importlib.import_module("hb.__main__")
0027     topdir = hb_main.find_top()
0028     python_base_dir = os.path.join(topdir, 'prebuilts/python')
0029     if os.path.exists(python_base_dir):
0030         python_dir = hb_main.search(python_base_dir, 'python3')
0031         return os.path.join(python_dir, 'python3')
0032     else:
0033         print("please execute build/prebuilts_download.sh")
0034         sys.exit()

这里的主要目的是判断源码根目录下是否有prebuilts/python这个目录,如果没有的话,则需要执行
build/prebuilts_download.sh(当然是在源码根目录执行了)去下载对应的python解释器程序文件,当然这里还会下载其它一些工具,

这样做的目的是减少用户自行安装软件失败的可能性,因为这些软件对版本有要求,随着openharmony代码的更新,可能需要新的工具软件或者软件的新版本。通过上述脚本下载的工具软件都会存放在prebuilts目录下。
上述程序返回的是python解释器程序的文件路径。
build/lite/hb/__main__.py

0035 def find_top():
0036     cur_dir = os.getcwd()
0037     while cur_dir != "/":
0038         hb_internal = os.path.join(cur_dir, 'build/lite/hb_internal')
0039         if os.path.exists(hb_internal):
0040             return cur_dir
0041         cur_dir = os.path.dirname(cur_dir)
0042     raise Exception("Please call hb utilities inside source root directory")

上述代码的意思是从build/lite/hb目录开始一直往父目录查找,如果找到一个目录,其内部存在build/lite/hb_internal目录,则此目录为源码根目录,最后返回源码根目录(40行)

0045 def search(findir, target):
0046     for root, dirs, files in os.walk(findir):
0047         if target in files:
0048             return root
0049     return False

这里主要是从目录findir中去寻找target文件,可以是在子目录内(递归查找),如果文件存在,返回文件所在的目录

hb主程序

当python解释器成功获取后,就会使用构造的参数调用hb主程序
build/hb/main.py

0104     @staticmethod
0105     @throw_exception
0106     def main():
0107         main = Main()
0108         module_type = sys.argv[1]
0109         if module_type == 'build':
0110             module = main._init_build_module()
0111         elif module_type == 'set':
0112             module = main._init_set_module()
0113         elif module_type == 'env':
0114             module = main._init_env_module()
0115         elif module_type == 'clean':
0116             module = main._init_clean_module()
0117         elif module_type == 'tool':
0118             module = main._init_tool_module()
0119         elif module_type == 'help':
0120             for all_module_type in ModuleType:
0121                 LogUtil.hb_info(Separator.long_line)
0122                 LogUtil.hb_info(Arg.get_help(all_module_type))
0123             exit()
0124         else:
0125             raise OHOSException(
0126                 'There is no such option {}'.format(module_type), '0018')
0127         try:
0128             module.run()
0129         except KeyboardInterrupt:
0130             for file in os.listdir(ARGS_DIR):
0131                 if file.endswith('.json') and os.path.exists(os.path.join(ARGS_DIR, file)):
0132                     os.remove(os.path.join(ARGS_DIR, file))
0133             print('User abort')
0134             return -1
0135         else:
0136             return 0
0137 
0138 
0139 
0140 if __name__ == "__main__":
0141     sys.exit(Main.main())

本程序有2种使用方式

  1. hb (build | set | clean | env | tool | help) args
  2. python3 build/hb/main.py (build | set | clean | env | tool | help) args
    第一种方式需要安装此python模块,即
pip install build/hb

第二种方式即上文介绍的方式,参考build/lite/build.py第61行。

build模块初始化

build/hb/main.py

0062     def _init_build_module(self) -> BuildModuleInterface:
0063         args_dict = Arg.parse_all_args(ModuleType.BUILD)
0064 
0065         if args_dict.get("product_name").arg_value != '':
0066             set_args_dict = Arg.parse_all_args(ModuleType.SET)
0067             set_args_resolver = SetArgsResolver(set_args_dict)
0068             ohos_set_module = OHOSSetModule(set_args_dict, set_args_resolver, "")
0069             ohos_set_module.set_product()
0070 
0071         preloader = OHOSPreloader()
0072         loader = OHOSLoader()
0073         generate_ninja = Gn()
0074         ninja = Ninja()
0075         build_args_resolver = BuildArgsResolver(args_dict)
0076 
0077         return OHOSBuildModule(args_dict, build_args_resolver, preloader, loader, generate_ninja, ninja)

63行先将所有build能识别的参数进行解析,并存入args_dict字典中。
如果参数里面有"product_name", 那么还要执行set的逻辑(65到69行)。本文中的-p参数就是product_name的简写,所以这段逻辑会执行。
71到75行初始化几个对象,代表编译构建的几个阶段:预加载、加载、生成ninja脚本,执行ninja脚本,以及初始化build阶段各参数的解析器。
最后(77行)用这些对象初始化构建模块

build模块执行

build/hb/modules/ohos_build_module.py

0062     @throw_exception
0063     def run(self):
0064         try:
0065             super().run()
0066         except OHOSException as exception:
0067             raise exception
0068         else:
0069             LogUtil.hb_info('{} build success'.format(
0070                 self.args_dict.get('product_name').arg_value))
0071             LogUtil.hb_info('Cost time:  {}'.format(self.build_time))

run函数实际上使用的是父类的函数,如下
hb/modules/interface/build_module_interface.py

0061     def run(self):
0062         try:
0063             self._prebuild()
0064             self._preload()
0065             self._load()
0066             self._pre_target_generate()
0067             self._target_generate()
0068             self._post_target_generate()
0069             self._pre_target_compilation()
0070             self._target_compilation()
0071         except OHOSException as exception:
0072             raise exception
0073         else:
0074             self._post_target_compilation()
0075         finally:
0076             self._post_build()

可以看到实际上run定义了一个编译构造的流程,分别是

  1. prebuild
  2. preload
  3. load
  4. pre_target_generate
  5. target_generate
  6. post_target_generate
  7. pre_target_compilation
  8. target_compilation
  9. post_target_compilation
  10. post_build

后续会详细讲这10个流程

build和set参数解析

hb/containers/arg.py

0230     @staticmethod
0231     def parse_all_args(module_type: ModuleType) -> dict:
0232         args_dict = {}
0233         parser = argparse.ArgumentParser()
0234         all_args = Arg.read_args_file(module_type)
0235 
0236         for arg in all_args.values():
0237             arg = dict(arg)
0238             ArgsFactory.genetic_add_option(parser, arg)
0239             oh_arg = Arg.create_instance_by_dict(arg)
0240             args_dict[oh_arg.arg_name] = oh_arg
0241 
0242         parser.usage = 'hb {} [option]'.format(module_type.name.lower())
0243         parser_args = parser.parse_known_args(sys.argv[2:])
0244 
0245         for oh_arg in args_dict.values():
0246             if isinstance(oh_arg, Arg):
0247                 assigned_value = parser_args[0].__dict__[oh_arg.arg_name]
0248                 if oh_arg.arg_type == ArgType.LIST:
0249                     convert_assigned_value = TypeCheckUtil.tile_list(assigned_value)
0250                     convert_assigned_value = list(set(convert_assigned_value))
0251                 elif oh_arg.arg_type == ArgType.SUBPARSERS:
0252                     convert_assigned_value = TypeCheckUtil.tile_list(assigned_value)
0253                     if len(convert_assigned_value):
0254                         convert_assigned_value = list(set(convert_assigned_value))
0255                         convert_assigned_value.extend(parser_args[1])
0256                         convert_assigned_value.sort(key=sys.argv[2:].index)
0257                 elif oh_arg.arg_type == ArgType.BOOL or oh_arg.arg_type == ArgType.GATE:
0258                     if str(assigned_value).lower() == 'false':
0259                         convert_assigned_value = False
0260                     elif str(assigned_value).lower() == 'true':
0261                         convert_assigned_value = True
0262                 else:
0263                     convert_assigned_value = assigned_value
0264 
0265                 if oh_arg.arg_attribute.get('deprecated', None) and oh_arg.arg_value != convert_assigned_value:
0266                     LogUtil.hb_warning(
0267                         'compile option "{}" will be deprecated, \
0268                             please consider use other options'.format(oh_arg.arg_name))
0269                 oh_arg.arg_value = convert_assigned_value
0270                 Arg.write_args_file(
0271                     oh_arg.arg_name, oh_arg.arg_value, module_type)
0272 
0273         return args_dict

231行: 参数类型可以是build, set或者其它类型,
232行: 解析完的参数存储在此字典,并最后返回
233行:通用的参数解析器对象
234行:读取此类型支持的参数列表,由json配置文件存储(这些文件在build中的某个地方,后续分析)
236行:遍历本模块(如build)支持的参数
237行:每个参数的描述信息也是一个字典形式
238行:命令行加入对此参数的识别能力
239行:创建一个描述本参数的软件对象
240行:记录下参数名称与参数对象的映射关系
243行:先解析此模块能识别的参数,比如本文的“-p rk3568”,注意参数解析后,原来的参数表(sys.argv)并不会减少,下一个模块还能继续解析。
245行:遍历支持的参数表中的所有参数
247行:读取已解析出的参数值,如本例的rk3568,针对用户没有配置的参数,则读取的是默认值
248~263行:对参数值进行规范化处理
265~268行:如果使用了一个过期的参数,且和默认值不同,即用户指定了其他值,则打印警告提示日志
269~270行:更新参数对象的值并刷新参数配置文件
273行:最后返回参数对象表。

参数配置文件读写

hb/containers/arg.py

0297     @staticmethod
0298     @throw_exception
0299     def read_args_file(module_type: ModuleType):
0300         args_file_path = ''
0301         default_file_path = ''
0302         if module_type == ModuleType.BUILD:
0303             args_file_path = CURRENT_BUILD_ARGS
0304             default_file_path = DEFAULT_BUILD_ARGS
0305         elif module_type == ModuleType.SET:
0306             args_file_path = CURRENT_SET_ARGS
0307             default_file_path = DEFAULT_SET_ARGS
0308         elif module_type == ModuleType.CLEAN:
0309             args_file_path = CURRENT_CLEAN_ARGS
0310             default_file_path = DEFAULT_CLEAN_ARGS
0311         elif module_type == ModuleType.ENV:
0312             args_file_path = CURRENT_ENV_ARGS
0313             default_file_path = DEFAULT_ENV_ARGS
0314         elif module_type == ModuleType.TOOL:
0315             args_file_path = CURRENT_TOOL_ARGS
0316             default_file_path = DEFAULT_TOOL_ARGS
0317         else:
0318             raise OHOSException(
0319                 'You are trying to read args file, but there is no corresponding module "{}" args file'
0320                 .format(module_type.name.lower()), "0018")
0321         if not os.path.exists(CURRENT_ARGS_DIR):
0322             os.makedirs(CURRENT_ARGS_DIR)
0323         if not os.path.exists(args_file_path):
0324             IoUtil.copy_file(src=default_file_path, dst=args_file_path)
0325         return IoUtil.read_json_file(args_file_path)

以build类型的参数文件为例,其它类似。

  • CURRENT_BUILD_ARGS: 生效的参数配置文件
  • DEFAULT_BUILD_ARGS:默认的参数配置文件
    如果生效的参数文件存在,则直接读出,否则从默认参数文件拷贝后再读出。
    build/hb/resources/global_var.py
0022 CURRENT_OHOS_ROOT = os.path.dirname(os.path.dirname(
0023     os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
0024 CURRENT_BUILD_DIR = os.path.join(CURRENT_OHOS_ROOT, 'build')
0025 CURRENT_HB_DIR = os.path.join(CURRENT_BUILD_DIR, 'hb')
0026 DEFAULT_CCACHE_DIR = os.path.join(CURRENT_OHOS_ROOT, '.ccache')
0027 
0028 ARGS_DIR = os.path.join(CURRENT_HB_DIR, 'resources/args')
0029 
0030 DEFAULT_BUILD_ARGS = os.path.join(
0031     CURRENT_HB_DIR, 'resources/args/default/buildargs.json')
0032 DEFAULT_SET_ARGS = os.path.join(
0033     CURRENT_HB_DIR, 'resources/args/default/setargs.json')
0034 DEFAULT_CLEAN_ARGS = os.path.join(
0035     CURRENT_HB_DIR, 'resources/args/default/cleanargs.json')
0036 DEFAULT_ENV_ARGS = os.path.join(
0037     CURRENT_HB_DIR, 'resources/args/default/envargs.json')
0038 DEFAULT_TOOL_ARGS = os.path.join(
0039     CURRENT_HB_DIR, 'resources/args/default/toolargs.json')
0040 
0041 CURRENT_ARGS_DIR = os.path.join(CURRENT_OHOS_ROOT, 'out/hb_args')
0042 CURRENT_BUILD_ARGS = os.path.join(
0043     CURRENT_ARGS_DIR, 'buildargs.json')
0044 CURRENT_SET_ARGS = os.path.join(
0045     CURRENT_ARGS_DIR, 'setargs.json')
0046 CURRENT_CLEAN_ARGS = os.path.join(
0047     CURRENT_ARGS_DIR, 'cleanargs.json')
0048 CURRENT_ENV_ARGS = os.path.join(
0049     CURRENT_ARGS_DIR, 'envargs.json')
0050 CURRENT_TOOL_ARGS = os.path.join(
0051     CURRENT_ARGS_DIR, 'toolargs.json')

hb build, hb set, hb clean等参数文件位置和文件名称定义如上
以build为例

  • 默认参数文件位置为:build/hb/resources/args/default/buildargs.json
  • 生效参数文件位置为:out/hb_args/buildargs.json

hb/containers/arg.py

0275     @staticmethod
0276     @throw_exception
0277     def write_args_file(key: str, value, module_type: ModuleType):
0278         args_file_path = ''
0279         if module_type == ModuleType.BUILD:
0280             args_file_path = CURRENT_BUILD_ARGS
0281         elif module_type == ModuleType.SET:
0282             args_file_path = CURRENT_SET_ARGS
0283         elif module_type == ModuleType.CLEAN:
0284             args_file_path = CURRENT_CLEAN_ARGS
0285         elif module_type == ModuleType.ENV:
0286             args_file_path = CURRENT_ENV_ARGS
0287         elif module_type == ModuleType.TOOL:
0288             args_file_path = CURRENT_TOOL_ARGS
0289         else:
0290             raise OHOSException(
0291                 'You are trying to write args file, but there is no corresponding module "{}" args file'
0292                 .format(module_type), "0002")
0293         args_file = Arg.read_args_file(module_type)
0294         args_file[key]["argDefault"] = value
0295         IoUtil.dump_json_file(args_file_path, args_file)

上述函数根据用户配置的参数值,刷新参数配置文件。

配置对象的初始化

参考build/lite/hb_internal/set/set.py的第49行,这里是config对象的首次创建,我们来看看其怎么初始化的
build/lite/hb_internal/common/config.py

0034 class Config(metaclass=Singleton):
0035     def __init__(self):
0036         self.config_json = CONFIG_JSON
0037 
0038         config_content = read_json_file(self.config_json)
0039         self._root_path = config_content.get('root_path', None)
0040         self._board = config_content.get('board', None)
0041         self._kernel = config_content.get('kernel', None)
0042         self._product = config_content.get('product', None)
0043         self._product_path = config_content.get('product_path', None)
0044         self._device_path = config_content.get('device_path', None)
0045         self._device_company = config_content.get('device_company', None)
0046         self._patch_cache = config_content.get('patch_cache', None)
0047         self._version = config_content.get('version', '3.0')
0048         self._os_level = config_content.get('os_level', 'small')
0049         self._product_json = config_content.get('product_json', None)
0050         self._target_cpu = config_content.get('target_cpu', None)
0051         self._target_os = config_content.get('target_os', None)
0052         self._out_path = config_content.get('out_path', None)
0053         self._compile_config = config_content.get('compile_config', None)
0054         self._component_type = config_content.get('component_type', None)
0055         self._device_config_path = config_content.get('device_config_path',
0056                                                       None)
0057         self._product_config_path = config_content.get('product_config_path',
0058                                                        None)
0059         self._subsystem_config_json = config_content.get(
0060             'subsystem_config_json', None)
0061         self._subsystem_config_overlay_json = ''
0062         self.fs_attr = set()
0063         self.platform = platform.system()

34行:Config类是一个单实例类,即只会存在一个Config类型的对象
36行:记录config配置文件的位置
38行:读取config配置文件
39~60行:将读取到的值记录到config对象,如果值不存在,则记录为None。
61~62行:初始化2个空的配置属性
63行:记录当前平台类型,如windows, linux, macos等

配置文件位置

build/​lite/​hb_internal/​__init__.py

0022 def get_config_path():
0023     search_path = os.getcwd()
0024     root_path = os.path.abspath(os.sep)
0025     while search_path != root_path:
0026         config_path = os.path.join(search_path, 'ohos_config.json')
0027         if os.path.isfile(config_path):
0028             return config_path
0029         search_path = os.path.dirname(search_path)
0030     return os.path.abspath(
0031         os.path.join(os.path.dirname(__file__), 'common', 'config.json'))
0032 
0033 
0034 CONFIG_JSON = get_config_path()

从上述代码可知:
如果根目录下存在ohos_config.json,则配置文件为ohos_config.json
否则配置文件为build/lite/hb_internal/common/config.json
刚开始时ohos_config.json不存在,什么时候生成的呢?
请看如下代码逻辑
build/lite/hb_internal/common/config.py

0137     @root_path.setter
0138     def root_path(self, value):
0139         self._root_path = os.path.abspath(value)
0140         if not os.path.isdir(self._root_path):
0141             raise OHOSException(f'{self._root_path} is not a valid path')
0142 
0143         config_path = os.path.join(self._root_path, 'ohos_config.json')
0144         if not os.path.isfile(config_path):
0145             self.config_create(config_path)
0146         self.config_update('root_path', self._root_path)
0384     def config_create(self, config_path):
0385         dump_json_file(config_path, CONFIG_STRUCT)
0386         self.config_json = config_path

build/lite/hb_internal/init.py

0035 CONFIG_STRUCT = {
0036     "root_path": None,
0037     "board": None,
0038     "kernel": None,
0039     "product": None,
0040     "product_path": None,
0041     "device_path": None,
0042     "device_company": None,
0043     "os_level": None,
0044     "version": None,
0045     "patch_cache": None
0046 }

build/lite/hb_internal/set/set.py

0056     config.root_path = root_path

56行: 触发setter的执行,从而创建ohos_config.json

产品配置信息的获取

刚开始时,除了在配置对象中设置了源码根目录的位置,其它数据都是None, 那么某产品(如rk3568)的实际配置是怎么获取到的呢
让我们回到build/hb/main.py第69行,这里会刷新产品信息
build/hb/main.py

0069             ohos_set_module.set_product()

然后我们看看set_product的实现
build/​hb/​modules/​ohos_set_module.py

0046     def set_product(self):
0047         self.args_resolver.resolve_arg(self.args_dict['product_name'], self)

进一步查看对应的resolve_arg函数
​build/​hb/​resolver/​set_args_resolver.py

0035     @staticmethod
0036     def resolve_product_name(target_arg: Arg, set_module: SetModuleInterface):
0037         config = Config()
0038         product_info = dict()
0039         device_info = dict()
0040         if target_arg.arg_value == '':
0041             product_info = set_module._menu.select_product()
0042         elif target_arg.arg_value.__contains__('@'):
0043             product_name, company_name = target_arg.arg_value.split('@', 2)
0044             product_info = ProductUtil.get_product_info(
0045                 product_name, company_name)
0046         else:
0047             product_info = ProductUtil.get_product_info(target_arg.arg_value)
0048 
0049         config.product = product_info.get('name')
0050         config.product_path = product_info.get('product_path')
0051         config.version = product_info.get('version')
0052         config.os_level = product_info.get('os_level')
0053         config.product_json = product_info.get('config')
0054         config.component_type = product_info.get('component_type')
0055         if product_info.get('product_config_path'):
0056             config.product_config_path = product_info.get(
0057                 'product_config_path')
0058         else:
0059             config.product_config_path = product_info.get('path')
0060 
0061         device_info = ProductUtil.get_device_info(config.product_json)
0062         config.board = device_info.get('board')
0063         config.kernel = device_info.get('kernel')
0064         config.target_cpu = device_info.get('target_cpu')
0065         config.target_os = device_info.get('target_os')
0066         config.support_cpu = device_info.get("support_cpu")
0067         kernel_version = device_info.get('kernel_version')
0068         config.device_company = device_info.get('company')
0069         board_path = device_info.get('board_path')
0070 
0071         if product_info.get('build_out_path'):
0072             config.out_path = os.path.join(config.root_path,
0073                                            product_info.get('build_out_path'))
0074         else:
0075             if config.os_level == 'standard':
0076                 config.out_path = os.path.join(config.root_path, 'out',
0077                                                config.board)
0078             else:
0079                 config.out_path = os.path.join(config.root_path, 'out',
0080                                                config.board, config.product)
0081 
0082         if product_info.get('subsystem_config_json'):
0083             config.subsystem_config_json = product_info.get(
0084                 'subsystem_config_json')
0085         else:
0086             config.subsystem_config_json = 'build/subsystem_config.json'
0087 
0088         subsystem_config_overlay_path = os.path.join(
0089             config.product_path, 'subsystem_config_overlay.json')
0090         if os.path.isfile(subsystem_config_overlay_path):
0091             if product_info.get('subsystem_config_overlay_json'):
0092                 config.subsystem_config_overlay_json = product_info.get(
0093                     'subsystem_config_overlay_json')
0094             else:
0095                 config.subsystem_config_overlay_json = subsystem_config_overlay_path
0096 
0097         if config.version == '2.0':
0098             config.device_path = board_path
0099         else:
0100             if config.os_level == "standard":
0101                 config.device_path = board_path
0102             else:
0103                 config.device_path = DeviceUtil.get_device_path(
0104                     board_path, config.kernel, kernel_version)
0105 
0106         if device_info.get('board_config_path'):
0107             config.device_config_path = device_info.get('board_config_path')
0108         else:
0109             config.device_config_path = config.device_path
0110 
0111         Arg.write_args_file(target_arg.arg_name,
0112                             product_info.get('name'), ModuleType.BUILD)
0113         Arg.write_args_file(target_arg.arg_name,
0114                             product_info.get('name'), ModuleType.SET)

41行:产品名称为空,则弹出菜单让用户选择产品并获取产品配置
43行:产品名称含@符号,则用公司名和产品名一起查找产品配置
47行:直接用产品名查找产品配置
49~59行:根据产品配置刷新config对象
61行:获取设备配置信息
62~69行:根据设备配置信息刷新config对象
71~80行:设置编译构建的输出目录
82~95行:与产品相关的子系统配置信息
97~109行:设备路径和设备配置路径信息
111~114行:将产品名称记录到set和build对应的参数文件中

这里需要注意设备和产品的概念,理论上可以基于设备进行二次开发,形成不同的产品。所以设备和产品的关系可以是一对多的关系。这里的术语设备,有时我们也称为开发板。
至此,config对象的主要信息获取到了。
这里也许你会有一个疑惑,命令行参数product_name和处理函数resolve_product_name是怎么对应上的,这里给出解答
build/​hb/​resources/​args/​default/​setargs.json

0002     "product_name": {
0003         "arg_name": "--product-name",
0004         "argDefault": "",
0005         "arg_help": "Default:''. Help:build a specified product. You could use this option like this: 1.'hb set --product-name rk3568@hihope' 2.'hb set --product-name rk3568' 3.'hb set'[graphical ui]",
0006         "arg_phase": "prebuild",
0007         "arg_type": "str",
0008         "arg_attribute": {
0009             "abbreviation": "-p"
0010         },
0011         "resolve_function": "resolve_product_name",
0012         "testFunction": "testProductName"
0013     },

可以从11行看到product_name的处理函数为resolve_product_name。
build/hb/resolver/interface/args_resolver_interface.py

0031     @throw_exception
0032     def resolve_arg(self, target_arg: Arg, module):
0033         if target_arg.arg_name not in self._args_to_function.keys():
0034             raise OHOSException(
0035                 'You are tring to call {} resolve function, but it has not been defined yet', '0000')
0036         if not hasattr(self._args_to_function[target_arg.arg_name], '__call__'):
0037             raise OHOSException()
0038 
0039         resolve_function = self._args_to_function[target_arg.arg_name]
0040         return resolve_function(target_arg, module)
0041 
0042     @throw_exception
0043     def _map_args_to_function(self, args_dict: dict):
0044         for entity in args_dict.values():
0045             if isinstance(entity, Arg):
0046                 args_name = entity.arg_name
0047                 function_name = entity.resolve_function
0048                 if not hasattr(self, function_name) or \
0049                         not hasattr(self.__getattribute__(function_name), '__call__'):
0050                     raise OHOSException(
0051                         'There is no resolution function for arg: {}'.format(
0052                             args_name),
0053                         "0004")
0054                 entity.resolve_function = self.__getattribute__(function_name)
0055                 self._args_to_function[args_name] = self.__getattribute__(
0056                     function_name)

这里可以洞见,通用的resolve_arg函数怎么调用到具体的与参数对应的解析函数上的
39行:通过参数名称查询对应的解析函数
40行:调用对应的解析函数
那么名称和解析函数映射表是怎么建立的呢,请看43~56行代码
44行:遍历所有的参数对象
46~47行:分别获取参数名和解析函数名称
48行:判断此名称的函数是否存在
54~55行:如果存在则取出函数对象,再建立函数对象与参数名称的映射

0025 class ArgsResolverInterface(metaclass=ABCMeta):
0026 
0027     def __init__(self, args_dict: dict):
0028         self._args_to_function = dict()
0029         self._map_args_to_function(args_dict)

如上所示,解析器对象(子类)初始化的时候,就把参数名与函数映射表建立好了。
此时,config对象的信息也写入配置文件了,因为config对象中可以配置的属性基本都有一个setter方法和一个property方法,下面举个例子
build/hb/resources/config.py

0181     @property
0182     def device_company(self):
0183         if self._device_company is None:
0184             raise OHOSException('Failed to init compile config', '0019')
0185         return self._device_company
0186 
0187     @device_company.setter
0188     def device_company(self, value):
0189         self._device_company = value
0190         self.config_update('device_company', self._device_company)

如上。setter方法用于修改device_company属性时,property方法用于读取device_company属性时。而config_update会刷新配置文件
build/hb/resources/config.py

0378     def config_update(self, key, value):
0379         config_content = IoUtil.read_json_file(self.config_json)
0380         config_content[key] = value
0381         IoUtil.dump_json_file(self.config_json, config_content)

因此ohos_config.json会得到及时的刷新

prebuild

build/hb/modules/ohos_build_module.py

0073     def _prebuild(self):
0074         self._run_phase(BuildPhase.PRE_BUILD)
0111     def _run_phase(self, phase: BuildPhase):
0112         '''Description: Traverse all registered parameters in build process and 
0113             execute the resolver function of the corresponding phase
0114         @parameter: [phase]:  Build phase corresponding to parameter
0115         @return :none
0116         '''
0117         for phase_arg in [arg for arg in self.args_dict.values()if arg.arg_phase == phase]:
0118             self.args_resolver.resolve_arg(phase_arg, self)

刚开始遍历执行参数中为prebuild的阶段的这些处理函数,根据配置文件查询,这些函数有
build/​hb/​resources/​args/​default/​buildargs.json

0011     "resolve_function": "resolve_product",
0028     "resolve_function": "resolve_target_cpu",
0048     "resolve_function": "resolve_ccache",
0058     "resolve_function": "resolve_pycache",
0094     "resolve_function": "resolve_build_target",
0104     "resolve_function": "resolve_ninja_args",
0116     "resolve_function": "resolve_full_compilation",

还有2个参数标记成deprecated(过期)状态,这里没有列出

产品名称解析

build/hb/resolver/build_args_resolver.py

0049     @staticmethod
0050     def resolve_product(target_arg: Arg, build_module: BuildModuleInterface):
0051         """resolve '--product-name' arg.
0052         :param target_arg: arg object which is used to get arg value.
0053         :param build_module [maybe unused]: build module object which is used to get other services.
0054         :phase: prebuild.
0055         """
0056         config = Config()
0057         target_generator = build_module.target_generator
0058         target_generator.regist_arg('product_name', config.product)
0059         target_generator.regist_arg('product_path', config.product_path)
0060         target_generator.regist_arg(
0061             'product_config_path', config.product_config_path)
0062 
0063         target_generator.regist_arg('device_name', config.board)
0064         target_generator.regist_arg('device_path', config.device_path)
0065         target_generator.regist_arg('device_company', config.device_company)
0066         target_generator.regist_arg(
0067             'device_config_path', config.device_config_path)
0068 
0069         target_generator.regist_arg('target_cpu', config.target_cpu)
0070         target_generator.regist_arg(
0071             'is_{}_system'.format(config.os_level), True)
0072 
0073         target_generator.regist_arg('ohos_kernel_type', config.kernel)
0074         target_generator.regist_arg('ohos_build_compiler_specified',
0075                                     ProductUtil.get_compiler(config.device_path))
0076 
0077         target_generator.regist_arg('ohos_build_time',
0078                                     SystemUtil.get_current_time(time_type='timestamp'))
0079         target_generator.regist_arg('ohos_build_datetime',
0080                                     SystemUtil.get_current_time(time_type='datetime'))
0081 
0082         features_dict = ProductUtil.get_features_dict(config.product_json)
0083         for key, value in features_dict.items():
0084             target_generator.regist_arg(key, value)
0085 
0086         if ProductUtil.get_compiler(config.device_path) == 'clang':
0087             target_generator.regist_arg(
0088                 'ohos_build_compiler_dir', config.clang_path)
0089 
0090         if target_arg.arg_value == 'ohos-sdk':
0091             target_generator = build_module.target_generator
0092             target_generator.regist_arg('build_ohos_sdk', True)
0093             target_generator.regist_arg('build_ohos_ndk', True)
0094             if len(build_module.args_dict['build_target'].arg_value) == 0:
0095                 build_module.args_dict['build_target'].arg_value = [
0096                     'build_ohos_sdk']
0097             build_module.args_dict['target_cpu'].arg_value = 'arm64'

56~80行:将config配置对象的数据读出并记录到target_generator对象中(后续gn程序会使用到),这些config配置对象在之前hb set 处理中已设置好。
82~84行:读取产品对应的特性配置,对于rk3568来说,其在vendor/hihope/rk3568/config.json中,并记录下来
86~88行:读取并记录clang编译器的目录位置
90~97行:如果编译的产品是openharmony sdk, 则需要记录一些特殊的参数

目标CPU解析

build/hb/resolver/build_args_resolver.py

0099     @staticmethod
0100     def resolve_target_cpu(target_arg: Arg, build_module: BuildModuleInterface):
0101         """resolve '--target-cpu' arg.
0102         :param target_arg: arg object which is used to get arg value.
0103         :param build_module [maybe unused]: build module object which is used to get other services.
0104         :phase: prebuild.
0105         """
0106         config = Config()
0107         default_build_args = IoUtil.read_json_file(DEFAULT_BUILD_ARGS)
0108         if config.target_cpu == "":
0109             config.target_cpu = target_arg.arg_value
0110         elif target_arg.arg_value != default_build_args.get("target_cpu").get("argDefault"):
0111             config.target_cpu = target_arg.arg_value

target_cpu表示此代码最终编译出来后在什么cpu架构上执行。
对于./build.py -p rk3568场景
由于hb set逻辑已经设置了target_cpu所以,这里if条件不满足
110~111行:用户指定了target_cpu参数,且与默认值不同,则更新一下。

ccache参数解析

build/hb/resolver/build_args_resolver.py

0163     @staticmethod
0164     def resolve_ccache(target_arg: Arg, build_module: BuildModuleInterface):
0165         """resolve '--ccache' arg
0166         :param target_arg: arg object which is used to get arg value.
0167         :param build_module [maybe unused]: build module object which is used to get other services.
0168         :phase: prebuild.
0169         """
0170         if target_arg.arg_value:
0171             config = Config()
0172             ccache_path = find_executable('ccache')
0173             if ccache_path is None:
0174                 LogUtil.hb_warning('Failed to find ccache, ccache disabled.')
0175                 return
0176             else:
0177                 target_generator = build_module.target_generator
0178                 target_generator.regist_arg(
0179                     'ohos_build_enable_ccache', target_arg.arg_value)
0180 
0181             ccache_local_dir = os.environ.get('CCACHE_LOCAL_DIR')
0182             ccache_base = os.environ.get('CCACHE_BASE')
0183             if not ccache_local_dir:
0184                 ccache_local_dir = '.ccache'
0185             if not ccache_base:
0186                 ccache_base = os.environ.get('HOME')
0187             ccache_base = os.path.join(ccache_base, ccache_local_dir)
0188             if not os.path.exists(ccache_base):
0189                 os.makedirs(ccache_base)
0190 
0191             ccache_log_suffix = os.environ.get('CCACHE_LOG_SUFFIX')
0192             if ccache_log_suffix:
0193                 logfile = os.path.join(
0194                     ccache_base, "ccache.{}.log".format(ccache_log_suffix))
0195             else:
0196                 logfile = os.path.join(ccache_base, "ccache.log")
0197             if os.path.exists(logfile):
0198                 oldfile = os.path.join(ccache_base, '{}.old'.format(logfile))
0199                 if os.path.exists(oldfile):
0200                     os.unlink(oldfile)
0201                 os.rename(logfile, oldfile)
0202 
0203             os.environ['CCACHE_EXEC'] = ccache_path
0204             os.environ['CCACHE_LOGFILE'] = logfile
0205             os.environ['USE_CCACHE'] = '1'
0206             os.environ['CCACHE_DIR'] = ccache_base
0207             os.environ['CCACHE_UMASK'] = '002'
0208             os.environ['CCACHE_BASEDIR'] = config.root_path
0209             ccache_max_size = os.environ.get('CCACHE_MAXSIZE')
0210             if not ccache_max_size:
0211                 ccache_max_size = '50G'
0212 
0213             cmd = ['ccache', '-M', ccache_max_size]
0214 
0215             SystemUtil.exec_command(cmd, log_path=config.log_path)

ccache是一个工具,可以缓存C/C++编译过程的中间结果,这样下次编译的时候可以从中间结果继续往下执行,加快编译速度。
本段代码处理ccache功能打开或关闭的逻辑。当前此功能默认打开。
172~175行:查找ccache工具程序路径
176~179行:记录ccache功能开关
181~189行:查找或创建ccache输出文件夹
191~201行:ccache功能日志文件创建或重命名
203~208行:将ccache一些参数记入环境变量
209~215行:设置ccache的缓冲区尺寸

pycache参数解析

pycache是一个可以加快python程序性能的工具,默认是关闭的
build/hb/resolver/build_args_resolver.py

0217     @staticmethod
0218     def resolve_pycache(target_arg: Arg, build_module: BuildModuleInterface):
0219         """resolve '--enable-pycache' arg
0220         :param target_arg: arg object which is used to get arg value.
0221         :param build_module [maybe unused]: build module object which is used to get other services.
0222         :phase: prebuild.
0223         """
0224         if target_arg.arg_value:
0225             config = Config()
0226             pycache_dir = os.environ.get('CCACHE_BASE')
0227             # The default value is HOME for local users
0228             if not pycache_dir:
0229                 pycache_dir = os.environ.get('HOME')
0230             pycache_dir = os.path.join(pycache_dir, '.pycache')
0231             os.environ['PYCACHE_DIR'] = pycache_dir
0232             pyd_start_cmd = [
0233                 'python3',
0234                 '{}/build/scripts/util/pyd.py'.format(config.root_path),
0235                 '--root',
0236                 pycache_dir,
0237                 '--start',
0238             ]
0239             cmd = ['/bin/bash', '-c', ' '.join(pyd_start_cmd), '&']
0240             subprocess.Popen(cmd)

224行:如果开启了pycache功能,则执行后面的逻辑
主要目标就是执行build/scripts/util/pyd.py脚本,此脚本不做分析,读者自己阅读代码即可。

构建目标解析

操作系统编译构建可以有很多目标,如果不做指定,则会是整个操作系统镜像(非常大),也可以指定成一些具体的软件模块(相对较小)-比如二进制程序或者共享库等。可以通过hb tool查看当前支持哪些构建目标。
build/hb/resolver/build_args_resolver.py

0113     @staticmethod
0114     @throw_exception
0115     def resolve_build_target(target_arg: Arg, build_module: BuildModuleInterface):
0116         """resolve '--build-target' arg.
0117         :param target_arg: arg object which is used to get arg value.
0118         :param build_module [maybe unused]: build module object which is used to get other services.
0119         :phase: prebuild.
0120         :raise OHOSException: when build target not exist in compiling product.
0121         """
0122         config = Config()
0123         build_executor = build_module.target_compiler
0124         target_list = []
0125         if len(target_arg.arg_value):
0126             target_list = target_arg.arg_value
0127         else:
0128             if os.getcwd() == CURRENT_OHOS_ROOT:
0129                 target_list = ['images']
0130             elif ComponentUtil.is_in_component_dir(os.getcwd()) and \
0131                     ComponentUtil.is_component_in_product(
0132                     ComponentUtil.get_component_name(os.getcwd()), Config().product):
0133                 component_name = ComponentUtil.get_component_name(os.getcwd())
0134                 LogUtil.write_log(Config().log_path, 'In the component "{}" directory,'
0135                                   'this compilation will compile only this component'.format(
0136                                       component_name),
0137                                   'warning')
0138                 target_list.append(component_name)
0139                 target_list.append(component_name + '_test')
0140             else:
0141                 component_name = ComponentUtil.get_component_name(os.getcwd())
0142                 component_name = os.path.basename(
0143                     os.getcwd()) if component_name == '' else component_name
0144                 raise OHOSException('There is no target component "{}" for the current product "{}"'
0145                                     .format(component_name, Config().product), "4001")
0146         build_executor.regist_arg('build_target', target_list)

125~126行,如果参数指明了构建目标,则直接记录下来
否则
128~129行,当前在代码根目录,则直接生成系统镜像
130~139行,如果在某组件目录,且此产品含有这个组件,则构建对应的组件
140~145行,组件不属于产品,报错提示

ninja配置参数解析

build/hb/resolver/build_args_resolver.py

0309     @staticmethod
0310     @throw_exception
0311     def resolve_ninja_args(target_arg: Arg, build_module: BuildModuleInterface):
0312         """resolve '--ninja-args' arg
0313         :param target_arg: arg object which is used to get arg value.
0314         :param build_module [maybe unused]: build module object which is used to get other services.
0315         :phase: prebuild.
0316         :raise OHOSException: when the value of the ninja parameter does not use quotation marks.
0317         """
0318         build_executor = build_module.target_compiler
0319         ninja_args_list = []
0320         for ninja_arg in target_arg.arg_value:
0321             ninja_arg = re.sub("'", "", ninja_arg)
0322             ninja_args_list.append(ninja_arg)
0323         build_executor.regist_arg('ninja_args', ninja_args_list)

主要就是把所有额外添加的单引号删除掉,回归ninja参数本来面目

全量编译开关

build/hb/resolver/build_args_resolver.py

0242     @staticmethod
0243     def resolve_full_compilation(target_arg: Arg, build_module: BuildModuleInterface):
0244         """resolve '--full-compilation' arg
0245         :param target_arg: arg object which is used to get arg value.
0246         :param build_module [maybe unused]: build module object which is used to get other services.
0247         :phase: prebuild.
0248         """
0249         if target_arg.arg_value:
0250             build_executor = build_module.target_compiler
0251             target_list = build_executor.args_dict.get('build_target', None)
0252             if isinstance(target_list, list):
0253                 target_list.append('make_all')
0254                 target_list.append('make_test')
0255             else:
0256                 build_executor.regist_arg(
0257                     'build_target', ['make_all', 'make_test'])

在已有构建目标的基础上,增加全量构建目标,即会构建整个系统镜像

preload

build/hb/modules/ohos_build_module.py

0076     def _preload(self):
0077         self._run_phase(BuildPhase.PRE_LOAD)
0078         if self.args_dict.get('fast_rebuild', None) and not self.args_dict.get('fast_rebuild').arg_value:
0079             self.preloader.run()

77行:先运行preload。
78~79行:如果开启了fast_rebuild,则会跳过preloader。
目前在buildarg json文件中没有查询到preload阶段的参数,所以,77行代码为空操作
对于preloader, 继续查看如下代码
build/hb/services/interface/preload_interface.py

0045     def run(self):
0046         self.__post_init__()
0047         self._generate_build_prop()
0048         self._generate_build_config_json()
0049         self._generate_parts_json()
0050         self._generate_parts_config_json()
0051         self._generate_build_gnargs_prop()
0052         self._generate_features_json()
0053         self._generate_syscap_json()
0054         self._generate_exclusion_modules_json()
0055         self._generate_platforms_build()
0056         self._generate_subsystem_config_json()
0057         self._generate_systemcapability_json()

可以看出,其主要作用是生成一系列的json文件,接下来逐一进行描述

preloader初始化

build/hb/services/preloader.py

0030     def __init__(self):
0031         super().__init__()
0032         self._dirs = ""
0033         self._outputs = ""
0034         self._product = ""
0035         self._os_level = ""
0036         self._target_cpu = ""
0037         self._target_os = ""
0038         self._toolchain_label = ""
0039         self._subsystem_info = {}
0040         self._all_parts = {}
0041         self._build_vars = {}
0042 
0043     def __post_init__(self):
0044         self._dirs = Dirs(self._config)
0045         self._outputs = Outputs(self._dirs.preloader_output_dir)
0046         self._product = Product(self._dirs, self._config)
0047         self._all_parts = self._product._parts
0048         self._build_vars = self._product._build_vars
0049         self._os_level = self._build_vars.get('os_level')
0050         self._target_os = self._build_vars.get('target_os')
0051         self._target_cpu = self._build_vars.get('target_cpu')
0052         self._toolchain_label = self._build_vars.get('product_toolchain_label')
0053         self._subsystem_info = self._get_org_subsystem_info()

preloader初始化时,上述2个函数会依次调用。
44行: 记录一些输入目录和文件的路径
45行:记录需要输出的一些文件路径名
46行:记录下产品相关的信息,并解析
47~53行:记录下其它信息

记录输入目录和文件

build/hb/util/preloader/preloader_process_data.py

0050 class Dirs:
0051 
0052     def __init__(self, config):
0053         self.__post_init__(config)
0054 
0055     def __post_init__(self, config):
0056         self.source_root_dir = config.root_path
0057         self.built_in_product_dir = config.built_in_product_path
0058         self.productdefine_dir = os.path.join(
0059             self.source_root_dir, 'productdefine/common')
0060         self.built_in_base_dir = os.path.join(self.productdefine_dir, 'base')
0061 
0062         # Configs of vendor specified products are stored in ${vendor_dir} directory.
0063         self.vendor_dir = config.vendor_path
0064         # Configs of device specified products are stored in ${device_dir} directory.
0065         self.device_dir = os.path.join(config.root_path, 'device')
0066 
0067         self.subsystem_config_json = os.path.join(
0068             config.root_path, config.subsystem_config_json)
0069         self.subsystem_config_overlay_json = os.path.join(config.product_path,
0070                                                           'subsystem_config_overlay.json')
0071         self.lite_components_dir = os.path.join(
0072             config.root_path, 'build/lite/components')
0073 
0074         self.preloader_output_dir = os.path.join(
0075             config.root_path, 'out/preloader', config.product)

56~75行:各种目录的记录

记录各输出文件的路径

build/hb/util/preloader/preloader_process_data.py

0025 class Outputs:
0026 
0027     def __init__(self, output_dir):
0028         self.__post_init__(output_dir)
0029 
0030     def __post_init__(self, output_dir):
0031         os.makedirs(output_dir, exist_ok=True)
0032         self.build_prop = os.path.join(output_dir, 'build.prop')
0033         self.build_config_json = os.path.join(output_dir, 'build_config.json')
0034         self.parts_json = os.path.join(output_dir, 'parts.json')
0035         self.parts_config_json = os.path.join(output_dir, 'parts_config.json')
0036         self.build_gnargs_prop = os.path.join(output_dir, 'build_gnargs.prop')
0037         self.features_json = os.path.join(output_dir, 'features.json')
0038         self.syscap_json = os.path.join(output_dir, 'syscap.json')
0039         self.exclusion_modules_json = os.path.join(output_dir,
0040                                                    'exclusion_modules.json')
0041         self.subsystem_config_json = os.path.join(output_dir,
0042                                                   'subsystem_config.json')
0043         self.subsystem_config_overlay_json = os.path.join(output_dir,
0044                                                   'subsystem_config_overlay.json')
0045         self.platforms_build = os.path.join(output_dir, 'platforms.build')
0046         self.systemcapability_json = os.path.join(
0047             output_dir, 'SystemCapability.json')

31行:创建输出目录
32~47行:记录各输出文件的路径

记录产品相关信息

build/hb/util/preloader/preloader_process_data.py

0078 class Product():
0079 
0080     def __init__(self, config_dirs: Dirs, ohos_config: Config):
0081         self._ohos_config = None
0082         self._dirs = None
0083         self._name = ""
0084         self._config = {}
0085         self._build_vars = {}
0086         self._parts = {}
0087         self._syscap_info = {}
0088         self._device_name = ""
0089         self._device_info = {}
0090         self._config_file = ""
0091         self._version = ''
0092         self.__post_init__(config_dirs, ohos_config)
0093 
0094     def __post_init__(self, config_dirs: Dirs, config: Config):
0095         self._ohos_config = config
0096         self._dirs = config_dirs
0097         self._name = config.product
0098         self._config_file = config.product_json
0099         self._config = self._get_full_product_config()
0100         self._version = self._config.get('version', '3.0')
0101         self._do_parse()
0102 
0103     # parse product configuration, then generate parts list and build vars
0104     def _do_parse(self):
0105         self._update_syscap_info()
0106         self._update_device()
0107         self._update_parts()
0108         self._update_build_vars()
0109         self._remove_excluded_components()

105~106行:解析产品信息
107行:生成部件列表信息
108行:生成build var列表
109行:移除不需要的组件

记录系统能力信息

build/hb/util/preloader/preloader_process_data.py

0114     def _update_syscap_info(self):
0115         product_name = self._config.get('product_name')
0116         if product_name is None:
0117             product_name = ""
0118         os_level = self._config.get('type')
0119         if os_level is None:
0120             os_level = ""
0121         api_version = self._config.get('api_version')
0122         if api_version is None:
0123             api_version = 0
0124         manufacturer_id = self._config.get('manufacturer_id')
0125         if manufacturer_id is None:
0126             manufacturer_id = 0
0127         self._syscap_info = {'product': product_name, 'api_version': api_version,
0128                              'system_type': os_level, 'manufacturer_id': manufacturer_id}

主要记录一些基础信息,如果没有配置,则记录成空字符串或0。

记录设备信息

build/hb/util/preloader/preloader_process_data.py

0131     def _update_device(self):
0132         if self._version == "2.0":
0133             device_name = self._config.get('product_device')
0134             if device_name:
0135                 self._device_name = device_name
0136                 self._device_info = self._get_device_info_v2(
0137                     device_name, self._dirs.built_in_device_dir)
0138         else:
0139             device_name = self._config.get('board')
0140             if device_name:
0141                 self._device_name = device_name
0142                 self._device_info = self._get_device_info_v3(self._config)
0143         if self._ohos_config.target_cpu:
0144             self._device_info["target_cpu"] = self._ohos_config.target_cpu
0145         if self._ohos_config.compile_config:
0146             self._device_info[self._ohos_config["compile_config"]] = True

132~142行:记录device name和device info
144行:记录目标CPU
145行:记录编译工具类型

生成部件信息列表

build/hb/util/preloader/preloader_process_data.py

0149     def _update_parts(self):
0150         if self._version == "1.0":
0151             _parts = {}
0152             self._parts = _parts
0153         else:
0154             # 1. inherit parts information from base config
0155             if self._version == "2.0":
0156                 os_level = self._config.get("type", "standard")
0157             else:
0158                 os_level = self._config.get("type", "mini")
0159             # 2. product config based on default minimum system
0160             based_on_mininum_system = self._config.get(
0161                 'based_on_mininum_system')
0162             if based_on_mininum_system == "true":
0163                 self._parts = self._get_base_parts(
0164                     self._dirs.built_in_base_dir, os_level)
0165             # 3. inherit parts information from inherit config
0166             inherit = self._config.get('inherit')
0167             if inherit:
0168                 self._parts.update(
0169                     self._get_inherit_parts(inherit, self._dirs.source_root_dir))
0170 
0171             # 4. chipset products relate system parts config
0172             sys_info_path = self._config.get('system_component')
0173             if sys_info_path:
0174                 sys_parts = self._get_sys_relate_parts(
0175                     sys_info_path, self._parts, self._dirs.source_root_dir)
0176                 self._parts.update(sys_parts)
0177             all_parts = {}
0178             if self._version == "2.0":
0179                 current_product_parts = self._config.get("parts")
0180                 if current_product_parts:
0181                     all_parts.update(current_product_parts)
0182             else:
0183                 all_parts.update(get_vendor_parts_list(self._config))
0184                 all_parts.update(self._get_product_specific_parts())
0185 
0186                 device_name = self._config.get('board')
0187                 if device_name:
0188                     all_parts.update(self._get_device_specific_parts())
0189             self._parts.update(all_parts)

160~164行:先生成最小系统的部件集合,目前搜索整个代码仓没有发现使用。
166~169行:从某些地方继承合并一些部件
172~176行:补充一些系统部件,目前搜索整个代码仓没有发现使用。
178~188行:补充只属于本产品的一些部件

继承合并一些其它部件集合

build/hb/util/preloader/preloader_process_data.py

0337     def _get_inherit_parts(self, inherit, source_root_dir) -> dict:
0338         inherit_parts = {}
0339         for _config in inherit:
0340             _file = os.path.join(source_root_dir, _config)
0341             _info = IoUtil.read_json_file(_file)
0342             parts = _info.get('parts')
0343             if parts:
0344                 inherit_parts.update(parts)
0345             else:
0346                 inherit_parts.update(get_vendor_parts_list(_info))
0347         return inherit_parts

这里的inherit是一个列表,每个元素描述待继承的部件描述json文件路径(相对于代码根目录)
即从其它若干产品继承部件集合。
注意:由于历史原因,软件组件早期按子系统和组件模型划分,即系统拆分成若干子系统,每个子系统下面再拆分成若干组件,后来组件术语改变成部件(不再强调子系统概念)。即子系统与组件整体变更成部件概念。所以,这里get_vendor_parts_list就是从组件信息推导出部件信息。

获取产品部件集合

build/hb/util/preloader/parse_vendor_product_config.py

0108 def transform(config):
0109     subsystems = config.get('subsystems')
0110     if subsystems:
0111         config.pop('subsystems')
0112         parts = from_ss_to_parts(subsystems)
0113         config['parts'] = parts
0114     return config
0140 def get_vendor_parts_list(config):
0141     return transform(config).get('parts')

将config.json中的子系统转换成部件并记录

获取本产品额外部件

build/hb/util/preloader/preloader_process_data.py

0361     def _get_product_specific_parts(self) -> dict:
0362         part_name = 'product_{}'.format(self._name)
0363         subsystem_name = part_name
0364         info = {}
0365         info['{}:{}'.format(subsystem_name, part_name)] = {}
0366         return info

这里得到的info[‘product_xxx:product_xxx’]

增加本设备额外部件

build/hb/util/preloader/preloader_process_data.py

0316     def _get_device_specific_parts(self) -> dict:
0317         info = {}
0318         if self._device_info and self._device_info.get('device_build_path'):
0319             subsystem_name = 'device_{}'.format(self._device_name)
0320             part_name = subsystem_name
0321             info['{}:{}'.format(subsystem_name, part_name)] = {}
0322         return info

319~321行:增加设备额外的部件

生成build var列表

build/hb/util/preloader/preloader_process_data.py

0192     def _update_build_vars(self):
0193         config = self._config
0194         build_vars = {}
0195         if self._version == "1.0":
0196             build_vars = {"os_level": 'large'}
0197         else:
0198             if self._version == "2.0":
0199                 build_vars['os_level'] = config.get("type", "standard")
0200                 device_name = config.get('product_device')
0201                 if device_name:
0202                     build_vars['device_name'] = device_name
0203                 else:
0204                     build_vars['device_name'] = ''
0205                 build_vars['product_company'] = config.get('product_company')
0206             else:
0207                 build_vars['os_level'] = config.get('type', 'mini')
0208                 build_vars['device_name'] = config.get('board')
0209                 if config.get('product_company'):
0210                     build_vars['product_company'] = config.get(
0211                         'product_company')
0212                 elif os.path.dirname(self._config_file) != self._dirs.built_in_product_dir:
0213                     relpath = os.path.relpath(
0214                         self._config_file, self._dirs.vendor_dir)
0215                     build_vars['product_company'] = relpath.split('/')[0]
0216                 else:
0217                     build_vars['product_company'] = config.get(
0218                         'device_company')
0219             build_vars['product_name'] = config.get('product_name')
0220             if 'enable_ramdisk' in config:
0221                 build_vars['enable_ramdisk'] = config.get('enable_ramdisk')
0222             if 'enable_absystem' in config:
0223                 build_vars['enable_absystem'] = config.get('enable_absystem')
0224             if 'build_selinux' in config:
0225                 build_vars['build_selinux'] = config.get('build_selinux')
0226             if 'build_seccomp' in config:
0227                 build_vars['build_seccomp'] = config.get('build_seccomp')
0228             if 'support_jsapi' in config:
0229                 build_vars['support_jsapi'] = config.get('support_jsapi')
0230             if 'chipprod_config_path' in config:
0231                 chipprod_config_path = os.path.join(
0232                     self._dirs.source_root_dir, config.get('chipprod_config_path'))
0233                 if os.path.exists(chipprod_config_path):
0234                     build_vars['chipprod_config_path'] = chipprod_config_path
0235             if 'ext_sdk_config_file' in config:
0236                 ext_sdk_config_file = os.path.join(
0237                     self._dirs.source_root_dir, config.get('ext_sdk_config_file'))
0238                 if os.path.exists(ext_sdk_config_file):
0239                     build_vars['ext_sdk_config_file'] = ext_sdk_config_file
0240             if 'ext_ndk_config_file' in config:
0241                 ext_ndk_config_file = os.path.join(
0242                     self._dirs.source_root_dir, config.get('ext_ndk_config_file'))
0243                 if os.path.exists(ext_ndk_config_file):
0244                     build_vars['ext_ndk_config_file'] = ext_ndk_config_file
0245         build_vars.update(self._device_info)
0246         if build_vars['os_level'] == 'mini' or build_vars['os_level'] == 'small':
0247             toolchain_label = ""
0248         else:
0249             toolchain_label = '//build/toolchain/{0}:{0}_clang_{1}'.format(
0250                 self._device_info.get('target_os'), self._device_info.get('target_cpu'))
0251         build_vars['product_toolchain_label'] = toolchain_label
0252         self._build_vars = build_vars

记录下各种各样的build参数

移除不需要的部件

build/hb/util/preloader/preloader_process_data.py

0255     def _remove_excluded_components(self):
0256         items_to_remove = []
0257         for part, val in self._parts.items():
0258             if "exclude" in val and val["exclude"] == "true":
0259                 items_to_remove.append(part)
0260         for item in items_to_remove:
0261             del self._parts[item]

生成build.prop

build/hb/services/preloader.py

0176     '''Description: generate build prop info to "out/preloader/product_name/build.prop"
0177     @parameter:none
0178     @return :none
0179     '''
0180 
0181     def _generate_build_prop(self):
0182         build_vars_list = []
0183         for key, value in self._build_vars.items():
0184             build_vars_list.append('{}={}'.format(key, value))
0185         with os.fdopen(os.open(self._outputs.build_prop,
0186                                os.O_RDWR | os.O_CREAT, stat.S_IWUSR | stat.S_IRUSR), 'w') as fobj:
0187             fobj.write('\n'.join(build_vars_list))
0188         LogUtil.hb_info(
0189             'generated build prop info to {}/build.prop'.format(self._dirs.preloader_output_dir))

将所有的build参数写入build.prop文件

生成build_config.json

build/hb/services/preloader.py

0165     '''Description: generate build config info to "out/preloader/product_name/build_config.json"
0166     @parameter:none
0167     @return :none
0168     '''
0169 
0170     def _generate_build_config_json(self):
0171         IoUtil.dump_json_file(
0172             self._outputs.build_config_json, self._build_vars)
0173         LogUtil.hb_info(
0174             'generated build config info to {}/build_config.json'.format(self._dirs.preloader_output_dir))

仍然是build参数,不过以json文件格式记录下来

生成部件json

build/hb/services/preloader.py

0191     '''Description: generate parts to "out/preloader/product_name/parts.json"
0192     @parameter:none
0193     @return :none
0194     '''
0195 
0196     def _generate_parts_json(self):
0197         parts_info = {"parts": sorted(list(self._all_parts.keys()))}
0198         IoUtil.dump_json_file(self._outputs.parts_json, parts_info)
0199         LogUtil.hb_info(
0200             'generated product parts info to {}/parts.json'.format(self._dirs.preloader_output_dir))

将部件列表信息按部件名排序后,写入json文件

生成部件配置json

build/hb/services/preloader.py

0202     '''Description: generate parts config to "out/preloader/product_name/parts_config.json"
0203     @parameter:none
0204     @return :none
0205     '''
0206 
0207     def _generate_parts_config_json(self):
0208         parts_config = {}
0209         for part in self._all_parts:
0210             part = part.replace(":", "_")
0211             part = part.replace("-", "_")
0212             part = part.replace(".", "_")
0213             part = part.replace("/", "_")
0214             parts_config[part] = True
0215         IoUtil.dump_json_file(self._outputs.parts_config_json, parts_config)
0216         LogUtil.hb_info(
0217             'generated parts config info to {}/parts_config.json'.format(self._dirs.preloader_output_dir))

将部件中的名称进行规范化处理后写入json文件

生成gn参数属性文件

build/hb/services/preloader.py

0075     '''Description: generate build gnargs prop info to "out/preloader/{product_name}/build_gnargs.prop"
0076     @parameter:none
0077     @return :none
0078     '''
0079 
0080     def _generate_build_gnargs_prop(self):
0081         all_features = {}
0082         for _part_name, vals in self._all_parts.items():
0083             _features = vals.get('features')
0084             if _features:
0085                 all_features.update(_features)
0086         attr_list = []
0087         for key, val in all_features.items():
0088             _item = ''
0089             if isinstance(val, bool):
0090                 _item = f'{key}={str(val).lower()}'
0091             elif isinstance(val, int):
0092                 _item = f'{key}={val}'
0093             elif isinstance(val, str):
0094                 _item = f'{key}="{val}"'
0095             else:
0096                 raise Exception("part feature '{key}:{val}' type not support.")
0097             attr_list.append(_item)
0098         with os.fdopen(os.open(self._outputs.build_gnargs_prop,
0099                                os.O_RDWR | os.O_CREAT, stat.S_IWUSR | stat.S_IRUSR), 'w') as fobj:
0100             fobj.write('\n'.join(attr_list))
0101         LogUtil.hb_info(
0102             'generated build gnargs prop info to {}/build_gnargs.prop'.format(self._dirs.preloader_output_dir))

81~85行:读出所有部件的特性集合
86~97行:对特性字符串进行规范化处理,只支持整数,布尔类型和字符串
98~102行:写入规范化后的结果

生成特性集合

build/hb/services/preloader.py

0104     '''Description: generate features to "out/preloader/{product_name}/features.json"
0105     @parameter:none
0106     @return :none
0107     '''
0108 
0109     def _generate_features_json(self):
0110         all_features = {}
0111         part_feature_map = {}
0112         for _part_name, vals in self._all_parts.items():
0113             _features = vals.get('features')
0114             if _features:
0115                 all_features.update(_features)
0116             if _features:
0117                 part_feature_map[_part_name.split(
0118                     ':')[1]] = list(_features.keys())
0119         parts_feature_info = {
0120             "features": all_features,
0121             "part_to_feature": part_feature_map
0122         }
0123         IoUtil.dump_json_file(self._outputs.features_json, parts_feature_info)
0124         LogUtil.hb_info(
0125             'generated features info to {}/features.json'.format(self._dirs.preloader_output_dir))

114~115行:汇总所有的特性
116~118行:记录每一个部件下的特性集合(部件名使用简称)
119~125行:结果写入文件

生成系统能力记录

build/hb/services/preloader.py

0127     '''Description: generate syscap to "out/preloader/product_name/syscap.json"
0128     @parameter:none
0129     @return :none
0130     '''
0131 
0132     def _generate_syscap_json(self):
0133         all_syscap = {}
0134         part_syscap_map = {}
0135         for _part_name, vals in self._all_parts.items():
0136             _syscap = vals.get('syscap')
0137             if _syscap:
0138                 all_syscap.update(_syscap)
0139                 part_syscap_map[_part_name.split(':')[1]] = _syscap
0140         parts_syscap_info = {
0141             "syscap": all_syscap,
0142             "part_to_syscap": part_syscap_map
0143         }
0144         IoUtil.dump_json_file(self._outputs.syscap_json, parts_syscap_info)
0145         LogUtil.hb_info(
0146             'generated syscap info to {}/syscap.json'.format(self._dirs.preloader_output_dir))

136~139行:获取系统能力以及组件和能力集的映射
然后写入结果

生成排除模块记录

build/hb/services/preloader.py

0148     '''Description: generate exclusion modules info to "out/preloader/product_name/exclusion_modules.json"
0149     @parameter:none
0150     @return :none
0151     '''
0152 
0153     def _generate_exclusion_modules_json(self):
0154         exclusions = {}
0155         for _part_name, vals in self._all_parts.items():
0156             _exclusions = vals.get('exclusions')
0157             if _exclusions:
0158                 pair = dict()
0159                 pair[_part_name] = _exclusions
0160                 exclusions.update(pair)
0161         IoUtil.dump_json_file(self._outputs.exclusion_modules_json, exclusions)
0162         LogUtil.hb_info(
0163             'generated exclusion modules info to {}/exclusion_modules.json'.format(self._dirs.preloader_output_dir))

将各部件排除的模块整合起来
再记录到json文件中
本能力目前在各产品还没有看见使用。

生成子系统配置json

build/hb/services/preloader.py

0219     '''Description: generate subsystem config info to "out/preloader/product_name/subsystem_config.json"
0220     @parameter:none
0221     @return :none
0222     '''
0223 
0224     def _generate_subsystem_config_json(self):
0225         if self._subsystem_info:
0226             self._subsystem_info.update(
0227                 self._product._get_product_specific_subsystem())
0228             self._subsystem_info.update(
0229                 self._product._get_device_specific_subsystem())
0230         IoUtil.dump_json_file(
0231             self._outputs.subsystem_config_json, self._subsystem_info)
0232         LogUtil.hb_info(
0233             'generated subsystem config info to {}/subsystem_config.json'.format(self._dirs.preloader_output_dir))

汇总设备和产品的配置信息,然后记录到json文件中

生成系统能力配置json

build/hb/services/preloader.py

0235     '''Description: generate systemcapability_json to "out/preloader/product_name/systemcapability.json"
0236     @parameter:none
0237     @return :none
0238     '''
0239 
0240     def _generate_systemcapability_json(self):
0241         IoUtil.dump_json_file(
0242             self._outputs.systemcapability_json, self._product._syscap_info)
0243         LogUtil.hb_info(
0244             'generated system capability info to {}/systemcapability.json'.format(self._dirs.preloader_output_dir))

写入当前的系统能力信息

load

build/hb/modules/ohos_build_module.py

0081     def _load(self):
0082         self._run_phase(BuildPhase.LOAD)
0083         if self.args_dict.get('fast_rebuild', None) and not self.args_dict.get('fast_rebuild').arg_value:
0084             self.loader.run()

82行:先支持LOAD阶段的处理
这些处理有

  • resolve_strict_mode
  • resolve_scalable_build
  • resolve_build_example
  • resolve_build_platform_name
  • resolve_build_xts
  • resolve_ignore_api_check
  • resolve_load_test_config

82~84行:在没有开启fast_rebuild的情况下,还要运行loader

严格模式

build/hb/resolver/build_args_resolver.py

0327     def resolve_strict_mode(target_arg: Arg, build_module: BuildModuleInterface):
0328         """resolve '--strict-mode' arg.
0329         :param target_arg: arg object which is used to get arg value.
0330         :param build_module [maybe unused]: build module object which is used to get other services.
0331         :phase: load.
0332         :raise OHOSException: when preloader or loader results not correct
0333         """
0334         if target_arg.arg_value:
0335             preloader = build_module.preloader
0336             loader = build_module.loader
0337             if not preloader.outputs.check_outputs():
0338                 raise OHOSException('Preloader result not correct', "1001")
0339             if not loader.outputs.check_outputs():
0340                 raise OHOSException('Loader result not correct ', "2001")

目前这2个check_outputs函数没有看到定义的地方
这个命令行参数当前不可用,后续可能会支持

增量构建

build/hb/resolver/build_args_resolver.py

0343     def resolve_scalable_build(target_arg: Arg, build_module: BuildModuleInterface):
0344         """resolve '--scalable-build' arg.
0345         :param target_arg: arg object which is used to get arg value.
0346         :param build_module [maybe unused]: build module object which is used to get other services.
0347         :phase: load.
0348         """
0349         loader = build_module.loader
0350         loader.regist_arg("scalable_build", target_arg.arg_value)

记录增量构建开关

构建范例

build/hb/resolver/build_args_resolver.py

0353     def resolve_build_example(target_arg: Arg, build_module: BuildModuleInterface):
0354         """resolve '--build-example' arg.
0355         :param target_arg: arg object which is used to get arg value.
0356         :param build_module [maybe unused]: build module object which is used to get other services.
0357         :phase: load.
0358         """
0359         loader = build_module.loader
0360         loader.regist_arg("build_example", target_arg.arg_value)

记录build_example开关

构建平台名称

build/hb/resolver/build_args_resolver.py

0363     def resolve_build_platform_name(target_arg: Arg, build_module: BuildModuleInterface):
0364         """resolve '---build-platform-name' arg
0365         :param target_arg: arg object which is used to get arg value.
0366         :param build_module [maybe unused]: build module object which is used to get other services.
0367         :phase: load.
0368         """
0369         loader = build_module.loader
0370         loader.regist_arg("build_platform_name", target_arg.arg_value)

记录构建平台的名称

构建测试用例

build/hb/resolver/build_args_resolver.py

0373     def resolve_build_xts(target_arg: Arg, build_module: BuildModuleInterface):
0374         """resolve '--build-xts' arg
0375         :param target_arg: arg object which is used to get arg value.
0376         :param build_module [maybe unused]: build module object which is used to get other services.
0377         :phase: load.
0378         """
0379         loader = build_module.loader
0380         for gn_arg in build_module.args_dict['gn_args'].arg_value:
0381             if 'build_xts' in gn_arg:
0382                 variable, value = gn_arg.split('=')
0383                 if str(value).lower() == 'false':
0384                     value = False
0385                 elif str(value).lower() == 'true':
0386                     value = True
0387                 loader.regist_arg(variable, value)
0388                 return
0389         loader.regist_arg("build_xts", target_arg.arg_value)

如果在gn_args参数里面配置了build_xts, 则优先使用gn_args里面的开关
否则使用外面这个开关

忽略API检查

build/hb/resolver/build_args_resolver.py

0392     def resolve_ignore_api_check(target_arg: Arg, build_module: BuildModuleInterface):
0393         """resolve '--ignore-api-check' arg
0394         :param target_arg: arg object which is used to get arg value.
0395         :param build_module [maybe unused]: build module object which is used to get other services.
0396         :phase: load.
0397         """
0398         loader = build_module.loader
0399         if len(target_arg.arg_value):
0400             loader.regist_arg("ignore_api_check", target_arg.arg_value)
0401         else:
0402             loader.regist_arg("ignore_api_check", [
0403                               'xts', 'common', 'developertest'])

记录用户指定的忽略api检查的部件集合
如果用户没有指定,默认有3个部件不检查API

加载测试配置

build/hb/resolver/build_args_resolver.py

0406     def resolve_load_test_config(target_arg: Arg, build_module: BuildModuleInterface):
0407         """resolve '--load-test-config' arg
0408         :param target_arg: arg object which is used to get arg value.
0409         :param build_module [maybe unused]: build module object which is used to get other services.
0410         :phase: load.
0411         """
0412         loader = build_module.loader
0413         loader.regist_arg("load_test_config", target_arg.arg_value)

记录下对应开关值

loader

build/hb/services/interface/load_interface.py

0044     def run(self):
0045         self.__post_init__()
0046         self._execute_loader_args_display()
0047         self._check_parts_config_info()
0048         self._generate_subsystem_configs()
0049         self._generate_target_platform_parts()
0050         self._generate_system_capabilities()
0051         self._generate_stub_targets()
0052         self._generate_platforms_part_by_src()
0053         self._generate_target_gn()
0054         self._generate_phony_targets_build_file()
0055         self._generate_required_parts_targets()
0056         self._generate_required_parts_targets_list()
0057         self._generate_src_flag()
0058         self._generate_auto_install_part()
0059         self._generate_platforms_list()
0060         self._generate_part_different_info()
0061         self._generate_infos_for_testfwk()
0062         self._check_product_part_feature()
0063         self._generate_syscap_files()

loader初始化

build/hb/services/loader.py

0058     def __post_init__(self):
0059         self.source_root_dir = self.config.root_path + '/'
0060         self.gn_root_out_dir = self.config.out_path if not self.config.out_path.startswith(
0061             '/') else os.path.relpath(self.config.out_path, self.config.root_path)
0062         self.os_level = self.config.os_level if self.config.os_level else "standard"
0063         self.target_cpu = self.config.target_cpu if self.config.target_cpu else "arm"
0064         self.target_os = self.config.target_os if self.config.target_os else "ohos"
0065         self.config_output_relpath = os.path.join(
0066             self.gn_root_out_dir, 'build_configs')
0067         self.config_output_dir = os.path.join(
0068             self.source_root_dir, self.config_output_relpath)
0069         self.target_arch = '{}_{}'.format(self.target_os, self.target_cpu)
0070         self.subsystem_config_file = os.path.join(
0071             self.config.root_path, 'out/preloader', self.config.product, 'subsystem_config.json')
0072         self.platforms_config_file = os.path.join(
0073             self.config.root_path, 'out/preloader', self.config.product, 'platforms.build')
0074         self.exclusion_modules_config_file = os.path.join(
0075             self.config.root_path, 'out/preloader', self.config.product, 'exclusion_modules.json')
0076         self.example_subsystem_file = os.path.join(
0077             self.config.root_path, 'build', 'subsystem_config_example.json')
0078 
0079         # check config args
0080         self._check_args()
0081 
0082         self.build_example = self.args_dict.get('build_example')
0083         if not self.build_example:
0084             self.example_subsystem_file = ""
0085         self.scalable_build = self.args_dict.get('scalable_build')
0086         self.build_platform_name = self.args_dict.get('build_platform_name')
0087         self.build_xts = self.args_dict.get('build_xts')
0088         self.ignore_api_check = self.args_dict.get('ignore_api_check')
0089         self.load_test_config = self.args_dict.get('load_test_config')
0090         self.subsystem_configs = subsystem_scan.scan(self.subsystem_config_file,
0091                                                      self.example_subsystem_file,
0092                                                      self.source_root_dir)
0093 
0094         self._subsystem_info = subsystem_info.get_subsystem_info(
0095             self.subsystem_config_file,
0096             self.example_subsystem_file,
0097             self.source_root_dir,
0098             self.config_output_relpath,
0099             self.os_level)
0100         overrided_components = self._override_components()
0101 
0102         self._platforms_info = platforms_loader.get_platforms_info(
0103             self.platforms_config_file,
0104             self.source_root_dir,
0105             self.gn_root_out_dir,
0106             self.target_arch,
0107             self.config_output_relpath,
0108             self.scalable_build)
0109         self.variant_toolchains = self._platforms_info.get(
0110             'variant_toolchain_info').get('platform_toolchain')
0111         self._all_platforms = self.variant_toolchains.keys()
0112         self.build_platforms = self._get_build_platforms()
0113         self.parts_config_info = load_ohos_build.get_parts_info(
0114             self.source_root_dir,
0115             self.config_output_relpath,
0116             self._subsystem_info,
0117             self.variant_toolchains,
0118             self.target_arch,
0119             self.ignore_api_check,
0120             self.exclusion_modules_config_file,
0121             self.load_test_config,
0122             overrided_components,
0123             self.build_xts)
0124         self.parts_targets = self.parts_config_info.get('parts_targets')
0125         self.phony_targets = self.parts_config_info.get('phony_target')
0126         self.parts_info = self.parts_config_info.get('parts_info')
0127         self.target_platform_parts = self._get_platforms_all_parts()
0128         self.target_platform_stubs = self._get_platforms_all_stubs()
0129         self.required_parts_targets_list = self._get_required_build_parts_list()
0130         self.required_phony_targets = self._get_required_phony_targets()
0131         self.required_parts_targets = self._get_required_build_targets()

59~89行:记录本阶段解析好的命令行参数,以及一些文件的路径
90~92行:扫描subsystem_config.json和subsystem_config_example.json内的所有subsystem,含ohos.build或bundle.json文件的,即为合法的部件,否则为不参与编译的源码。
94~99行:将扫描出的subsystem写入json文件,并返回扫描结果
out/productname/build_configs/subsystem_info/src_subsystem_info.json
out/productname/build_configs/subsystem_info/no_src_subsystem_info.json
out/productname/build_configs/subsystem_info/subsystem_build_config.json
100行:处理被替换的部件, 厂商自己实现的部件替换掉系统默认实现的部件
102~108行:获取平台信息
109~110行:获取平台对应的工具链信息
111行:获取本次编译的所有平台,当前只支持phone
112行:获取本次构建的品台,仅支持phone
113~123行:获取ohos.build两个的部件信息
124~125行:记录部件编译目标和伪目标
126~131行:记录相关的需要构建的目标

扫描已知子系统中的部件

build/hb/util/loader/subsystem_scan.py

0075 def scan(subsystem_config_file, example_subsystem_file, source_root_dir):
0076     subsystem_infos = _read_config(subsystem_config_file,
0077                                    example_subsystem_file)
0078     # add common subsystem info
0079     subsystem_infos.update(_default_subsystem)
0080 
0081     no_src_subsystem = {}
0082     _build_configs = {}
0083     for key, val in subsystem_infos.items():
0084         _all_build_config_files = []
0085         if not isinstance(val, list):
0086             val = [val]
0087         else:
0088             if not _check_path_prefix(val):
0089                 raise OHOSException(
0090                     "subsystem '{}' path configuration is incorrect.".format(
0091                         key), "2013")
0092         _info = {'path': val}
0093         for _path in val:
0094             _subsystem_path = os.path.join(source_root_dir, _path)
0095             _build_config_files = _scan_build_file(_subsystem_path)
0096             _all_build_config_files.extend(_build_config_files)
0097         if _all_build_config_files:
0098             _info['build_files'] = _all_build_config_files
0099             _build_configs[key] = _info
0100         else:
0101             no_src_subsystem[key] = val
0102 
0103     scan_result = {
0104         'source_path': source_root_dir,
0105         'subsystem': _build_configs,
0106         'no_src_subsystem': no_src_subsystem
0107     }
0108     LogUtil.hb_info('subsytem config scan completed')
0109     return scan_result

76行:读取已知子系统的名称和路径列表
79行:添加common子系统
83行:遍历所有子系统
85~86行: path只有一项,直接存入
87~91行:path是一个列表,那么列表中最多只能含一个(device或vendor前缀)的路径
93~96行:遍历path列表,在这些路径中找出软件包配置文件
97~99行:记录下这些配置文件
100~101行:无软件包的子系统也记录一下
103~109行:记录结果并返回

根据子系统路径配置读取子系统信息

build/hb/util/loader/subsystem_scan.py

0028 def _read_config(subsystem_config_file, example_subsystem_file):
0029     if not os.path.exists(subsystem_config_file):
0030         raise OHOSException(
0031             "config file '{}' doesn't exist.".format(subsystem_config_file), "2013")
0032     subsystem_config = read_json_file(subsystem_config_file)
0033     if subsystem_config is None:
0034         raise OHOSException("read file '{}' failed.".format(
0035             subsystem_config_file), "2013")
0036 
0037     # example subsystem
0038     if example_subsystem_file:
0039         example_subsystem_config = read_json_file(example_subsystem_file)
0040         if example_subsystem_config is not None:
0041             subsystem_config.update(example_subsystem_config)
0042 
0043     subsystem_info = {}
0044     for key, val in subsystem_config.items():
0045         if 'path' not in val:
0046             raise OHOSException(
0047                 "subsystem '{}' not config path.".format(key), "2013")
0048         subsystem_info[key] = val.get('path')
0049     return subsystem_info

48行:将每个子系统的名称和路径记录到新的字典并返回

检查非法路径

build/hb/util/loader/subsystem_scan.py

0066 def _check_path_prefix(paths):
0067     allow_path_prefix = ['vendor', 'device']
0068     result = list(
0069         filter(lambda x: x is False,
0070                map(lambda p: p.split('/')[0] in allow_path_prefix, paths)))
0071     return len(result) <= 1

在路径列表中,最多只能出现一次不是device或vendor开头的路径
map表示针对对每条路径按/切分获取首个字符串,并判断字符串是否device或vendor
filter表示,将map计算结果为False的进行汇总
最终结果就是路径列表中允许出现device和vendor开头,其它开头的最多只能一条。

寻找部件

build/hb/util/loader/subsystem_scan.py

0052 def _scan_build_file(subsystem_path):
0053     _files = []
0054     _bundle_files = []
0055     for root, dirs, files in os.walk(subsystem_path):
0056         for name in files:
0057             if name == 'ohos.build':
0058                 _files.append(os.path.join(root, name))
0059             elif name == 'bundle.json':
0060                 _bundle_files.append(os.path.join(root, name))
0061     if _bundle_files:
0062         _files.extend(_bundle_files)
0063     return _files

遍历子系统目录,寻找obos.build或bundle.json文件,返回文件路径集合。

获取存在部件的子系统列表

build/hb/util/loader/subsystem_info.py

0046 def get_subsystem_info(subsystem_config_file, example_subsystem_file,
0047                        source_root_dir, config_output_path, os_level):
0048     if not subsystem_config_file:
0049         subsystem_config_file = 'build/subsystem_config.json'
0050 
0051     subsystem_configs = {}
0052     output_dir_realpath = os.path.join(source_root_dir, config_output_path)
0053     subsystem_configs = subsystem_scan.scan(subsystem_config_file,
0054                                             example_subsystem_file,
0055                                             source_root_dir)
0056     config = Config()
0057     subsystem_config_overlay_file = os.path.join(
0058         config.product_path, "subsystem_config_overlay.json")
0059     if os.path.isfile(subsystem_config_overlay_file):
0060         subsystem_config_overlay = {}
0061         subsystem_config_overlay = subsystem_scan.scan(subsystem_config_overlay_file,
0062                                                        example_subsystem_file,
0063                                                        source_root_dir)
0064         subsystem_configs['subsystem'].update(
0065             subsystem_config_overlay['subsystem'])
0066         subsystem_configs['no_src_subsystem'].update(
0067             subsystem_config_overlay['no_src_subsystem'])
0068     _output_subsystem_configs(output_dir_realpath, subsystem_configs)
0069     return subsystem_configs.get('subsystem')

57~67行:如果存在overlay配置,则需要将overlay相关子系统也考虑进来
68行:保存查找到的部件信息
69行:返回部件信息

保存部件信息

build/hb/util/loader/subsystem_info.py

0024 def _output_subsystem_configs(output_dir, subsystem_configs):
0025     build_config_file_name = "subsystem_build_config.json"
0026     build_config_file = os.path.join(output_dir, 'subsystem_info',
0027                                      build_config_file_name)
0028     write_json_file(build_config_file, subsystem_configs)
0029 
0030     src_output_file_name = "src_subsystem_info.json"
0031     no_src_output_file_name = "no_src_subsystem_info.json"
0032 
0033     src_subsystem = {}
0034     for key, val in subsystem_configs.get('subsystem').items():
0035         src_subsystem[key] = val.get('path')
0036     src_output_file = os.path.join(output_dir, 'subsystem_info',
0037                                    src_output_file_name)
0038     write_json_file(src_output_file, src_subsystem)
0039 
0040     no_src_output_file = os.path.join(output_dir, 'subsystem_info',
0041                                       no_src_output_file_name)
0042     write_json_file(no_src_output_file,
0043                     subsystem_configs.get('no_src_subsystem'))

25~28行:保存完整部件信息
33~38行:保存含部件的子系统路径映射表
40~43行:保存不含部件的子系统列表

替换冗余部件

build/hb/services/loader.py

0794     def _override_components(self):
0795         '''Description: Check whether there are components that need to be replaced, and if so, 
0796             replace the component configuration file bundle.json in subsystem_info and update 
0797             the component list generated by the preloader.
0798         @parameter:none
0799         @return :overrided_components
0800         '''
0801         parts_file = self.platforms_config_file.replace(
0802             "platforms.build", "parts.json")
0803         all_parts = read_json_file(parts_file)
0804         if "parts" not in all_parts:
0805             LogUtil.hb_warning("{} does not contain parts!".format(parts_file))
0806             return {}
0807         overrided = False
0808         overrided_components = {}
0809         all_parts = all_parts["parts"]
0810         component_override_map = {}
0811         for subsystem_name, build_config_info in self._subsystem_info.items():
0812             if "build_files" not in build_config_info:
0813                 continue
0814 
0815             # scan all bundle.json or ohos.build files with named groups
0816             for build_file in build_config_info["build_files"]:
0817 
0818                 # ohos.build does not support overrided components
0819                 if not build_file.endswith('bundle.json'):
0820                     continue
0821 
0822                 # Only device or vendor components can do named groups extensions
0823                 if (not build_file.startswith(self.source_root_dir + 'device/')) \
0824                         and (not build_file.startswith(self.source_root_dir + 'vendor/')):
0825                     continue
0826 
0827                 # "subsystem", "name" and "override" is required
0828                 component = read_json_file(build_file).get("component")
0829 
0830                 if (not component) or (not all(key in component for key in ("subsystem", "name", "override"))):
0831                     continue
0832 
0833                 full_part_name = f"{component.get('subsystem')}:{component.get('name')}"
0834                 if full_part_name not in all_parts:
0835                     LogUtil.hb_warning("{} was not configured for this product: {}".format(
0836                         build_file, full_part_name))
0837                     continue
0838 
0839                 if self._override_one_component(self._subsystem_info, component, build_file, all_parts, overrided_components, component_override_map):
0840                     overrided = True
0841 
0842         if overrided:
0843             # Update parts.json and parts_config.json generated by preloader
0844             write_json_file(parts_file, {"parts": all_parts})
0845             parts_file = self.platforms_config_file.replace(
0846                 "platforms.build", "parts_config.json")
0847             self._output_parts_config_json(all_parts, parts_file)
0848 
0849         write_json_file(
0850             f"{self.config_output_dir}/component_override_map.json", component_override_map)
0851         return overrided_components

801~810行:读取部件列表
811行:遍历子系统
816行:遍历子系统中部件列表
819~820行:只处理bundle.json描述的部件
823~825行:只处理device或vendor开头的部件
828~831行:部件中必须包含subsystem, name, override属性
833~837行:子系统与组件名和部件名需匹配
839~840行:移除被本组件替换(覆盖)的组件
842~850行:更新相关文件,并记录下覆盖关系
851行:返回被覆盖的部件集合

替换一个部件

build/hb/services/loader.py

0853     def _override_one_component(self, subsystem_info, component, build_file, all_parts, overrided_components, component_override_map):
0854         '''Description: Perform a replacement of a single component and return the component list update result.
0855         @parameter:subsystem_info, component, build_file, all_parts, overrided_components
0856         @return :True or False(Whether replacement has been performed)
0857         '''
0858         splits = component["override"].split(":")
0859         if len(splits) != 2:
0860             LogUtil.hb_warning(
0861                 "{} override value is invalid format. Skip override process".format(build_file))
0862             return False
0863         overrided_subsystem = splits[0]
0864         overrided_component = splits[1]
0865         if overrided_subsystem not in subsystem_info:
0866             LogUtil.hb_warning(
0867                 "{} override invalid subsystem. Skip override process".format(build_file))
0868             return False
0869 
0870         founded_bundle = ""
0871 
0872         for bundle in subsystem_info[overrided_subsystem]["build_files"]:
0873             if not bundle.endswith('bundle.json'):
0874                 continue
0875 
0876             bundle_obj = read_json_file(bundle)
0877 
0878             if bundle_obj.get("component", {}).get("name") == overrided_component:
0879                 founded_bundle = bundle
0880                 break
0881 
0882         if founded_bundle:
0883             origin_component = read_json_file(build_file).get('component')
0884             LogUtil.hb_warning(
0885                 f"You are trying to override \"{component['override']}\" with \"{origin_component.get('subsystem')}:{origin_component.get('name')}\". \nPlease ensure that the modules in \"{component['override']}\" only rely on the interfaces of other components through \"external_deps\"")
0886 
0887             # replace bundle.json in subsystem_info's build_files
0888             subsystem_info[overrided_subsystem]["build_files"].remove(
0889                 founded_bundle)
0890 
0891             # Update parts.json generated by preloader, which means that new added components will not be installed
0892             # Ensure that the overrided components will be installed
0893             full_partname = f"{overrided_subsystem}:{overrided_component}"
0894             if full_partname in all_parts:
0895                 all_parts.remove(full_partname)
0896 
0897             overrided_components[f"{component['subsystem']}:{component['name']}"] = {
0898                 'subsystem': overrided_subsystem,
0899                 'partName': overrided_component
0900             }
0901             component_override_map[overrided_component] = component["name"]
0902             return True
0903         LogUtil.hb_warning(
0904             "{}:{} is not configured in product, \new add component will be installed!".format(
0905                 overrided_subsystem, overrided_component))
0906         return False

858~864行:解析被替换的子系统和组件名
865~868行:子系统不存在
872行:遍历子系统中的包
873~874行:只支持bundle.json类型的包
878~880行:找到匹配的包
883~885行:获取原始组件,并输出覆盖动作日志
888~889行:移除被覆盖的bundle.json路径
893~895行:移除对应的部件名
897~900行:记录下部件覆盖关系–覆盖了谁
901行:记录下部件覆盖关系–被谁覆盖

获取平台信息

build/hb/util/loader/platforms_loader.py

0183 def get_platforms_info(platforms_config_file, source_root_dir, root_build_dir,
0184                        target_arch, config_output_relpath, scalable_build):
0185     platform_loader = PlatformsLoader(platforms_config_file, source_root_dir,
0186                                       root_build_dir, target_arch,
0187                                       scalable_build)
0188 
0189     platforms_info_output_dir = 'platforms_info'
0190     all_parts = platform_loader.get_all_parts()
0191     all_parts_file = os.path.join(source_root_dir, config_output_relpath,
0192                                   platforms_info_output_dir, "all_parts.json")
0193     write_json_file(all_parts_file, all_parts)
0194     LogUtil.hb_info(
0195         "generate all parts of platforms info to '{}'".format(all_parts_file))
0196 
0197     # variant to toolchain and toolchain to variant
0198     toolchain_to_variant_dict = platform_loader.platforms_toolchain()
0199     toolchain_variant_info_file = os.path.join(source_root_dir,
0200                                                config_output_relpath,
0201                                                platforms_info_output_dir,
0202                                                "toolchain_to_variant.json")
0203     write_json_file(toolchain_variant_info_file,
0204                     toolchain_to_variant_dict,
0205                     check_changes=True)
0206     LogUtil.hb_info("generate toolchain to variant of platforms info to '{}'".format(
0207         toolchain_variant_info_file))
0208 
0209     result = {}
0210     result['all_parts'] = all_parts
0211     result['all_stubs'] = platform_loader.get_all_stubs()
0212     result['variant_toolchain_info'] = toolchain_to_variant_dict
0213     return result

189~196行:将所有部件信息换一种形式存储起来
198~207行:将平台与工具链之间的映射信息存储起来
209~213行:记录并返回上述信息

获取平台和工具链的映射

build/hb/util/loader/platforms_loader.py

0168     def platforms_toolchain(self):
0169         self._loading()
0170         platform_toolchain = {}
0171         toolchain_platform = {}
0172         for key, val in self._platforms_info.items():
0173             _toolchain = val.get('toolchain')
0174             platform_toolchain[key] = _toolchain
0175             toolchain_platform[_toolchain] = key
0176         _result = {
0177             "platform_toolchain": platform_toolchain,
0178             "toolchain_platform": toolchain_platform
0179         }
0180         return _result

174行:从平台到工具链的映射
175行:从工具链到平台的映射
177~178行:记录下上述2个映射表

显示loader参数

检查部件配置信息

生成子系统配置

生成目标平台部件

生成系统能力

生成桩目标

基于源码生成平台部件

生成目标gn

生成伪目标构建文件

生成必要的部件目标

生成必要的部件目标列表

生成源码对应的标识

生成自动安装部件

生成平台列表

生成部件差异化信息

生成测试框架信息

检查产品部件特性

生成系统能力文件

gn前处理

gn处理

构建类型

日志等级

测试集选择

gn参数

gn flag

快速重建

gn执行

gn后处理

ninja前处理

ninja处理

ninja错误后继续

只生成gn

ninja执行

ninja后处理

root或user构建

设备类型

打包镜像

rom尺寸统计

ccache性能度量

汇总编译警告

ninja性能度量

计算overlap rate

依赖分析清理

postbuild

清理构建参数

未完待续

你可能感兴趣的:(python,鸿蒙,OpenHarmony,harmonyos)