基于4.1-release,(因为本人主c++,对于python中的一些方法可能有误欢迎大家指出)
一.build.py解读
命令行执行./build.py --product-name oriole –ccache --no-prebuilt-sdk
调用当前目录下的build.py脚本文件后面为相应参数
1.进入main函数
def main(): root_path = find_top() return build(root_path, sys.argv[1:]) if __name__ == "__main__": sys.exit(main())
因此,执行到到 if __name__ == "__main__":
这个条件,调用 main()
函数,
root_path = find_top()调用前面定义的函数
2.进入find_top()函数
def find_top() -> str: cur_dir = os.getcwd() while cur_dir != "/": build_config_file = os.path.join( cur_dir, 'build/config/BUILDCONFIG.gn') if os.path.exists(build_config_file): return cur_dir cur_dir = os.path.dirname(cur_dir)
#find_top() -> str 以字符串的格式返回结果
类似python导入os,在 C++ 中,你可以使用 <filesystem>
标准库(C++17 引入)来进行文件系统操作,其中包含了一系列用于文件路径操作的函数和类。比如,你可以使用 std::filesystem::path
类来表示文件路径,并进行路径的拼接、分解等操作。
举个例子,你可以像这样使用 <filesystem>
标准库来构建文件路径:
#include <iostream> #include <filesystem> namespace fs = std::filesystem; int main() { fs::path cur_dir = fs::current_path(); fs::path build_config_file = cur_dir / "build" / "config" / "BUILDCONFIG.gn"; std::cout << "Build config file path: " << build_config_file << std::endl; return 0; }
这段代码会输出构建的文件路径,其中 cur_dir
是当前工作目录的路径,build_config_file
是要构建的文件的路径。通过使用 <filesystem>
标准库,你可以方便地进行跨平台的文件系统操作。
while cur_dir != "/": #当路径不为顶级目录 os.getcwd()
在根目录执行时会返回空字符串而不是根目录本身,所以即使是根目录也会执行循环
build_config_file = os.path.join(cur_dir, 'build/config/BUILDCONFIG.gn')
#os.path.join拼接两个路径并传给build_config_file
if os.path.exists(build_config_file): return cur_dir
cur_dir = os.path.dirname(cur_dir) # 在则跳出循环返回cur_dir给主函数,否则就向上(父级目录)找
3.进入主函数
root_path = find_top() #主函数读取到字符串, return build(root_path, sys.argv[1:])
4,进入build函数
#执行build函数(当前路径,接受敲入命令行第一个以外的所有参数)及./build.py除外 --product-name oriole –ccache -- no-prebuilt-sdk 其作用既是构建该路径下的项目
def build(path: str, args_list: list) -> str: python_dir = None if "--python-dir" in args_list: index = args_list.index("--python-dir") if index < len(args_list) - 1: python_dir = args_list[index + 1] del args_list[index: index + 2] else: print("-python-dir parmeter missing value.") sys.exit() python_executable = get_python(python_dir) cmd = [python_executable, 'build/hb/main.py', 'build'] + args_list return check_output(cmd, cwd=path)
--python-dir
是一个选项,用于指定 Python 的安装目录或路径。通常情况下,它可以让用户指定程序在哪里找到 Python 解释器或 Python 模块
如果 --python-dir
存在于参数列表中,就会找到它在列表中的位置, if index < len(args_list) - 1:python_dir = args_list[index + 1]检查其后是否存在对应的值。如果存在值,则将其赋给 python_dir
变量,并从参数列表中删除 --python-dir
及其对应的值。如果不存在对应的值,则输出错误信息并退出程序。
所以命令行中没有指定python路径不进入判断
python_executable = get_python(python_dir)
这行代码的作用是根据指定的 Python 路径(python_dir
)来获取 Python 解释器的可执行文件路径。
在 Unix/Linux 系统上,Python 解释器通常被安装在 /usr/bin/python
或者 /usr/local/bin/python
等位置
5.进入get_python函数
函数的作用是根据给定的相对路径找到 Python 解释器的可执行文件路径。
def get_python(python_relative_dir: str) -> str: topdir = find_top() if python_relative_dir is None: python_relative_dir = 'prebuilts/python' python_base_dir = os.path.join(topdir, python_relative_dir) if os.path.exists(python_base_dir): python_dir = search(python_base_dir, 'python3') return os.path.join(python_dir, 'python3') else: print("please execute build/prebuilts_download.sh.", "if you used '--python-dir', check whether the input path is valid.") sys.exit()
get_python
函数,接受一个参数 python_relative_dir,(我们这里应该为none)。
函数调用了 find_top()
函数来获取顶级目录的路径,
然后,如果未提供 python_relative_dir
参数,函数将其默认设置为 'prebuilts/python'
。这意味着如果用户没有指定 Python 的相对路径,将使用默认路径 'prebuilts/python'
。也就是我们在配置环境时build/prebuilts_download.sh
接着,函数将顶级目录路径和 Python 相对路径拼接起来,得到 Python 的基础目录路径 python_base_dir
。
接下来,函数检查 python_base_dir
是否存在。如果存在,函数调用了 search()
函数来在 Python 基础目录中查找名为 'python3'
的文件。
def search(findir: str, target: str) -> str or bool: for root, _, files in os.walk(findir): if target in files: return root return False
这段代码定义了一个名为 search
的函数,它接受两个参数:findir
和 target
,分别表示要搜索的目录和目标文件名。
函数使用 os.walk()
函数来遍历指定目录及其子目录中的所有文件和目录。在每次迭代中,os.walk()
返回一个三元组 (root, dirs, files)
,其中 root
是当前正在遍历的目录的路径,dirs
是该目录中的子目录列表,files
是该目录中的文件列表。
在循环中,函数检查目标文件名 target
是否在当前目录的文件列表中。如果是,说明找到了目标文件,函数立即返回当前目录的路径 root
,表示找到了目标文件所在的目录。
如果遍历完整个目录树后仍未找到目标文件,函数将返回 False
,表示未找到目标文件
所以寻找python3解释器,找到了 Python 解释器的路径,函数将其与 'python3'
文件名拼接起来,并返回完整的 Python 可执行文件路径。
如果未找到 Python 基础目录,函数将打印一条错误消息,并提示用户执行 build/prebuilts_download.sh
脚本来下载 Python,或者检查提供的 --python-dir
参数是否有效。然后,程序将退出。
6.重回build函数开始构建
这里我们得到了python解释器的路径继续执行
**cmd = [python_executable, 'build/hb/main.py', 'build'] + args_list**
这一行构造了要执行的命令列表 cmd
。该列表包含了 Python 解释器路径、 build/hb/main.py
文件路径、build
参数,以及传入的其他参数列表
return check_output(cmd, cwd=path)
7.check_output函数
def check_output(cmd: str, **kwargs) -> str: process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, **kwargs) for line in iter(process.stdout.readline, ''): sys.stdout.write(line) sys.stdout.flush() process.wait() ret_code = process.returncode return ret_code
让python以操作系统的身份运行这条编译命令
subprocess.Popen 创建子进程,这段代码会执行你提供的编译命令,并且通过管道捕获子进程的输出,使得你可以在 Python 中获取到编译结果,同时还能处理可能出现的错误信息。
后面一段则是打印编译构建的命令内容到终端
小结
我们可以看出build.py的主要作用就是检查构建的命令和相应构建环境
cmd = [python_executable, 'build/hb/main.py', 'build'] + args_list 才是关键的构建入口
process = subprocess.Popen执行命令
二.进入构建子系统build/hb/main.py
执行编译命令后我们开始构建,这里我们就不再次一 一讲解了
注意以下内容
1.理清重点内容,省略部分函数实现的过程只讲实现的功能
2.main.py中导入各种内置库,定义库,所以有些实现(路径变量赋值,等)是在定义函数之前
3.后创建main类执行定义的各类函数(函数是一层嵌套一层,执行完后,具体实例化实现时才是真正调用):
4.main.py函数其他的模块类似_init_build_module都是执行读懂命令行输入的参数进行相关操作,我们这里主要是讲解执行build操作
main中module_type = sys.argv[1] 读取第一个参数,我们的命令第一个是build则开始执行:
Ⅰ.进入_init_build_module函数
1.主干(作用:初始化各build模块)
def _init_build_module(self) -> BuildModuleInterface: args_dict = Arg.parse_all_args(ModuleType.BUILD) #进入2 获取编译命令参数 if args_dict.get("product_name").arg_value != '': #取出product参数判断是需要进行一下设置操作 set_args_dict = Arg.parse_all_args(ModuleType.SET) #同理上为枚举类,获取各种产品参数 set_args_resolver = SetArgsResolver(set_args_dict) #进5 参数解析器类继承父类(抽象类)又可称接口,为对应产品设置各种参数 ohos_set_module = OHOSSetModule(set_args_dict, set_args_resolver, "") #存入编译参数,产品参数 ohos_set_module.set_product() #进行产品实例化设置 preloader = OHOSPreloader() loader = OHOSLoader() generate_ninja = Gn() ninja = Ninja() build_args_resolver = BuildArgsResolver(args_dict) return OHOSBuildModule(args_dict, build_args_resolver, preloader, loader, generate_ninja, ninja) 进入7
后面几个代表编译构建的几个阶段:预加载、加载、生成ninja脚本,执行ninja脚本,以及初始化build阶段各参数的解析器。 最后用这些对象初始化构建模块,返回到OHOSBuildModule函数
这里说一下整个构建过程大致就分为:
2.进入arg.py的parse_all_args函数
调用build/hb/contains/下arg类的方法(python中允许直接用抽象类的方法)
作用:解析读取到所以可支持的命令行编译参数
def parse_all_args(module_type: ModuleType) -> dict: args_dict = {} parser = argparse.ArgumentParser() #定义解释器 all_args = Arg.read_args_file(module_type) #进入3(读取到所以可支持的命令行编译参数) for arg in all_args.values(): arg = dict(arg) #转化字典类型 ArgsFactory.genetic_add_option(parser, arg) oh_arg = Arg.create_instance_by_dict(arg) args_dict[oh_arg.arg_name] = oh_arg #将解析的内容存在对象中 parser.usage = 'hb {} [option]'.format(module_type.name.lower()) parser_args = parser.parse_known_args(sys.argv[2:]) for oh_arg in args_dict.values(): if isinstance(oh_arg, Arg): assigned_value = parser_args[0].__dict__[oh_arg.arg_name] if oh_arg.arg_type == ArgType.LIST: convert_assigned_value = TypeCheckUtil.tile_list(assigned_value) convert_assigned_value = list(set(convert_assigned_value)) elif oh_arg.arg_type == ArgType.SUBPARSERS: convert_assigned_value = TypeCheckUtil.tile_list(assigned_value) if len(convert_assigned_value): convert_assigned_value = list(set(convert_assigned_value)) convert_assigned_value.extend(parser_args[1]) convert_assigned_value.sort(key=sys.argv[2:].index) elif oh_arg.arg_type == ArgType.BOOL: if str(assigned_value).lower() == 'false': convert_assigned_value = False elif str(assigned_value).lower() == 'true' or assigned_value is None: convert_assigned_value = True else: convert_assigned_value = assigned_value if oh_arg.arg_attribute.get('deprecated', None) and oh_arg.arg_value != convert_assigned_value: LogUtil.hb_warning( 'compile option "{}" will be deprecated, \ please consider use other options'.format(oh_arg.arg_name)) oh_arg.arg_value = convert_assigned_value Arg.write_args_file( oh_arg.arg_name, oh_arg.arg_value, module_type) return args_dict
函数的作用是解析特定类型的模块参数,并将解析结果保存在一个字典中。
具体来说,这个函数的实现包括以下几个步骤:
-
创建一个空字典
args_dict
,用于保存解析后的参数。 -
使用
Arg.read_args_file(module_type)
读取特定类型模块的所有参数,并将其保存在all_args
变量中。 -
遍历
all_args
中的所有参数,对每个参数进行以下操作:-
将参数转换为字典形式。
-
使用
ArgsFactory.genetic_add_option(parser, arg)
将参数添加到命令行解析器parser
中。 -
使用
Arg.create_instance_by_dict(arg)
根据参数字典创建一个参数实例oh_arg
。 -
将参数实例
oh_arg
添加到args_dict
字典中,键为参数名(就是解析的BUILD.gn中各种类型的参数:参数值)。
-
-
设置命令行解析器
parser
的用法信息,指定使用方式。 -
使用
parser.parse_known_args(sys.argv[2:])
解析命令行参数,获取解析结果。 -
对
args_dict
中的每个参数实例oh_arg
进行以下操作:-
根据参数类型进行相应的值转换,如列表类型的参数进行去重操作。
-
根据参数的属性判断是否已被弃用,如果是则打印警告信息。
-
更新参数实例的值为转换后的值。
-
将参数的值写入参数文件。
-
3.进入read_args_file
def read_args_file(module_type: ModuleType): args_file_path = '' default_file_path = '' if module_type == ModuleType.BUILD: #我们是这个类型 args_file_path = CURRENT_BUILD_ARGS #参数的路径是这个 在import中我们已经导入这个值 进4 default_file_path = DEFAULT_BUILD_ARGS elif module_type == ModuleType.SET: args_file_path = CURRENT_SET_ARGS default_file_path = DEFAULT_SET_ARGS elif module_type == ModuleType.CLEAN: args_file_path = CURRENT_CLEAN_ARGS default_file_path = DEFAULT_CLEAN_ARGS elif module_type == ModuleType.ENV: args_file_path = CURRENT_ENV_ARGS default_file_path = DEFAULT_ENV_ARGS elif module_type == ModuleType.TOOL: args_file_path = CURRENT_TOOL_ARGS default_file_path = DEFAULT_TOOL_ARGS else: raise OHOSException( 'You are trying to read args file, but there is no corresponding module "{}" args file' .format(module_type.name.lower()), "0018") if not os.path.exists(CURRENT_ARGS_DIR): os.makedirs(CURRENT_ARGS_DIR, exist_ok=True) if not os.path.exists(args_file_path): IoUtil.copy_file(src=default_file_path, dst=args_file_path) return IoUtil.read_json_file(args_file_path)
4进入global_var.py
import os VERSION = "1.0.0" CURRENT_OHOS_ROOT = os.path.dirname(os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) CURRENT_BUILD_DIR = os.path.join(CURRENT_OHOS_ROOT, 'build') CURRENT_HB_DIR = os.path.join(CURRENT_BUILD_DIR, 'hb') DEFAULT_CCACHE_DIR = os.path.join(CURRENT_OHOS_ROOT, '.ccache') ARGS_DIR = os.path.join(CURRENT_HB_DIR, 'resources/args') DEFAULT_BUILD_ARGS = os.path.join( CURRENT_HB_DIR, 'resources/args/default/buildargs.json') DEFAULT_SET_ARGS = os.path.join( CURRENT_HB_DIR, 'resources/args/default/setargs.json') DEFAULT_CLEAN_ARGS = os.path.join( CURRENT_HB_DIR, 'resources/args/default/cleanargs.json') DEFAULT_ENV_ARGS = os.path.join( CURRENT_HB_DIR, 'resources/args/default/envargs.json') DEFAULT_TOOL_ARGS = os.path.join( CURRENT_HB_DIR, 'resources/args/default/toolargs.json') CURRENT_ARGS_DIR = os.path.join(CURRENT_OHOS_ROOT, 'out/hb_args') CURRENT_BUILD_ARGS = os.path.join( #这里,就是这个文件buildargs.json,对应各种可支持的构建命令参数我们的命令有 #--ccache -product-name等 CURRENT_ARGS_DIR, 'buildargs.json') CURRENT_SET_ARGS = os.path.join( CURRENT_ARGS_DIR, 'setargs.json') CURRENT_CLEAN_ARGS = os.path.join( CURRENT_ARGS_DIR, 'cleanargs.json') CURRENT_ENV_ARGS = os.path.join( CURRENT_ARGS_DIR, 'envargs.json') CURRENT_TOOL_ARGS = os.path.join( CURRENT_ARGS_DIR, 'toolargs.json') BUILD_CONFIG_FILE = os.path.join( CURRENT_HB_DIR, 'resources/config/config.json') ROOT_CONFIG_FILE = os.path.join(CURRENT_OHOS_ROOT, 'out/ohos_config.json') STATUS_FILE = os.path.join(CURRENT_HB_DIR, 'resources/status/status.json') ENV_SETUP_FILE = os.path.join( CURRENT_BUILD_DIR, 'build_scripts', 'env_setup.sh')
5.SetArgsResolver类(参数解析器类)
作用:main中module_type = sys.argv[1] 读取第一个参数,我们的命令第一个是build则开始执行:给产品设定相关参数
有则直接从编译参数赋值,没有则进入产品参数设置参数
class SetArgsResolver(ArgsResolverInterface): def __init__(self, args_dict: dict): super().__init__(args_dict) #调用ArgsResolverInterface父类方法 @staticmethod def resolve_product_name(target_arg: Arg, set_module: SetModuleInterface): //传入编译参数,产品设置参数 config = Config() product_info = dict() #创建列表存储相关信息 device_info = dict() if target_arg.arg_value == '': //如果没有编译参数则进入菜单选择 product_info = set_module._menu.select_product() elif target_arg.arg_value.__contains__('@'): product_name, company_name = target_arg.arg_value.split('@', 2) product_info = ProductUtil.get_product_info( product_name, company_name) else: product_info = ProductUtil.get_product_info(target_arg.arg_value) config.product = product_info.get('name') config.product_path = product_info.get('product_path') config.version = product_info.get('version') config.os_level = product_info.get('os_level') config.product_json = product_info.get('config') config.component_type = product_info.get('component_type') if product_info.get('product_config_path'): config.product_config_path = product_info.get( 'product_config_path') else: config.product_config_path = product_info.get('path') device_info = ProductUtil.get_device_info(config.product_json) config.board = device_info.get('board') config.kernel = device_info.get('kernel') config.target_cpu = device_info.get('target_cpu') config.target_os = device_info.get('target_os') config.support_cpu = device_info.get("support_cpu") kernel_version = device_info.get('kernel_version') config.device_company = device_info.get('company') board_path = device_info.get('board_path') if product_info.get('build_out_path'): config.out_path = os.path.join(config.root_path, product_info.get('build_out_path')) else: if config.os_level == 'standard': config.out_path = os.path.join(config.root_path, 'out', config.board) else: config.out_path = os.path.join(config.root_path, 'out', config.board, config.product) if product_info.get('subsystem_config_json'): config.subsystem_config_json = product_info.get( 'subsystem_config_json') else: config.subsystem_config_json = 'build/subsystem_config.json' subsystem_config_overlay_path = os.path.join( config.product_path, 'subsystem_config_overlay.json') if os.path.isfile(subsystem_config_overlay_path): if product_info.get('subsystem_config_overlay_json'): config.subsystem_config_overlay_json = product_info.get( 'subsystem_config_overlay_json') else: config.subsystem_config_overlay_json = subsystem_config_overlay_path if config.version == '2.0': config.device_path = board_path else: if config.os_level == "standard": config.device_path = board_path else: config.device_path = DeviceUtil.get_device_path( board_path, config.kernel, kernel_version) if device_info.get('board_config_path'): config.device_config_path = device_info.get('board_config_path') else: config.device_config_path = config.device_path Arg.write_args_file(target_arg.arg_name, product_info.get('name'), ModuleType.BUILD) Arg.write_args_file(target_arg.arg_name, f"{product_info.get('name')}@{product_info.get('company')}", ModuleType.SET)
用于解析参数的工:
-
初始化方法 (
__init__
): 这个方法接受一个字典args_dict
作为参数,并调用父类的初始化方法。在这里,父类应该是ArgsResolverInterface
,但在给出的代码中没有定义出来。通常,父类的初始化方法可能会做一些必要的准备工作,如设置一些属性。 -
resolve_product_name
方法: 这是一个静态方法-
首先,它创建了一个
Config
对象,然后根据不同的情况获取产品信息和设备信息,并将它们存储在config
对象中的相应属性中。 -
接着,它根据获取的产品信息和设备信息设置了一系列
config
对象的属性,如产品路径、版本、操作系统级别、内核版本等。 -
然后,它根据不同的条件设置了输出路径、子系统配置路径等。
-
最后,它根据设备信息设置了设备路径和设备配置路径,并将参数信息写入相应的文件中。
-
这里就省略细节可自行了解
6.创建实例化对象ohos_set_module类
class OHOSSetModule(SetModuleInterface): _instance = None def __init__(self, args_dict: dict, args_resolver: ArgsResolverInterface, menu: MenuInterface): super().__init__(args_dict, args_resolver) self._menu = menu OHOSSetModule._instance = self @staticmethod def get_instance(): if OHOSSetModule._instance is not None: return OHOSSetModule._instance else: raise OHOSException( 'OHOSSetModule has not been instantiated', '0000') @property def menu(self): return self._menu def set_product(self): self.args_resolver.resolve_arg(self.args_dict['product_name'], self) def set_parameter(self): self.args_resolver.resolve_arg(self.args_dict['all'], self)
Ⅱ.开始构造OHOSBuildModule
第Ⅰ步中我们将所有的参数都返回到 OHOSBuildModule(args_dict编译参数, build_args_resolver各编译参数对应的解释器, preloader, loader, generate_ninja, ninja)中
class OHOSBuildModule(BuildModuleInterface): #继承抽象类父类(接口) _instance = None def __init__(self, #创建传入参数的实例 args_dict: dict, args_resolver: ArgsResolverInterface, preloader: PreloadInterface, loader: LoadInterface, target_generator: BuildFileGeneratorInterface, target_compiler: BuildExecutorInterface): super().__init__(args_dict, args_resolver, preloader, # super() 函数来调用父类的相同方法 loader, target_generator, target_compiler) OHOSBuildModule._instance = self self._start_time = SystemUtil.get_current_time() @property def build_time(self): #计算构建花费时间 return SystemUtil.get_current_time() - self._start_time @staticmethod def get_instance(): #获取实例,单例设计模式,保证该类只有一个实例 if OHOSBuildModule._instance is not None: return OHOSBuildModule._instance else: raise OHOSException( 'OHOSBuildModule has not been instantiated', '0000') @throw_exception def run(self): #构建 try: super().run() except OHOSException as exception: raise exception else: LogUtil.hb_info('{} build success'.format( self.args_dict.get('product_name').arg_value)) LogUtil.hb_info('Cost time: {}'.format(self.build_time)) def _prebuild(self): self._run_phase(BuildPhase.PRE_BUILD) def _preload(self): self._run_phase(BuildPhase.PRE_LOAD) if self.args_dict.get('fast_rebuild', None) and not self.args_dict.get('fast_rebuild').arg_value: self.preloader.run() def _load(self): self._run_phase(BuildPhase.LOAD) if self.args_dict.get('fast_rebuild', None) and not self.args_dict.get('fast_rebuild').arg_value: self.loader.run() def _pre_target_generate(self): self._run_phase(BuildPhase.PRE_TARGET_GENERATE) def _target_generate(self): self._run_phase(BuildPhase.TARGET_GENERATE) if not self.args_dict.get("build_only_load").arg_value and not self.args_dict.get("fast_rebuild").arg_value: self.target_generator.run() def _post_target_generate(self): self._run_phase(BuildPhase.POST_TARGET_GENERATE) def _pre_target_compilation(self): self._run_phase(BuildPhase.PRE_TARGET_COMPILATION) def _target_compilation(self): self._run_phase(BuildPhase.TARGET_COMPILATION) if not self.args_dict.get("build_only_load").arg_value and not self.args_dict.get("build_only_gn").arg_value: self.target_compiler.run() def _post_target_compilation(self): self._run_phase(BuildPhase.POST_TARGET_COMPILATION) def _post_build(self): self._run_phase(BuildPhase.POST_BUILD) def _run_phase(self, phase: BuildPhase): '''Description: Traverse all registered parameters in build process and execute the resolver function of the corresponding phase @parameter: [phase]: Build phase corresponding to parameter @return :none ''' for phase_arg in [arg for arg in self.args_dict.values()if arg.arg_phase == phase]: self.args_resolver.resolve_arg(phase_arg, self)