登录
注册
开源
企业版
高校版
搜索
帮助中心
使用条款
关于我们
开源
企业版
高校版
私有云
模力方舟
登录
注册
9月17日,Gitee Xtreme 极智AI重磅发布,来Gitee直播间一起探索AI时代的软件研发新模式
代码拉取完成,页面将自动刷新
捐赠
捐赠前请先登录
取消
前往登录
扫描微信二维码支付
取消
支付完成
支付提示
将跳转至支付宝完成支付
确定
取消
Watch
不关注
关注所有动态
仅关注版本发行动态
关注但不提醒动态
7
Star
28
Fork
8
Gitee Community
/
OpenHarmony组件开发大赛-有奖征文
代码
Issues
17
Pull Requests
0
Wiki
统计
流水线
服务
质量分析
Jenkins for Gitee
腾讯云托管
腾讯云 Serverless
悬镜安全
阿里云 SAE
Codeblitz
SBOM
我知道了,不再自动展开
更新失败,请稍后重试!
移除标识
内容风险标识
本任务被
标识为内容中包含有代码安全 Bug 、隐私泄露等敏感信息,仓库外成员不可访问
源码解读 - OpenHarmony编译过程简介
意向
#I4DC2Z
需求
droidzxy
创建于
2021-10-11 15:26
# OpenHarmony 编译过程简介 本文介绍针对Hi3516DV300开发板的OpenHarmony的源码编译过程。 标准系统编译命令: ``` ./build.sh --product-name Hi3516DV300 ``` 轻量和小型系统编译命令: ``` hb set . hb build -f ``` 本文主要介绍标准系统的编译过程。 ## 脚本 build.sh 执行流程 ### 1. 检查gn环境配置 遍历检查源码根目录和它的所有上级目录,看是否有一个.gn文件,如果查找不到将会退出。 .gn文件应该存在源码根目录下,用于指定编译工具、配置文件等。 OpenHarmony系统是采用ninja工具构建的,gn是生成ninja构建文件的工具。 ``` script_path=$(cd $(dirname $0);pwd) source_root_dir="${script_path}" while [[ ! -f "${source_root_dir}/.gn" ]]; do source_root_dir="$(dirname "${source_root_dir}")" if [[ "${source_root_dir}" == "/" ]]; then echo "Cannot find source tree containing $(pwd)" exit 1 fi done ``` ### 2. 检测命令行是否包含参数,并解析部分参数 ``` build_params="" while test $# -gt 0 do case "$1" in --product-name) shift product_name="$1" # product_name = Hi3516DV300 ;; --help | -h) help exit 0 ;; *) build_params+=" $1" ;; esac shift done ``` 通过while循环逐个解析命令行参数, product_name : Hi3516DV300, build_params : 指product-name和help之外的参数,例如--build-target,--jobs N等 ### 3. 检查参数 source_root_dir 和 product_name 是否为空 ``` if [[ "${source_root_dir}x" == "x" ]]; then echo "Error: source_root_dir cannot be empty." exit 1 fi if [[ ! -d "${source_root_dir}" ]]; then echo "Error: source_root_dir is incorrect." exit 1 fi if [[ "${product_name}x" == "x" ]]; then echo -e "\033[31mError: the product name should be specified!\033[0m" help exit 1 fi ``` 检查source_root_dir是否为空,是否为目录,默认为build.sh脚本所在目录。 检查product_name是否为空,该参数为必填参数。 ### 4. 检查系统平台,目前支持 mac 和 linux ``` case $(uname -s) in Darwin) HOST_DIR="darwin-x86" HOST_OS="mac" ;; Linux) HOST_DIR="linux-x86" HOST_OS="linux" ;; *) echo "Unsupported host platform: $(uname -s)" RET=1 exit $RET esac ``` 检查系统环境,并设置HOST_DIR和HOST_OS参数。 ### 5. 设置脚本解释器python3的路径 ``` PYTHON3=${source_root_dir}/prebuilts/python/${HOST_DIR}/3.8.5/bin/python3 if [[ ! -f "${PYTHON3}" ]]; then echo -e "\033[33m Please execute the build/prebuilts_download.sh \033[0m" exit 1 fi ``` 自定义PYTHON3所在路径,对于存在多个python版本的系统非常有用。如果PYTHON3找不到,很有可能是用户下载完代码后,没有执行prebuilts_download.sh去下载一些编译工具。 ### 6. 执行 tools_checker.py 脚本,检查文件系统版本和所需工具是否安装完全 ``` ${PYTHON3} ${source_root_dir}/build/scripts/tools_checker.py ``` 这行命令主要检查系统编译所需要的工具是否安装完整。 #### 6.1 定义run_command函数,在新进程中执行cmd命令,返回执行结果 ``` def run_command(cmd, verbose=None): if verbose: print("Running: {}".format(' '.join(cmd))) p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output, _ = p.communicate() if verbose: print(output.decode().rstrip()) return output, p.returncode ``` #### 6.2 定义package_installed函数,通过dpkg查看指定包是否已安装 ``` def package_installed(pkg_name): cmd = ['dpkg', '-s', pkg_name] _, r = run_command(cmd) return r ``` 返回0表明已经运行完毕,负值表明子进程被终止。 #### 6.3 遍历列表中的工具,检查是否全部已经安装 ``` def check_build_repuried_packages(): _build_package_list = [ 'binutils', 'flex', 'bison', 'git', 'build-essential', 'zip', 'curl', 'unzip', 'm4', 'wget', 'perl', 'bc' ] for pkg in _build_package_list: if package_installed(pkg): print("\033[33m {} is not installed. please install it.\033[0m". format(pkg)) sys.exit(1) ``` #### 6.4 检查操作系统的版本,目前只支持Ubuntu系统的18.04和20.04版本系统 ``` def check_os_version(): available_os = ('Ubuntu') available_releases = ('18.04', '20.04') _os_info, _returncode = run_command(['cat', '/etc/issue']) if _returncode != 0: return -1 _os_info = _os_info.decode().rstrip().split() host_os = _os_info[0] host_version = _os_info[1] if host_os not in available_os: print("\033[33m OS {} is not supported for ohos build.\033[0m".format( host_os)) return -1 version_available = False for _version in available_releases: if host_version == _version or host_version.startswith(_version): version_available = True break if not version_available: print("\033[33m OS version {} is not supported for ohos build.\033[0m". format(host_version)) print("\033[33m Avaliable OS version are {}.\033[0m".format( ', '.join(available_releases))) return -1 return 0 ``` #### 6.5 主函数,先检查系统版本,然后检查工具完整性 ``` def main(): check_result = check_os_version() if check_result != 0: return check_build_repuried_packages() return 0 if __name__ == '__main__': sys.exit(main()) ``` ### 7. 执行预加载,解析相关配置参数,为编译做准备 ``` export OHOS_ROOT_PATH="${source_root_dir}" export PYTHON3="${PYTHON3}" cd ${source_root_dir} # preloader ${PYTHON3} ${source_root_dir}/build/loader/preloader/preloader.py \ --product-name ${product_name} \ --source-root-dir ${source_root_dir} \ --products-config-dir "productdefine/common/products" \ --preloader-output-root-dir "out/build_configs" ``` #### 7.1 获取产品的配置文件路径 ``` def _get_product_config(config_dir, product_name): config_file = os.path.join(config_dir, '{}.json'.format(product_name)) if not os.path.exists(config_file): raise Exception( "product configuration '{}' doesn't exist.".format(config_file)) return config_file ``` config_dir为 ${source_root_dir}/productdefine/common/products, product_name为Hi3516DV300, 函数返回值为${source_root_dir}/productdefine/common/products/Hi3516DV300.json #### 7.2 获取基础部分的配置文件 ``` def _get_base_parts(base_config_dir, system_type): system_base_config_file = os.path.join( base_config_dir, '{}_system.json'.format(system_type)) if not os.path.exists(system_base_config_file): raise Exception("product configuration '{}' doesn't exist.".format( system_base_config_file)) data = read_json_file(system_base_config_file) if data is None: raise Exception( "read file '{}' failed.".format(system_base_config_file)) return data ``` base_config_dir为${source_root_dir}/productdefine/common/base, system_type为standard, 函数返回值为${source_root_dir}/productdefine/common/base/standard_system.json文件内容, 该文件目前为空{} #### 7.3 获取继承部分的配置文件 ``` def _get_inherit_parts(inherit_config, current_path): _parts = {} for _config in inherit_config: _file = os.path.join(current_path, _config) _info = read_json_file(_file) if _info is None: raise Exception("read file '{}' failed.".format(_file)) if _info.get('parts'): _parts.update(_info.get('parts')) return _parts ``` 这部分Hi3516DV300没有涉及到。 #### 7.4 获取设备的配置文件 ``` def _get_device_info(device_name, device_config_path): device_config_file = os.path.join(device_config_path, '{}.json'.format(device_name)) device_info = read_json_file(device_config_file) if device_info.get('device_name') != device_name: raise Exception("device name configuration incorrect in '{}'".format(device_config_file)) return device_info ``` device_name 为hi3516dv300 device_config_path为${source_root_dir}/productdefine/common/device 函数返回值为${source_root_dir}/productdefine/common/device/hi3516dv300.json文件内容 #### 7.5 分析配置文件的函数,第二版 ``` def _parse_config_v2(config_info, products_config_path, base_config_dir, device_config_path): system_type = config_info.get("type") base_parts = _get_base_parts(base_config_dir, system_type) all_parts = base_parts inherit_config = config_info.get('inherit') if inherit_config: inherit_parts = _get_inherit_parts(inherit_config, products_config_path) all_parts.update(inherit_parts) current_product_parts = config_info.get("parts") all_parts.update(current_product_parts) product_name = config_info.get('product_name') product_company = config_info.get('product_company') product_device_name = config_info.get('product_device') device_info = _get_device_info(product_device_name, device_config_path) build_configs = {} build_configs['system_type'] = system_type build_configs['product_name'] = product_name build_configs['product_company'] = product_company build_configs['device_name'] = product_device_name build_configs.update(device_info) return all_parts, build_configs ``` 返回两个字典结构, all_parts 为从productdefine/common/products/Hi3516DV300.json中读取的parts部分的内容, build_configs为当前定义的字典,然后根据hi3516dv300.json合并更新了之后的内容,包括system_type,product_name,product_company,device_name,device_company,target_os,target_cpu,kernel_version。 #### 7.6 以前的分析配置文件的函数,第一版 ``` def _parse_config_v1(config_info): build_configs = {"system_type": 'large'} return {}, build_configs ``` 只有在配置文件productdefine/common/device/hi3516dv300.json中没有指定version才会用此函数。 #### 7.7 解析配置文件 ``` def _parse_config(product_name, products_config_path, base_parts_config_path, device_config_path): curr_config_file = _get_product_config(products_config_path, product_name) config_info = read_json_file(curr_config_file) config_version = config_info.get('version') if not config_version: config_version = "1.0" if config_version == "2.0": if product_name != config_info.get('product_name'): raise Exception("product name configuration incorrect in '{}'".format(curr_config_file)) return _parse_config_v2(config_info, products_config_path, base_parts_config_path, device_config_path) else: return _parse_config_v1(config_info) ``` 获取配置文件curr_config_file为productdefine/common/device/hi3516dv300.json, config_info为读取出来的配置文件内容, 根据文件中version字段的值,采用不同的解析函数,本例中执行的是 _parse_config_v2。 #### 7.8 拷贝平台模板文件,修改后写到输出目录 ``` def _copy_platforms_config(platforms_template, parts_info_file, platform_config_output_path): if not os.path.exists(platforms_template): raise Exception( "template file '{}' doesn't exist.".format(platforms_template)) data = read_json_file(platforms_template) if data is None: raise Exception("read file '{}' failed.".format(platforms_template)) _parts_config_file = os.path.relpath(parts_info_file, platform_config_output_path) for _, _config in data.get('platforms').items(): for _info in _config: _info['parts_config'] = _parts_config_file output_file = os.path.join(platform_config_output_path, 'platforms.build') write_json_file(output_file, data) ``` 判断platforms_template文件是否存在,即${source_root_dir}/build/loader/preloader/platforms.template,如果存在则读取文件到data。 根据parts_info_file为${source_root_dir}/out/build_configs/Hi3516DV300/preloader/parts.json和 platform_config_output_path为${source_root_dir}/out/build_configs/standard_system, 计算相对路径_parts_config_file为 ../Hi3516DV300/preloader/parts.json。 把data其中的parts_config字段改为 ../Hi3516DV300/preloader/parts.json, 设置输出文件为${source_root_dir}/out/build_configs/standard_system/platforms.build, 把改后的data写入到输出文件platforms.build。 #### 7.9 获取平台模板配置文件 ``` def _get_platform_template_file(source_root_dir): platforms_template = os.path.join(source_root_dir, 'build/loader/preloader', 'platforms.template') return platforms_template ``` 函数返回值${source_root_dir}/build/loader/preloader/platforms.template #### 7.10 主要执行函数run ``` def _run(args): products_config_path = os.path.join(args.source_root_dir, args.products_config_dir) product_config_root_path = os.path.dirname(products_config_path) if args.base_parts_config_dir: base_parts_config_path = os.path.join(args.source_root_dir, args.base_parts_config_dir) else: base_parts_config_path = os.path.join(product_config_root_path, 'base') if args.device_config_dir: device_config_path = os.path.join(args.source_root_dir, args.device_config_dir) else: device_config_path = os.path.join(product_config_root_path, 'device') all_parts, build_configs = _parse_config(args.product_name, products_config_path, base_parts_config_path, device_config_path) system_type = build_configs.get('system_type') if system_type not in ['standard', 'large']: raise Exception("product config incorrect.") product_info_output_path = os.path.join(args.source_root_dir, args.preloader_output_root_dir, args.product_name, 'preloader') platform_config_output_path = os.path.join(args.source_root_dir, args.preloader_output_root_dir, '{}_system'.format(system_type)) parts_info_file = os.path.join(product_info_output_path, 'parts.json') parts_config_info = {"parts": list(all_parts.keys())} write_json_file(parts_info_file, parts_config_info) platforms_template_file = _get_platform_template_file(args.source_root_dir) _copy_platforms_config(platforms_template_file, parts_info_file, platform_config_output_path) _build_info_list = [] build_info_file = os.path.join(product_info_output_path, 'build.prop') for k, v in build_configs.items(): _build_info_list.append('{}={}'.format(k, v)) write_file(build_info_file, '\n'.join(_build_info_list)) build_info_json_file = os.path.join(product_info_output_path, 'build_config.json') write_json_file(build_info_json_file, build_configs) ``` 设置变量${source_root_dir}/products_config_path为productdefine/common/products, 变量${source_root_dir}/product_config_root_path为productdefine/common/, 判断是否指定了--base-parts-config-dir和--device-config-dir参数,如果指定就采用指定值,否则使用默认值。 base_parts_config_dir 默认为 productdefine/common/base, device_config_dir 默认为 productdefine/common/device, 通过_parse_config解析配置文件,返回all_parts, build_configs变量, 判断system_type是否为standard或者large。 设置输出文件路径, product_info_output_path为 ${source_root_dir}/out/build_configs/Hi3516DV300/preloader, platform_config_output_path为 ${source_root_dir}/out/build_configs/standard_system, 把all_parts中的keys全部输出到out/build_configs/Hi3516DV300/preloader/parts.json, 拷贝平台模板文件,修改后输出到 out/build_configs/standard_system/platforms.build, 把build_configs中的字段,逐行写入到out/build_configs/Hi3516DV300/preloader/build.prop, 把build_configs中的内容直接写入到out/build_configs/Hi3516DV300/preloader/build_config.json。 #### 7.11 主函数,声明参数并调用解析函数 ``` def main(argv): parser = argparse.ArgumentParser() parser.add_argument('--product-name', required=True) parser.add_argument('--source-root-dir', required=True) parser.add_argument('--products-config-dir', required=True) parser.add_argument('--base-parts-config-dir') parser.add_argument('--device-config-dir') parser.add_argument('--preloader-output-root-dir', required=True) args = parser.parse_args(argv) _run(args) return 0 if __name__ == '__main__': sys.exit(main(sys.argv[1:])) ``` ### 8. 加载属性文件中定义的变量 ``` source ${source_root_dir}/out/build_configs/${product_name}/preloader/build.prop ``` 通过导入属性文件,直接设置系统变量 system_type=standard product_name=Hi3516DV300 product_company=hisilicon device_name=hi3516dv300 device_company=hisilicon target_os=ohos target_cpu=arm kernel_version= ### 9. 开始执行系统编译 ``` # call build ${source_root_dir}/build/build_scripts/build_${system_type}.sh \ --product-name ${product_name} \ --device-name ${device_name} \ --target-os ${target_os} \ --target-cpu ${target_cpu} \ ${build_params} ``` system_type为standard,直接执行build/build_scripts/build_standard.sh,同时指定参数 product_name=Hi3516DV300 device_name=hi3516dv300 target_os=ohos target_cpu=arm build_params为除product-name和help之外的参数,例如--build-target,--jobs N等 #### 9.1 设置环境,打印提示信息 ``` set -e echo "build standard system..." echo "---------------------------------------" echo $@ ``` set -e 用来设置,当脚本执行出现错误时,直接退出。 $@ 打印全部参数,每个参数作为一个独立个体。 #### 9.2 引入脚本文件,加载其中的函数和变量 ``` script_path=$(cd $(dirname $0);pwd) # parse params source ${script_path}/parse_params.sh system_type="standard" source ${script_path}/build_common.sh ``` script_path为build_standard.sh文件所在目录。 加载同目录下的parse_params.sh和build_common.sh ##### 9.2.1 加载parse_params.sh文件 ``` # set default value target_os=ohos target_cpu=arm64 use_ccache=false ``` 设置参数默认值 ``` while test $# -gt 0 do case "$1" in --product-name) shift product_name="$1" ;; --device-name) shift device_name="$1" ;; --target-cpu) shift target_cpu="$1" ;; --target-os) shift target_os="$1" ;; --build-target | -t) shift build_target="${build_target} $1" ;; --gn-args) shift if [[ "$1" == "use_musl=true" ]];then export BUILD_WITH_MUSL=true fi gn_args="${gn_args} $1" ;; --ninja-args) shift ninja_args="${ninja_args} $1" ;; --ccache) use_ccache=true ;; --jobs) shift jobs="$1" ;; --export-para) shift PARAM1=$(echo "$1" | sed 's/\(.*\):\(.*\)/\1/') PARAM2=$(echo "$1" | sed 's/.*://') export $PARAM1=$PARAM2 ;; --build-only-gn) build_only_gn=true;; -* | *) echo "Unrecognized option: $1" exit 1 ;; esac shift done ``` 检测当前参数个数$#是否大于0,true表示还有参数,则继续解析。 ``` if [[ "${product_name}x" == "x" ]]; then echo "Error: the product name should be specified!" exit 1 fi ``` product_name为必填内容,如果为空则退出编译。 ``` if [[ "${use_ccache}" == true ]]; then set +e which ccache > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "Error: the ccache is not available, please install ccache." exit 1 fi source ${OHOS_ROOT_PATH}/build/core/build_scripts/set_ccache.sh export CCACHE_EXEC=$(which ccache) set_ccache set -e fi ``` 检查参数是否设置了使用ccache,并检测ccache是否已经安装。 如果使用ccache,则继续设置ccache环境,调用build/core/build_scripts/set_ccache.sh。 ``` function set_ccache() { # user can use environment variable "CCACHE_BASE" to customize ccache directory if [ -z "$CCACHE_BASE" ]; then CCACHE_BASE=${OHOS_ROOT_PATH}/.ccache if [ ! -d "$CCACHE_BASE" ]; then mkdir -p $CCACHE_BASE chmod -R 777 $CCACHE_BASE fi fi echo "CCACHE_DIR="$CCACHE_BASE export USE_CCACHE=1 export CCACHE_DIR=$CCACHE_BASE export CCACHE_UMASK=002 if [ -f "${OHOS_ROOT_PATH}"/ccache.log ]; then mv ${OHOS_ROOT_PATH}/ccache.log ${OHOS_ROOT_PATH}/ccache.log.old fi export CCACHE_LOGFILE=${OHOS_ROOT_PATH}/ccache.log ${CCACHE_EXEC} -M 50G } ``` 在源码根目录下设置.ccache缓存目录和ccache.log文件,并导入相关的系统变量。 export USE_CCACHE=1 export CCACHE_DIR=$CCACHE_BASE export CCACHE_UMASK=002 export CCACHE_LOGFILE=${OHOS_ROOT_PATH}/ccache.log ##### 9.2.2 加载build_common.sh文件 ``` function do_make_ohos() { local build_cmd="build/build_scripts/build_ohos.sh" build_cmd+=" device_type=${product_name} product_name=${product_name} target_os=${target_os} target_cpu=${target_cpu}" build_cmd+=" gn_args=is_standard_system=true" if [[ "${build_target}x" != "x" ]]; then for target_name in ${build_target[@]}; do echo $target_name build_cmd+=" build_target=$target_name" done fi if [[ "${gn_args}x" != "x" ]]; then for _args in ${gn_args[@]}; do build_cmd+=" gn_args=$_args" done fi if [[ "${ninja_args}x" != "x" ]]; then for _args in ${ninja_args[@]}; do build_cmd+=" ninja_args=$_args" done fi if [[ "${PYCACHE_ENABLE}" == true ]]; then build_cmd+=" pycache_enable=true" fi if [[ "${build_only_gn}" == true ]]; then build_cmd+=" build_only_gn=true" fi echo "build_ohos_cmd: $build_cmd" $build_cmd } ``` 定义局部变量build_cmd,设置build_ohos.sh命令并配置相关参数。 根据上面parse_params.sh解析的参数,定义build_ohos.sh的参数。 最后,执行build_cmd,调用build/build_scripts/build_ohos.sh脚本。 ##### 9.2.3 脚本build_ohos.sh执行过程 ``` BIN_PATH=$(cd $(dirname $0);pwd) BASE_HOME=$(dirname $(dirname ${BIN_PATH})) BUILD_SCRIPT_DIR=${BASE_HOME}/build/core/build_scripts main() { source ${BUILD_SCRIPT_DIR}/pre_process.sh pre_process "$@" source ${BUILD_SCRIPT_DIR}/make_main.sh do_make "$@" source ${BUILD_SCRIPT_DIR}/post_process.sh post_process "$@" exit $RET } main "$@" ``` BIN_PATH为 ${source_root_dir}/build/build_scripts, BASE_HOME为 ${source_root_dir}, BUILD_SCRIPT_DIR为 ${source_root_dir}/build/core/build_scripts。 依次执行pre_process -> do_make -> post_process ###### 9.2.3.1 函数pre_process执行过程 ``` case $(uname -s) in Darwin) HOST_DIR="darwin-x86" HOST_OS="mac" ;; Linux) HOST_DIR="linux-x86" HOST_OS="linux" ;; *) echo "Unsupported host platform: $(uname -s)" RET=1 exit $RET esac ``` 检查系统版本,赋值HOST_DIR和HOST_OS变量,目前只支持mac和linux。 ``` export PATH=${BASE_HOME}/prebuilts/python/${HOST_DIR}/3.8.5/bin:${BASE_HOME}/prebuilts/build-tools/${HOST_DIR}/bin:$PATH python --version ``` 导入python和其他工具目录到系统PATH变量,方便后面直接敲命令调用。 ``` source ${BUILD_SCRIPT_DIR}/init_parameters.sh source ${BUILD_SCRIPT_DIR}/parse_cmdline.sh source ${BUILD_SCRIPT_DIR}/common_fun.sh source ${BUILD_SCRIPT_DIR}/trap_ctrlc.sh ``` 加载init_parameters.sh、parse_cmdline.sh、common_fun.sh、trap_ctrlc.sh,引入相关函数。 init_parameters.sh ``` init_parameter() { BUILD_TOOLS_DIR=${BASE_HOME}/prebuilts/build-tools/${HOST_DIR}/bin TEST_BUILD_PARA_STRING="" DEVICE_TYPE="" TARGET_OS=ohos BUILD_VARIANT=release TARGET_ARCH=arm64 OUT_DIR=out NINJA_ARGS="" TARGET_VERSION_MODE="" DOUBLE_FRAMEWORK="" BUILD_ONLY_GN=false SKIP_GN_PARSE=false TARGET_PLATFORM="" EBPF_ENABLE=false MAPLE_JOBS=0 BUILD_XTS=false RELEASE_TEST_SUITE=false BUILD_MAPLE_TARGETS=false BUILD_OHOS_SDK=false BUILD_VERSION="0" INTERFACE_CHECK=true LITE_PARAM="--chip hi3518ev300" BUILD_EXAMPLE=false PYCAHCE_ENABLE=false USE_NARUTO=false OPEN_SOURCE=false REPO_CBUILD=false } ``` 初始化相关参数 parse_cmdline.sh ``` parse_cmdline() { while [ -n "$1" ] do var="$1" OPTIONS=$(echo ${var%%=*}) PARAM=$(echo ${var#*=}) echo "OPTIONS=$OPTIONS" echo "PARAM=$PARAM" echo "-------------------" case "$OPTIONS" in test_build_para) TEST_BUILD_PARA_STRING="$PARAM" ;; device_type) DEVICE_TYPE="$PARAM" ;; product_name) PRODUCT_NAME="$PARAM" ;; build_target) BUILD_TARGET_NAME="$BUILD_TARGET_NAME $PARAM" ;; target_os) TARGET_OS="$PARAM" ;; target_cpu) TARGET_ARCH="$PARAM" ;; variant) BUILD_VARIANT="$PARAM" ;; out_dir) OUT_DIR="$PARAM" ;; gn_args) GN_ARGS="$GN_ARGS $PARAM" ;; ninja_args) NINJA_ARGS="$PARAM" ;; versionmode) TARGET_VERSION_MODE="$PARAM" ;; coverage) COVERAGE="$PARAM" ;; custom_clang) CUSTOM_CLANG="$PARAM" ;; double_framework) DOUBLE_FRAMEWORK="$PARAM" ;; build_only_gn) BUILD_ONLY_GN="$PARAM" ;; skip_gn_parse) SKIP_GN_PARSE="$PARAM" ;; target_platform) TARGET_PLATFORM="$PARAM" ;; ebpf_enable) EBPF_ENABLE="$PARAM" ;; build_xts) BUILD_XTS="$PARAM" ;; release_test_suite) RELEASE_TEST_SUITE="$PARAM" ;; build_ohos_sdk) BUILD_OHOS_SDK="$PARAM" ;; interface_check) INTERFACE_CHECK="$PARAM" ;; lite_param) LITE_PARAM="$PARAM" ;; sdk_version) SDK_VERSION="$PARAM" ;; hosp_version) HOSP_VERSION="$PARAM" ;; api_version) API_VERSION="$PARAM" ;; release_type) RELEASE_TYPE="$PARAM" ;; meta_version) META_VERSION="$PARAM" ;; build_example) BUILD_EXAMPLE="$PARAM" ;; pycache_enable) PYCACHE_ENABLE="$PARAM" ;; use_naruto) USE_NARUTO="$PARAM" ;; open_source) OPEN_SOURCE="$PARAM" ;; esac shift done COMMAND_ARGS="$@" } ``` 逐个解析命令行参数, while [ -n "$1" ] 用来判断参数长度是否为空, var="$1" 参数赋给var变量, ${var%%=*} 把变量var 删掉第一个=及其右边的字符串 ${var#*=} 把变量var 删掉第一个=及其左边的字符串 >扩展:可以用${ }分别替换得到不同的值: ${var#*/}:删掉第一个 / 及其左边的字符串 ${var##*/}:删掉最后一个 / 及其左边的字符串 ${var#*.}:删掉第一个 . 及其左边的字符串 ${var##*.}:删掉最后一个 . 及其左边的字符串 ${var%/*}:删掉最后一个 / 及其右边的字符串 ${var%%/*}:删掉第一个 / 及其右边的字符串 ${var%.*}:删掉最后一个 . 及其右边的字符串 ${var%%.*}:删掉第一个 . 及其右边的字符串 记忆的方法为: > ‘#’ 是去掉左边(键盘上 # 在 $ 的左边) > ‘%’ 是去掉右边(键盘上 % 在 $ 的右边) 所以,OPTIONS=$(echo ${var%%=*})为=左边的内容,PARAM=$(echo ${var#*=})为=右边的内容。 common_fun.sh ``` rename_last_log() { local log=$1 if [ -e "${log}" ]; then if [ "${HOST_OS}x" == "macx" ]; then epoch=$(stat -f %m $log) else epoch=$(stat --format=%Y $log) fi mv $log ${TARGET_OUT_DIR}/build.$epoch.log fi } log_prepare() { mkdir -p $TARGET_OUT_DIR log=$1 rename_last_log $log touch $log } log() { if [ "$#" -lt 1 ]; then return fi echo "$@" | tee -a $LOG_FILE } ``` 日志记录相关的一些操作。 trap_ctrlc.sh ``` function trap_ctrlc() { echo "Caught Ctrl+C, doing post build cleaning" source ${BUILD_SCRIPT_DIR}/post_process.sh post_process } ``` 该函数是当Ctrl+C被按下时会调用。 ``` init_parameter "$@" parse_cmdline "$@" ``` 解析参数,处理命令行参数。 ``` # Trap SIGINT trap "trap_ctrlc" 2 ``` 检测Ctrl+C,看用户是否取消了此次编译。 ###### 9.2.3.2 函数do_make执行过程 ``` TARGET_OUT_DIR=${BASE_HOME}/${OUT_DIR}/${TARGET_OS}-${TARGET_ARCH}-${BUILD_VARIANT} if [[ "${TARGET_OS}" == "ohos" && "${TARGET_ARCH}" == "arm64" ]];then TARGET_OUT_DIR=${BASE_HOME}/${OUT_DIR}/${BUILD_VARIANT} fi if [[ ! -d "${TARGET_OUT_DIR}" ]];then mkdir -p ${TARGET_OUT_DIR} fi ``` 设置输出目录,如果不存在则创建。 TARGET_OS为ohos,TARGET_ARCH为arm,BUILD_VARIANT为release,所以输出目录为out/ohos-arm-release,注意,TARGET_ARCH默认为arm64,但通过解析 target_cpu参数arm,得到了 TARGET_ARCH 为arm。 ``` # prepare to save build log LOG_FILE=${TARGET_OUT_DIR}/build.log log_prepare $LOG_FILE log "$@" ``` 准备日志系统,日志保存在out/ohos-arm-release目录下,以前的旧日志会自动备份。 ``` BEGIN_TIME=$(date "+%s") source ${BUILD_SCRIPT_DIR}/get_gn_parameters.sh get_gn_parameters ``` 记录开始时间,设置gn的参数。 ``` if [ "${SKIP_GN_PARSE}"x = falsex ]; then ${BUILD_TOOLS_DIR}/gn gen ${TARGET_OUT_DIR} \ --args="target_os=\"${TARGET_OS}\" target_cpu=\"${TARGET_ARCH}\" is_debug=false \ product_name=\"${PRODUCT_NAME}\" \ device_type=\"${DEVICE_TYPE}\" is_component_build=true use_custom_libcxx=true \ ${GN_ARGS} ${TEST_BUILD_PARA_STRING} ${IS_ASAN} \ release_test_suite=${RELEASE_TEST_SUITE}" 2>&1 | tee -a $log if [ "${PIPESTATUS[0]}" != 0 ]; then log "build: gn gen error" RET=1 return fi if [[ "${BUILD_ONLY_GN}" = true ]];then RET=0 return fi fi ``` SKIP_GN_PARSE默认为false,后期参数解析时也没有更改。 执行prebuilts/build-tools/linux-x86/bin下的gn程序,后面跟着前面解析出来的参数。 ``` if [ "${BUILD_TARGET_NAME}" == "all" ]; then BUILD_TARGET_NAME="make_all make_test" elif [ "${BUILD_TARGET_NAME}" == "" ]; then BUILD_TARGET_NAME=packages fi ``` BUILD_TARGET_NAME=packages ``` log "Starting Ninja..." NINJA_START_TIME=$(date +%s%N) echo python version: $(python --version) ninja_build_args="--source-root-dir ${BASE_HOME} --root-build-dir ${TARGET_OUT_DIR} \ --build-target-name ${BUILD_TARGET_NAME}" if [ "${TARGET_PLATFORM}" != "" ];then ninja_build_args="$ninja_build_args --target-platform ${TARGET_PLATFORM}" fi real_build_target=$(python ${BASE_HOME}/build/scripts/build_target_handler.py $ninja_build_args) echo "build_target: "$real_build_target if [ "${USE_NARUTO}"x = "truex" ];then ${BUILD_TOOLS_DIR}/naruto -d keepdepfile -p ${BASE_HOME}/.naruto_cache -C ${TARGET_OUT_DIR} ${real_build_target} ${NINJA_ARGS} 2>&1 | tee -a $log else ${BUILD_TOOLS_DIR}/ninja -d keepdepfile -C ${TARGET_OUT_DIR} ${real_build_target} ${NINJA_ARGS} 2>&1 | tee -a $log fi ``` 开始执行ninja编译,TARGET_PLATFORM=phone ###### 9.2.3.3 函数post_process执行过程 ``` ninja_trace() { if [ "${NINJA_START_TIME}x" == "x" ]; then return fi #generate build.trace to TARGET_OUT_DIR dir. if [ -f "${TARGET_OUT_DIR}"/.ninja_log ]; then if [ -f "${BASE_HOME}"/build/scripts/ninja2trace.py ]; then python3 ${BASE_HOME}/build/scripts/ninja2trace.py --ninja-log ${TARGET_OUT_DIR}/.ninja_log \ --trace-file ${TARGET_OUT_DIR}/build.trace --ninja-start-time $NINJA_START_TIME \ --duration-file ${TARGET_OUT_DIR}/sorted_action_duration.txt fi fi } ``` 生成build.trace文件。 ``` calc_build_time() { END_TIME=$(date "+%s") log "used: $(expr $END_TIME - $BEGIN_TIME) seconds" } ``` 计算编译耗时。 ``` get_build_warning_list() { if [ -f "${BASE_HOME}"/build/scripts/get_warnings.py ];then python3 ${BASE_HOME}/build/scripts/get_warnings.py --build-log-file ${TARGET_OUT_DIR}/build.log --warning-out-file ${TARGET_OUT_DIR}/packages/WarningList.txt fi } ``` 获取编译时的警告信息。 ``` generate_opensource_package() { log "generate opensource package" if [ -f "${BASE_HOME}"/build/scripts/code_release.py ];then python3 "${BASE_HOME}"/build/scripts/code_release.py if [ ! -d "${TARGET_OUT_DIR}"/packages/code_opensource ];then mkdir -p "${TARGET_OUT_DIR}"/packages/code_opensource fi cp "${BASE_HOME}"/out/Code_Opensource.tar.gz "${TARGET_OUT_DIR}"/packages/code_opensource/Code_Opensource.tar.gz -f fi } ``` 生成开源软件包。 ``` ccache_stat() { if [[ ! -z "${CCACHE_EXEC}" ]] && [[ ! -z "${USE_CCACHE}" ]] && [[ "${USE_CCACHE}" == 1 ]]; then log "ccache statistics" if [ -e "${LOG_FILE}" -a -e "${CCACHE_LOGFILE}" ]; then python3 ${BASE_HOME}/build/scripts/summary_ccache_hitrate.py $CCACHE_LOGFILE | tee -a $LOG_FILE fi fi } ``` 统计ccache缓存的使用情况。 ``` pycache_stat() { log "pycache statistics" python3 ${BASE_HOME}/build/scripts/util/pyd.py --stat } ``` 统计pycache缓存的使用情况。 ``` pycache_manage() { log "manage pycache contents" python3 ${BASE_HOME}/build/scripts/util/pyd.py --manage } ``` 管理pycache内容。 ``` pycache_stop() { log "pycache daemon exit" python3 ${BASE_HOME}/build/scripts/util/pyd.py --stop } ``` 停止pycache后台进程。 ``` post_process() { if [ "${OPEN_SOURCE}" == true ];then generate_opensource_package fi calc_build_time pycache_stat pycache_manage pycache_stop ninja_trace ccache_stat python3 ${BASE_HOME}/build/ohos/statistics/build_overlap_statistics.py --build-out-dir ${TARGET_OUT_DIR} --subsystem-config-file ${BASE_HOME}/build/subsystem_config.json --root-source-dir ${BASE_HOME} get_build_warning_list echo "post_process" } ``` 执行编译后任务,打印处理耗时,管理缓存,记录警告信息等。 #### 9.3 主函数,执行编译任务 ``` function main() { # build ohos do_make_ohos if [[ "${build_only_gn}" == true ]]; then return fi build_target=$(echo ${build_target} | xargs) echo "build_target='${build_target}'" if [[ "${build_target}" == "build_ohos_sdk" ]]; then echo -e "\033[32m build ohos-sdk successful.\033[0m" return fi ohos_build_root_dir="${OHOS_ROOT_PATH}/out/release" if [[ "${target_cpu}" == "arm" ]]; then ohos_build_root_dir="${OHOS_ROOT_PATH}/out/ohos-arm-release" fi # build images build/adapter/images/build_image.sh --device-name ${device_name} \ --ohos-build-out-dir ${ohos_build_root_dir}/packages/phone } main ``` 函数do_make_ohos编译文件系统。 ``` if [[ "${build_only_gn}" == true ]]; then return fi ``` 如果仅仅通过gn编译系统,执行完do_make_ohos后就可以退出了。 ``` build_target=$(echo ${build_target} | xargs) echo "build_target='${build_target}'" if [[ "${build_target}" == "build_ohos_sdk" ]]; then echo -e "\033[32m build ohos-sdk successful.\033[0m" return fi ``` 如果是编译ohos-sdk,到这一步也可以退出了,就不用再执行build_image.sh编译系统镜像了。 ``` ohos_build_root_dir="${OHOS_ROOT_PATH}/out/release" if [[ "${target_cpu}" == "arm" ]]; then ohos_build_root_dir="${OHOS_ROOT_PATH}/out/ohos-arm-release" fi # build images build/adapter/images/build_image.sh --device-name ${device_name} \ --ohos-build-out-dir ${ohos_build_root_dir}/packages/phone ``` 设置编译镜像存放的根目录为out/ohos-arm-release/packages/phone, 脚本build_image.sh 开始执行镜像编译工作。 ##### 9.3.1 脚本build_image.sh执行过程,将编译好的文件打包成镜像 ``` while test $# -gt 0; do case "$1" in --device-name) shift device_name="$1" ;; --ohos-build-out-dir) shift ohos_build_out_dir="$1" ;; -* | *) echo "Unrecognized option: $1" exit 1 ;; esac shift done if [[ "${device_name}x" == "x" ]]; then echo "Error: the device_name cannot be empty." exit 1 fi ``` 解析device_name和ohos_build_out_dir参数,device_name为hi3516dv300 ,ohos_build_out_dir为out/ohos-arm-release/packages/phone。 ``` function build_vendro_image() { cp -arf prebuilts/aosp_prebuilt_libs/minisys/vendor ${ohos_build_out_dir}/images/ if [[ -d "${ohos_build_out_dir}/vendor" ]]; then cp -arf ${ohos_build_out_dir}/vendor/* ${ohos_build_out_dir}/images/vendor/ fi # remove img rm -rf ${ohos_build_out_dir}/images/vendor.img # build system image PATH=prebuilts/aosp_prebuilt_libs/host_tools/bin:$PATH prebuilts/aosp_prebuilt_libs/host_tools/releasetools/build_image.py \ ${ohos_build_out_dir}/images/vendor \ prebuilts/aosp_prebuilt_libs/minisys/vendor_image_info.txt \ ${ohos_build_out_dir}/images/vendor.img \ ${ohos_build_out_dir}/images/system if [[ "${PIPESTATUS[0]}" -ne 0 ]]; then echo "\033[31m build: build vendor image error.\033[0m" exit 1 fi echo -e "\033[32m build vendor image successful.\033[0m" } ``` 拷贝相关的预编译的文件到输出目录,删除旧镜像,调用build_image.py生成vendor.img。 ``` function _update_build_prop() { local system_build_prop_file=${ohos_build_out_dir}/images/system/build.prop local ohos_build_prop_file=${OHOS_ROOT_PATH}/build/ohos_system.prop if [[ -f "${ohos_build_prop_file}" ]] && [[ -f "${system_build_prop_file}" ]]; then echo '' >> ${system_build_prop_file} cat ${ohos_build_prop_file} >> ${system_build_prop_file} fi } ``` 把build/ohos_system.prop文件内容附加到out/ohos-arm-release/packages/phone/images/system/build.prop。 ``` function build_system_images_for_musl() { cp ${ohos_build_out_dir}/../../common/common/sh ${ohos_build_out_dir}/images/system/bin/sh cp ${ohos_build_out_dir}/../../common/common/toybox ${ohos_build_out_dir}/images/system/bin/toybox toybox_creater=${OHOS_ROOT_PATH}/build/adapter/images/create_init_toybox.sh ${toybox_creater} ${ohos_build_out_dir}/images/system/bin } ``` musl是一个全新的 Linux 基本系统实现的标准库,特点是轻量级、快速、简单、免费、标准兼容和安全。函数build_system_images_for_musl采用musl机制生成一个系统工具集。 ``` function copy_init() { cp ${ohos_build_out_dir}/system/etc/init.Hi3516DV300.cfg ${ohos_build_out_dir}/images/root/init.Hi3516DV300.cfg cp ${ohos_build_out_dir}/system/etc/init.cfg ${ohos_build_out_dir}/images/root/init.cfg cp ${ohos_build_out_dir}/system/etc/init.usb.cfg ${ohos_build_out_dir}/images/root/init.usb.cfg cp ${ohos_build_out_dir}/system/etc/init.usb.configfs.cfg ${ohos_build_out_dir}/images/root/init.usb.configfs.cfg cp ${ohos_build_out_dir}/system/etc/init.Hi3516DV300.usb.cfg ${ohos_build_out_dir}/images/root/init.Hi3516DV300.usb.cfg # It will be deleted after the musl compilation is completely successful if [[ $USE_OHOS_INIT != true ]]; then cp ${OHOS_ROOT_PATH}/prebuilts/aosp_prebuilt_libs/minisys/system/bin/init ${ohos_build_out_dir}/images/system/bin/init cp ${OHOS_ROOT_PATH}/prebuilts/aosp_prebuilt_libs/minisys/system/bin/reboot ${ohos_build_out_dir}/images/system/bin/reboot cp ${OHOS_ROOT_PATH}/prebuilts/aosp_prebuilt_libs/minisys/system/bin/sh ${ohos_build_out_dir}/images/system/bin/sh cp ${OHOS_ROOT_PATH}/prebuilts/aosp_prebuilt_libs/minisys/system/bin/toybox ${ohos_build_out_dir}/images/system/bin/toybox fi } ``` 拷贝一些初始化的配置文件,和基础工具与命令。 ``` function build_system_image() { if [[ ! -d "${ohos_build_out_dir}/images" ]]; then mkdir ${ohos_build_out_dir}/images fi cp -arf prebuilts/aosp_prebuilt_libs/minisys/system ${ohos_build_out_dir}/images/ cp -arf ${ohos_build_out_dir}/system/* ${ohos_build_out_dir}/images/system/ # update build.prop _update_build_prop # build for init copy_init if [[ $BUILD_WITH_MUSL == true ]]; then build_system_images_for_musl fi # remove img rm -rf ${ohos_build_out_dir}/images/system.img # build system image PATH=prebuilts/aosp_prebuilt_libs/host_tools/bin:$PATH prebuilts/aosp_prebuilt_libs/host_tools/releasetools/build_image.py \ ${ohos_build_out_dir}/images/system \ prebuilts/aosp_prebuilt_libs/minisys/system_image_info.txt \ ${ohos_build_out_dir}/images/system.img \ ${ohos_build_out_dir}/images/system if [[ "${PIPESTATUS[0]}" -ne 0 ]]; then echo "\033[31m build: build system image error.\033[0m" exit 1 fi echo -e "\033[32m build system image successful.\033[0m" } ``` 编译system.img镜像,首先检查输出目录是否存在,拷贝预编译文件到输出目录,更新属性文件,拷贝init脚本和命令,删除旧的system.img镜像,最后调用build_image.py生成system.img。 ``` function build_userdata_image() { if [[ -d "${ohos_build_out_dir}/images/data" ]]; then rm -rf ${ohos_build_out_dir}/images/data fi mkdir ${ohos_build_out_dir}/images/data mkdir ${ohos_build_out_dir}/images/data/media cp -r applications/standard/photos/demos ${ohos_build_out_dir}/images/data/media/ # build userdat image PATH=prebuilts/aosp_prebuilt_libs/host_tools/bin:$PATH prebuilts/aosp_prebuilt_libs/host_tools/releasetools/build_image.py \ ${ohos_build_out_dir}/images/data \ prebuilts/aosp_prebuilt_libs/minisys/userdata_image_info.txt \ ${ohos_build_out_dir}/images/userdata.img \ ${ohos_build_out_dir}/images/system if [[ "${PIPESTATUS[0]}" -ne 0 ]]; then echo "\033[31m build: build userdata image error.\033[0m" exit 1 fi echo -e "\033[32m build userdata image successful.\033[0m" } ``` 编译userdata.img镜像,具体过程和system.img的生成过程类似。 ``` function prepare_root() { if [[ -d "${ohos_build_out_dir}/images/root" ]]; then rm -rf ${ohos_build_out_dir}/images/root fi cp -arf prebuilts/aosp_prebuilt_libs/minisys/root ${ohos_build_out_dir}/images/ local dir_list=(acct apex cache config data debug_ramdisk dev mnt oem proc sbin storage sys system vendor) pushd ${ohos_build_out_dir}/images/root for _path in ${dir_list[@]} do if [[ ! -d "${_path}" ]]; then mkdir ${_path} fi done popd } ``` 准备root文件系统,同样是检查输出目录,拷贝预编译文件,然后创建指定目录集合。 ``` prepare_root build_vendro_image build_system_image build_userdata_image if [[ "${device_name}" == "hi3516dv300" ]]; then source ${OHOS_ROOT_PATH}/build/adapter/images/updater/build_updater_image.sh fi ``` 逐次调用函数,生成对应镜像,如果设备是hi3516dv300,还要生成系统升级镜像updater.img,用于系统进入升级模式。 ### 10. 检查系统编译结果,打印彩色提示文字 ``` if [[ "${PIPESTATUS[0]}" -ne 0 ]]; then echo -e "\033[31m=====build ${product_name} error.\033[0m" exit 1 fi echo -e "\033[32m=====build ${product_name} successful.\033[0m" date +%F' '%H:%M:%S echo "++++++++++++++++++++++++++++++++++++++++" ```
# OpenHarmony 编译过程简介 本文介绍针对Hi3516DV300开发板的OpenHarmony的源码编译过程。 标准系统编译命令: ``` ./build.sh --product-name Hi3516DV300 ``` 轻量和小型系统编译命令: ``` hb set . hb build -f ``` 本文主要介绍标准系统的编译过程。 ## 脚本 build.sh 执行流程 ### 1. 检查gn环境配置 遍历检查源码根目录和它的所有上级目录,看是否有一个.gn文件,如果查找不到将会退出。 .gn文件应该存在源码根目录下,用于指定编译工具、配置文件等。 OpenHarmony系统是采用ninja工具构建的,gn是生成ninja构建文件的工具。 ``` script_path=$(cd $(dirname $0);pwd) source_root_dir="${script_path}" while [[ ! -f "${source_root_dir}/.gn" ]]; do source_root_dir="$(dirname "${source_root_dir}")" if [[ "${source_root_dir}" == "/" ]]; then echo "Cannot find source tree containing $(pwd)" exit 1 fi done ``` ### 2. 检测命令行是否包含参数,并解析部分参数 ``` build_params="" while test $# -gt 0 do case "$1" in --product-name) shift product_name="$1" # product_name = Hi3516DV300 ;; --help | -h) help exit 0 ;; *) build_params+=" $1" ;; esac shift done ``` 通过while循环逐个解析命令行参数, product_name : Hi3516DV300, build_params : 指product-name和help之外的参数,例如--build-target,--jobs N等 ### 3. 检查参数 source_root_dir 和 product_name 是否为空 ``` if [[ "${source_root_dir}x" == "x" ]]; then echo "Error: source_root_dir cannot be empty." exit 1 fi if [[ ! -d "${source_root_dir}" ]]; then echo "Error: source_root_dir is incorrect." exit 1 fi if [[ "${product_name}x" == "x" ]]; then echo -e "\033[31mError: the product name should be specified!\033[0m" help exit 1 fi ``` 检查source_root_dir是否为空,是否为目录,默认为build.sh脚本所在目录。 检查product_name是否为空,该参数为必填参数。 ### 4. 检查系统平台,目前支持 mac 和 linux ``` case $(uname -s) in Darwin) HOST_DIR="darwin-x86" HOST_OS="mac" ;; Linux) HOST_DIR="linux-x86" HOST_OS="linux" ;; *) echo "Unsupported host platform: $(uname -s)" RET=1 exit $RET esac ``` 检查系统环境,并设置HOST_DIR和HOST_OS参数。 ### 5. 设置脚本解释器python3的路径 ``` PYTHON3=${source_root_dir}/prebuilts/python/${HOST_DIR}/3.8.5/bin/python3 if [[ ! -f "${PYTHON3}" ]]; then echo -e "\033[33m Please execute the build/prebuilts_download.sh \033[0m" exit 1 fi ``` 自定义PYTHON3所在路径,对于存在多个python版本的系统非常有用。如果PYTHON3找不到,很有可能是用户下载完代码后,没有执行prebuilts_download.sh去下载一些编译工具。 ### 6. 执行 tools_checker.py 脚本,检查文件系统版本和所需工具是否安装完全 ``` ${PYTHON3} ${source_root_dir}/build/scripts/tools_checker.py ``` 这行命令主要检查系统编译所需要的工具是否安装完整。 #### 6.1 定义run_command函数,在新进程中执行cmd命令,返回执行结果 ``` def run_command(cmd, verbose=None): if verbose: print("Running: {}".format(' '.join(cmd))) p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output, _ = p.communicate() if verbose: print(output.decode().rstrip()) return output, p.returncode ``` #### 6.2 定义package_installed函数,通过dpkg查看指定包是否已安装 ``` def package_installed(pkg_name): cmd = ['dpkg', '-s', pkg_name] _, r = run_command(cmd) return r ``` 返回0表明已经运行完毕,负值表明子进程被终止。 #### 6.3 遍历列表中的工具,检查是否全部已经安装 ``` def check_build_repuried_packages(): _build_package_list = [ 'binutils', 'flex', 'bison', 'git', 'build-essential', 'zip', 'curl', 'unzip', 'm4', 'wget', 'perl', 'bc' ] for pkg in _build_package_list: if package_installed(pkg): print("\033[33m {} is not installed. please install it.\033[0m". format(pkg)) sys.exit(1) ``` #### 6.4 检查操作系统的版本,目前只支持Ubuntu系统的18.04和20.04版本系统 ``` def check_os_version(): available_os = ('Ubuntu') available_releases = ('18.04', '20.04') _os_info, _returncode = run_command(['cat', '/etc/issue']) if _returncode != 0: return -1 _os_info = _os_info.decode().rstrip().split() host_os = _os_info[0] host_version = _os_info[1] if host_os not in available_os: print("\033[33m OS {} is not supported for ohos build.\033[0m".format( host_os)) return -1 version_available = False for _version in available_releases: if host_version == _version or host_version.startswith(_version): version_available = True break if not version_available: print("\033[33m OS version {} is not supported for ohos build.\033[0m". format(host_version)) print("\033[33m Avaliable OS version are {}.\033[0m".format( ', '.join(available_releases))) return -1 return 0 ``` #### 6.5 主函数,先检查系统版本,然后检查工具完整性 ``` def main(): check_result = check_os_version() if check_result != 0: return check_build_repuried_packages() return 0 if __name__ == '__main__': sys.exit(main()) ``` ### 7. 执行预加载,解析相关配置参数,为编译做准备 ``` export OHOS_ROOT_PATH="${source_root_dir}" export PYTHON3="${PYTHON3}" cd ${source_root_dir} # preloader ${PYTHON3} ${source_root_dir}/build/loader/preloader/preloader.py \ --product-name ${product_name} \ --source-root-dir ${source_root_dir} \ --products-config-dir "productdefine/common/products" \ --preloader-output-root-dir "out/build_configs" ``` #### 7.1 获取产品的配置文件路径 ``` def _get_product_config(config_dir, product_name): config_file = os.path.join(config_dir, '{}.json'.format(product_name)) if not os.path.exists(config_file): raise Exception( "product configuration '{}' doesn't exist.".format(config_file)) return config_file ``` config_dir为 ${source_root_dir}/productdefine/common/products, product_name为Hi3516DV300, 函数返回值为${source_root_dir}/productdefine/common/products/Hi3516DV300.json #### 7.2 获取基础部分的配置文件 ``` def _get_base_parts(base_config_dir, system_type): system_base_config_file = os.path.join( base_config_dir, '{}_system.json'.format(system_type)) if not os.path.exists(system_base_config_file): raise Exception("product configuration '{}' doesn't exist.".format( system_base_config_file)) data = read_json_file(system_base_config_file) if data is None: raise Exception( "read file '{}' failed.".format(system_base_config_file)) return data ``` base_config_dir为${source_root_dir}/productdefine/common/base, system_type为standard, 函数返回值为${source_root_dir}/productdefine/common/base/standard_system.json文件内容, 该文件目前为空{} #### 7.3 获取继承部分的配置文件 ``` def _get_inherit_parts(inherit_config, current_path): _parts = {} for _config in inherit_config: _file = os.path.join(current_path, _config) _info = read_json_file(_file) if _info is None: raise Exception("read file '{}' failed.".format(_file)) if _info.get('parts'): _parts.update(_info.get('parts')) return _parts ``` 这部分Hi3516DV300没有涉及到。 #### 7.4 获取设备的配置文件 ``` def _get_device_info(device_name, device_config_path): device_config_file = os.path.join(device_config_path, '{}.json'.format(device_name)) device_info = read_json_file(device_config_file) if device_info.get('device_name') != device_name: raise Exception("device name configuration incorrect in '{}'".format(device_config_file)) return device_info ``` device_name 为hi3516dv300 device_config_path为${source_root_dir}/productdefine/common/device 函数返回值为${source_root_dir}/productdefine/common/device/hi3516dv300.json文件内容 #### 7.5 分析配置文件的函数,第二版 ``` def _parse_config_v2(config_info, products_config_path, base_config_dir, device_config_path): system_type = config_info.get("type") base_parts = _get_base_parts(base_config_dir, system_type) all_parts = base_parts inherit_config = config_info.get('inherit') if inherit_config: inherit_parts = _get_inherit_parts(inherit_config, products_config_path) all_parts.update(inherit_parts) current_product_parts = config_info.get("parts") all_parts.update(current_product_parts) product_name = config_info.get('product_name') product_company = config_info.get('product_company') product_device_name = config_info.get('product_device') device_info = _get_device_info(product_device_name, device_config_path) build_configs = {} build_configs['system_type'] = system_type build_configs['product_name'] = product_name build_configs['product_company'] = product_company build_configs['device_name'] = product_device_name build_configs.update(device_info) return all_parts, build_configs ``` 返回两个字典结构, all_parts 为从productdefine/common/products/Hi3516DV300.json中读取的parts部分的内容, build_configs为当前定义的字典,然后根据hi3516dv300.json合并更新了之后的内容,包括system_type,product_name,product_company,device_name,device_company,target_os,target_cpu,kernel_version。 #### 7.6 以前的分析配置文件的函数,第一版 ``` def _parse_config_v1(config_info): build_configs = {"system_type": 'large'} return {}, build_configs ``` 只有在配置文件productdefine/common/device/hi3516dv300.json中没有指定version才会用此函数。 #### 7.7 解析配置文件 ``` def _parse_config(product_name, products_config_path, base_parts_config_path, device_config_path): curr_config_file = _get_product_config(products_config_path, product_name) config_info = read_json_file(curr_config_file) config_version = config_info.get('version') if not config_version: config_version = "1.0" if config_version == "2.0": if product_name != config_info.get('product_name'): raise Exception("product name configuration incorrect in '{}'".format(curr_config_file)) return _parse_config_v2(config_info, products_config_path, base_parts_config_path, device_config_path) else: return _parse_config_v1(config_info) ``` 获取配置文件curr_config_file为productdefine/common/device/hi3516dv300.json, config_info为读取出来的配置文件内容, 根据文件中version字段的值,采用不同的解析函数,本例中执行的是 _parse_config_v2。 #### 7.8 拷贝平台模板文件,修改后写到输出目录 ``` def _copy_platforms_config(platforms_template, parts_info_file, platform_config_output_path): if not os.path.exists(platforms_template): raise Exception( "template file '{}' doesn't exist.".format(platforms_template)) data = read_json_file(platforms_template) if data is None: raise Exception("read file '{}' failed.".format(platforms_template)) _parts_config_file = os.path.relpath(parts_info_file, platform_config_output_path) for _, _config in data.get('platforms').items(): for _info in _config: _info['parts_config'] = _parts_config_file output_file = os.path.join(platform_config_output_path, 'platforms.build') write_json_file(output_file, data) ``` 判断platforms_template文件是否存在,即${source_root_dir}/build/loader/preloader/platforms.template,如果存在则读取文件到data。 根据parts_info_file为${source_root_dir}/out/build_configs/Hi3516DV300/preloader/parts.json和 platform_config_output_path为${source_root_dir}/out/build_configs/standard_system, 计算相对路径_parts_config_file为 ../Hi3516DV300/preloader/parts.json。 把data其中的parts_config字段改为 ../Hi3516DV300/preloader/parts.json, 设置输出文件为${source_root_dir}/out/build_configs/standard_system/platforms.build, 把改后的data写入到输出文件platforms.build。 #### 7.9 获取平台模板配置文件 ``` def _get_platform_template_file(source_root_dir): platforms_template = os.path.join(source_root_dir, 'build/loader/preloader', 'platforms.template') return platforms_template ``` 函数返回值${source_root_dir}/build/loader/preloader/platforms.template #### 7.10 主要执行函数run ``` def _run(args): products_config_path = os.path.join(args.source_root_dir, args.products_config_dir) product_config_root_path = os.path.dirname(products_config_path) if args.base_parts_config_dir: base_parts_config_path = os.path.join(args.source_root_dir, args.base_parts_config_dir) else: base_parts_config_path = os.path.join(product_config_root_path, 'base') if args.device_config_dir: device_config_path = os.path.join(args.source_root_dir, args.device_config_dir) else: device_config_path = os.path.join(product_config_root_path, 'device') all_parts, build_configs = _parse_config(args.product_name, products_config_path, base_parts_config_path, device_config_path) system_type = build_configs.get('system_type') if system_type not in ['standard', 'large']: raise Exception("product config incorrect.") product_info_output_path = os.path.join(args.source_root_dir, args.preloader_output_root_dir, args.product_name, 'preloader') platform_config_output_path = os.path.join(args.source_root_dir, args.preloader_output_root_dir, '{}_system'.format(system_type)) parts_info_file = os.path.join(product_info_output_path, 'parts.json') parts_config_info = {"parts": list(all_parts.keys())} write_json_file(parts_info_file, parts_config_info) platforms_template_file = _get_platform_template_file(args.source_root_dir) _copy_platforms_config(platforms_template_file, parts_info_file, platform_config_output_path) _build_info_list = [] build_info_file = os.path.join(product_info_output_path, 'build.prop') for k, v in build_configs.items(): _build_info_list.append('{}={}'.format(k, v)) write_file(build_info_file, '\n'.join(_build_info_list)) build_info_json_file = os.path.join(product_info_output_path, 'build_config.json') write_json_file(build_info_json_file, build_configs) ``` 设置变量${source_root_dir}/products_config_path为productdefine/common/products, 变量${source_root_dir}/product_config_root_path为productdefine/common/, 判断是否指定了--base-parts-config-dir和--device-config-dir参数,如果指定就采用指定值,否则使用默认值。 base_parts_config_dir 默认为 productdefine/common/base, device_config_dir 默认为 productdefine/common/device, 通过_parse_config解析配置文件,返回all_parts, build_configs变量, 判断system_type是否为standard或者large。 设置输出文件路径, product_info_output_path为 ${source_root_dir}/out/build_configs/Hi3516DV300/preloader, platform_config_output_path为 ${source_root_dir}/out/build_configs/standard_system, 把all_parts中的keys全部输出到out/build_configs/Hi3516DV300/preloader/parts.json, 拷贝平台模板文件,修改后输出到 out/build_configs/standard_system/platforms.build, 把build_configs中的字段,逐行写入到out/build_configs/Hi3516DV300/preloader/build.prop, 把build_configs中的内容直接写入到out/build_configs/Hi3516DV300/preloader/build_config.json。 #### 7.11 主函数,声明参数并调用解析函数 ``` def main(argv): parser = argparse.ArgumentParser() parser.add_argument('--product-name', required=True) parser.add_argument('--source-root-dir', required=True) parser.add_argument('--products-config-dir', required=True) parser.add_argument('--base-parts-config-dir') parser.add_argument('--device-config-dir') parser.add_argument('--preloader-output-root-dir', required=True) args = parser.parse_args(argv) _run(args) return 0 if __name__ == '__main__': sys.exit(main(sys.argv[1:])) ``` ### 8. 加载属性文件中定义的变量 ``` source ${source_root_dir}/out/build_configs/${product_name}/preloader/build.prop ``` 通过导入属性文件,直接设置系统变量 system_type=standard product_name=Hi3516DV300 product_company=hisilicon device_name=hi3516dv300 device_company=hisilicon target_os=ohos target_cpu=arm kernel_version= ### 9. 开始执行系统编译 ``` # call build ${source_root_dir}/build/build_scripts/build_${system_type}.sh \ --product-name ${product_name} \ --device-name ${device_name} \ --target-os ${target_os} \ --target-cpu ${target_cpu} \ ${build_params} ``` system_type为standard,直接执行build/build_scripts/build_standard.sh,同时指定参数 product_name=Hi3516DV300 device_name=hi3516dv300 target_os=ohos target_cpu=arm build_params为除product-name和help之外的参数,例如--build-target,--jobs N等 #### 9.1 设置环境,打印提示信息 ``` set -e echo "build standard system..." echo "---------------------------------------" echo $@ ``` set -e 用来设置,当脚本执行出现错误时,直接退出。 $@ 打印全部参数,每个参数作为一个独立个体。 #### 9.2 引入脚本文件,加载其中的函数和变量 ``` script_path=$(cd $(dirname $0);pwd) # parse params source ${script_path}/parse_params.sh system_type="standard" source ${script_path}/build_common.sh ``` script_path为build_standard.sh文件所在目录。 加载同目录下的parse_params.sh和build_common.sh ##### 9.2.1 加载parse_params.sh文件 ``` # set default value target_os=ohos target_cpu=arm64 use_ccache=false ``` 设置参数默认值 ``` while test $# -gt 0 do case "$1" in --product-name) shift product_name="$1" ;; --device-name) shift device_name="$1" ;; --target-cpu) shift target_cpu="$1" ;; --target-os) shift target_os="$1" ;; --build-target | -t) shift build_target="${build_target} $1" ;; --gn-args) shift if [[ "$1" == "use_musl=true" ]];then export BUILD_WITH_MUSL=true fi gn_args="${gn_args} $1" ;; --ninja-args) shift ninja_args="${ninja_args} $1" ;; --ccache) use_ccache=true ;; --jobs) shift jobs="$1" ;; --export-para) shift PARAM1=$(echo "$1" | sed 's/\(.*\):\(.*\)/\1/') PARAM2=$(echo "$1" | sed 's/.*://') export $PARAM1=$PARAM2 ;; --build-only-gn) build_only_gn=true;; -* | *) echo "Unrecognized option: $1" exit 1 ;; esac shift done ``` 检测当前参数个数$#是否大于0,true表示还有参数,则继续解析。 ``` if [[ "${product_name}x" == "x" ]]; then echo "Error: the product name should be specified!" exit 1 fi ``` product_name为必填内容,如果为空则退出编译。 ``` if [[ "${use_ccache}" == true ]]; then set +e which ccache > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "Error: the ccache is not available, please install ccache." exit 1 fi source ${OHOS_ROOT_PATH}/build/core/build_scripts/set_ccache.sh export CCACHE_EXEC=$(which ccache) set_ccache set -e fi ``` 检查参数是否设置了使用ccache,并检测ccache是否已经安装。 如果使用ccache,则继续设置ccache环境,调用build/core/build_scripts/set_ccache.sh。 ``` function set_ccache() { # user can use environment variable "CCACHE_BASE" to customize ccache directory if [ -z "$CCACHE_BASE" ]; then CCACHE_BASE=${OHOS_ROOT_PATH}/.ccache if [ ! -d "$CCACHE_BASE" ]; then mkdir -p $CCACHE_BASE chmod -R 777 $CCACHE_BASE fi fi echo "CCACHE_DIR="$CCACHE_BASE export USE_CCACHE=1 export CCACHE_DIR=$CCACHE_BASE export CCACHE_UMASK=002 if [ -f "${OHOS_ROOT_PATH}"/ccache.log ]; then mv ${OHOS_ROOT_PATH}/ccache.log ${OHOS_ROOT_PATH}/ccache.log.old fi export CCACHE_LOGFILE=${OHOS_ROOT_PATH}/ccache.log ${CCACHE_EXEC} -M 50G } ``` 在源码根目录下设置.ccache缓存目录和ccache.log文件,并导入相关的系统变量。 export USE_CCACHE=1 export CCACHE_DIR=$CCACHE_BASE export CCACHE_UMASK=002 export CCACHE_LOGFILE=${OHOS_ROOT_PATH}/ccache.log ##### 9.2.2 加载build_common.sh文件 ``` function do_make_ohos() { local build_cmd="build/build_scripts/build_ohos.sh" build_cmd+=" device_type=${product_name} product_name=${product_name} target_os=${target_os} target_cpu=${target_cpu}" build_cmd+=" gn_args=is_standard_system=true" if [[ "${build_target}x" != "x" ]]; then for target_name in ${build_target[@]}; do echo $target_name build_cmd+=" build_target=$target_name" done fi if [[ "${gn_args}x" != "x" ]]; then for _args in ${gn_args[@]}; do build_cmd+=" gn_args=$_args" done fi if [[ "${ninja_args}x" != "x" ]]; then for _args in ${ninja_args[@]}; do build_cmd+=" ninja_args=$_args" done fi if [[ "${PYCACHE_ENABLE}" == true ]]; then build_cmd+=" pycache_enable=true" fi if [[ "${build_only_gn}" == true ]]; then build_cmd+=" build_only_gn=true" fi echo "build_ohos_cmd: $build_cmd" $build_cmd } ``` 定义局部变量build_cmd,设置build_ohos.sh命令并配置相关参数。 根据上面parse_params.sh解析的参数,定义build_ohos.sh的参数。 最后,执行build_cmd,调用build/build_scripts/build_ohos.sh脚本。 ##### 9.2.3 脚本build_ohos.sh执行过程 ``` BIN_PATH=$(cd $(dirname $0);pwd) BASE_HOME=$(dirname $(dirname ${BIN_PATH})) BUILD_SCRIPT_DIR=${BASE_HOME}/build/core/build_scripts main() { source ${BUILD_SCRIPT_DIR}/pre_process.sh pre_process "$@" source ${BUILD_SCRIPT_DIR}/make_main.sh do_make "$@" source ${BUILD_SCRIPT_DIR}/post_process.sh post_process "$@" exit $RET } main "$@" ``` BIN_PATH为 ${source_root_dir}/build/build_scripts, BASE_HOME为 ${source_root_dir}, BUILD_SCRIPT_DIR为 ${source_root_dir}/build/core/build_scripts。 依次执行pre_process -> do_make -> post_process ###### 9.2.3.1 函数pre_process执行过程 ``` case $(uname -s) in Darwin) HOST_DIR="darwin-x86" HOST_OS="mac" ;; Linux) HOST_DIR="linux-x86" HOST_OS="linux" ;; *) echo "Unsupported host platform: $(uname -s)" RET=1 exit $RET esac ``` 检查系统版本,赋值HOST_DIR和HOST_OS变量,目前只支持mac和linux。 ``` export PATH=${BASE_HOME}/prebuilts/python/${HOST_DIR}/3.8.5/bin:${BASE_HOME}/prebuilts/build-tools/${HOST_DIR}/bin:$PATH python --version ``` 导入python和其他工具目录到系统PATH变量,方便后面直接敲命令调用。 ``` source ${BUILD_SCRIPT_DIR}/init_parameters.sh source ${BUILD_SCRIPT_DIR}/parse_cmdline.sh source ${BUILD_SCRIPT_DIR}/common_fun.sh source ${BUILD_SCRIPT_DIR}/trap_ctrlc.sh ``` 加载init_parameters.sh、parse_cmdline.sh、common_fun.sh、trap_ctrlc.sh,引入相关函数。 init_parameters.sh ``` init_parameter() { BUILD_TOOLS_DIR=${BASE_HOME}/prebuilts/build-tools/${HOST_DIR}/bin TEST_BUILD_PARA_STRING="" DEVICE_TYPE="" TARGET_OS=ohos BUILD_VARIANT=release TARGET_ARCH=arm64 OUT_DIR=out NINJA_ARGS="" TARGET_VERSION_MODE="" DOUBLE_FRAMEWORK="" BUILD_ONLY_GN=false SKIP_GN_PARSE=false TARGET_PLATFORM="" EBPF_ENABLE=false MAPLE_JOBS=0 BUILD_XTS=false RELEASE_TEST_SUITE=false BUILD_MAPLE_TARGETS=false BUILD_OHOS_SDK=false BUILD_VERSION="0" INTERFACE_CHECK=true LITE_PARAM="--chip hi3518ev300" BUILD_EXAMPLE=false PYCAHCE_ENABLE=false USE_NARUTO=false OPEN_SOURCE=false REPO_CBUILD=false } ``` 初始化相关参数 parse_cmdline.sh ``` parse_cmdline() { while [ -n "$1" ] do var="$1" OPTIONS=$(echo ${var%%=*}) PARAM=$(echo ${var#*=}) echo "OPTIONS=$OPTIONS" echo "PARAM=$PARAM" echo "-------------------" case "$OPTIONS" in test_build_para) TEST_BUILD_PARA_STRING="$PARAM" ;; device_type) DEVICE_TYPE="$PARAM" ;; product_name) PRODUCT_NAME="$PARAM" ;; build_target) BUILD_TARGET_NAME="$BUILD_TARGET_NAME $PARAM" ;; target_os) TARGET_OS="$PARAM" ;; target_cpu) TARGET_ARCH="$PARAM" ;; variant) BUILD_VARIANT="$PARAM" ;; out_dir) OUT_DIR="$PARAM" ;; gn_args) GN_ARGS="$GN_ARGS $PARAM" ;; ninja_args) NINJA_ARGS="$PARAM" ;; versionmode) TARGET_VERSION_MODE="$PARAM" ;; coverage) COVERAGE="$PARAM" ;; custom_clang) CUSTOM_CLANG="$PARAM" ;; double_framework) DOUBLE_FRAMEWORK="$PARAM" ;; build_only_gn) BUILD_ONLY_GN="$PARAM" ;; skip_gn_parse) SKIP_GN_PARSE="$PARAM" ;; target_platform) TARGET_PLATFORM="$PARAM" ;; ebpf_enable) EBPF_ENABLE="$PARAM" ;; build_xts) BUILD_XTS="$PARAM" ;; release_test_suite) RELEASE_TEST_SUITE="$PARAM" ;; build_ohos_sdk) BUILD_OHOS_SDK="$PARAM" ;; interface_check) INTERFACE_CHECK="$PARAM" ;; lite_param) LITE_PARAM="$PARAM" ;; sdk_version) SDK_VERSION="$PARAM" ;; hosp_version) HOSP_VERSION="$PARAM" ;; api_version) API_VERSION="$PARAM" ;; release_type) RELEASE_TYPE="$PARAM" ;; meta_version) META_VERSION="$PARAM" ;; build_example) BUILD_EXAMPLE="$PARAM" ;; pycache_enable) PYCACHE_ENABLE="$PARAM" ;; use_naruto) USE_NARUTO="$PARAM" ;; open_source) OPEN_SOURCE="$PARAM" ;; esac shift done COMMAND_ARGS="$@" } ``` 逐个解析命令行参数, while [ -n "$1" ] 用来判断参数长度是否为空, var="$1" 参数赋给var变量, ${var%%=*} 把变量var 删掉第一个=及其右边的字符串 ${var#*=} 把变量var 删掉第一个=及其左边的字符串 >扩展:可以用${ }分别替换得到不同的值: ${var#*/}:删掉第一个 / 及其左边的字符串 ${var##*/}:删掉最后一个 / 及其左边的字符串 ${var#*.}:删掉第一个 . 及其左边的字符串 ${var##*.}:删掉最后一个 . 及其左边的字符串 ${var%/*}:删掉最后一个 / 及其右边的字符串 ${var%%/*}:删掉第一个 / 及其右边的字符串 ${var%.*}:删掉最后一个 . 及其右边的字符串 ${var%%.*}:删掉第一个 . 及其右边的字符串 记忆的方法为: > ‘#’ 是去掉左边(键盘上 # 在 $ 的左边) > ‘%’ 是去掉右边(键盘上 % 在 $ 的右边) 所以,OPTIONS=$(echo ${var%%=*})为=左边的内容,PARAM=$(echo ${var#*=})为=右边的内容。 common_fun.sh ``` rename_last_log() { local log=$1 if [ -e "${log}" ]; then if [ "${HOST_OS}x" == "macx" ]; then epoch=$(stat -f %m $log) else epoch=$(stat --format=%Y $log) fi mv $log ${TARGET_OUT_DIR}/build.$epoch.log fi } log_prepare() { mkdir -p $TARGET_OUT_DIR log=$1 rename_last_log $log touch $log } log() { if [ "$#" -lt 1 ]; then return fi echo "$@" | tee -a $LOG_FILE } ``` 日志记录相关的一些操作。 trap_ctrlc.sh ``` function trap_ctrlc() { echo "Caught Ctrl+C, doing post build cleaning" source ${BUILD_SCRIPT_DIR}/post_process.sh post_process } ``` 该函数是当Ctrl+C被按下时会调用。 ``` init_parameter "$@" parse_cmdline "$@" ``` 解析参数,处理命令行参数。 ``` # Trap SIGINT trap "trap_ctrlc" 2 ``` 检测Ctrl+C,看用户是否取消了此次编译。 ###### 9.2.3.2 函数do_make执行过程 ``` TARGET_OUT_DIR=${BASE_HOME}/${OUT_DIR}/${TARGET_OS}-${TARGET_ARCH}-${BUILD_VARIANT} if [[ "${TARGET_OS}" == "ohos" && "${TARGET_ARCH}" == "arm64" ]];then TARGET_OUT_DIR=${BASE_HOME}/${OUT_DIR}/${BUILD_VARIANT} fi if [[ ! -d "${TARGET_OUT_DIR}" ]];then mkdir -p ${TARGET_OUT_DIR} fi ``` 设置输出目录,如果不存在则创建。 TARGET_OS为ohos,TARGET_ARCH为arm,BUILD_VARIANT为release,所以输出目录为out/ohos-arm-release,注意,TARGET_ARCH默认为arm64,但通过解析 target_cpu参数arm,得到了 TARGET_ARCH 为arm。 ``` # prepare to save build log LOG_FILE=${TARGET_OUT_DIR}/build.log log_prepare $LOG_FILE log "$@" ``` 准备日志系统,日志保存在out/ohos-arm-release目录下,以前的旧日志会自动备份。 ``` BEGIN_TIME=$(date "+%s") source ${BUILD_SCRIPT_DIR}/get_gn_parameters.sh get_gn_parameters ``` 记录开始时间,设置gn的参数。 ``` if [ "${SKIP_GN_PARSE}"x = falsex ]; then ${BUILD_TOOLS_DIR}/gn gen ${TARGET_OUT_DIR} \ --args="target_os=\"${TARGET_OS}\" target_cpu=\"${TARGET_ARCH}\" is_debug=false \ product_name=\"${PRODUCT_NAME}\" \ device_type=\"${DEVICE_TYPE}\" is_component_build=true use_custom_libcxx=true \ ${GN_ARGS} ${TEST_BUILD_PARA_STRING} ${IS_ASAN} \ release_test_suite=${RELEASE_TEST_SUITE}" 2>&1 | tee -a $log if [ "${PIPESTATUS[0]}" != 0 ]; then log "build: gn gen error" RET=1 return fi if [[ "${BUILD_ONLY_GN}" = true ]];then RET=0 return fi fi ``` SKIP_GN_PARSE默认为false,后期参数解析时也没有更改。 执行prebuilts/build-tools/linux-x86/bin下的gn程序,后面跟着前面解析出来的参数。 ``` if [ "${BUILD_TARGET_NAME}" == "all" ]; then BUILD_TARGET_NAME="make_all make_test" elif [ "${BUILD_TARGET_NAME}" == "" ]; then BUILD_TARGET_NAME=packages fi ``` BUILD_TARGET_NAME=packages ``` log "Starting Ninja..." NINJA_START_TIME=$(date +%s%N) echo python version: $(python --version) ninja_build_args="--source-root-dir ${BASE_HOME} --root-build-dir ${TARGET_OUT_DIR} \ --build-target-name ${BUILD_TARGET_NAME}" if [ "${TARGET_PLATFORM}" != "" ];then ninja_build_args="$ninja_build_args --target-platform ${TARGET_PLATFORM}" fi real_build_target=$(python ${BASE_HOME}/build/scripts/build_target_handler.py $ninja_build_args) echo "build_target: "$real_build_target if [ "${USE_NARUTO}"x = "truex" ];then ${BUILD_TOOLS_DIR}/naruto -d keepdepfile -p ${BASE_HOME}/.naruto_cache -C ${TARGET_OUT_DIR} ${real_build_target} ${NINJA_ARGS} 2>&1 | tee -a $log else ${BUILD_TOOLS_DIR}/ninja -d keepdepfile -C ${TARGET_OUT_DIR} ${real_build_target} ${NINJA_ARGS} 2>&1 | tee -a $log fi ``` 开始执行ninja编译,TARGET_PLATFORM=phone ###### 9.2.3.3 函数post_process执行过程 ``` ninja_trace() { if [ "${NINJA_START_TIME}x" == "x" ]; then return fi #generate build.trace to TARGET_OUT_DIR dir. if [ -f "${TARGET_OUT_DIR}"/.ninja_log ]; then if [ -f "${BASE_HOME}"/build/scripts/ninja2trace.py ]; then python3 ${BASE_HOME}/build/scripts/ninja2trace.py --ninja-log ${TARGET_OUT_DIR}/.ninja_log \ --trace-file ${TARGET_OUT_DIR}/build.trace --ninja-start-time $NINJA_START_TIME \ --duration-file ${TARGET_OUT_DIR}/sorted_action_duration.txt fi fi } ``` 生成build.trace文件。 ``` calc_build_time() { END_TIME=$(date "+%s") log "used: $(expr $END_TIME - $BEGIN_TIME) seconds" } ``` 计算编译耗时。 ``` get_build_warning_list() { if [ -f "${BASE_HOME}"/build/scripts/get_warnings.py ];then python3 ${BASE_HOME}/build/scripts/get_warnings.py --build-log-file ${TARGET_OUT_DIR}/build.log --warning-out-file ${TARGET_OUT_DIR}/packages/WarningList.txt fi } ``` 获取编译时的警告信息。 ``` generate_opensource_package() { log "generate opensource package" if [ -f "${BASE_HOME}"/build/scripts/code_release.py ];then python3 "${BASE_HOME}"/build/scripts/code_release.py if [ ! -d "${TARGET_OUT_DIR}"/packages/code_opensource ];then mkdir -p "${TARGET_OUT_DIR}"/packages/code_opensource fi cp "${BASE_HOME}"/out/Code_Opensource.tar.gz "${TARGET_OUT_DIR}"/packages/code_opensource/Code_Opensource.tar.gz -f fi } ``` 生成开源软件包。 ``` ccache_stat() { if [[ ! -z "${CCACHE_EXEC}" ]] && [[ ! -z "${USE_CCACHE}" ]] && [[ "${USE_CCACHE}" == 1 ]]; then log "ccache statistics" if [ -e "${LOG_FILE}" -a -e "${CCACHE_LOGFILE}" ]; then python3 ${BASE_HOME}/build/scripts/summary_ccache_hitrate.py $CCACHE_LOGFILE | tee -a $LOG_FILE fi fi } ``` 统计ccache缓存的使用情况。 ``` pycache_stat() { log "pycache statistics" python3 ${BASE_HOME}/build/scripts/util/pyd.py --stat } ``` 统计pycache缓存的使用情况。 ``` pycache_manage() { log "manage pycache contents" python3 ${BASE_HOME}/build/scripts/util/pyd.py --manage } ``` 管理pycache内容。 ``` pycache_stop() { log "pycache daemon exit" python3 ${BASE_HOME}/build/scripts/util/pyd.py --stop } ``` 停止pycache后台进程。 ``` post_process() { if [ "${OPEN_SOURCE}" == true ];then generate_opensource_package fi calc_build_time pycache_stat pycache_manage pycache_stop ninja_trace ccache_stat python3 ${BASE_HOME}/build/ohos/statistics/build_overlap_statistics.py --build-out-dir ${TARGET_OUT_DIR} --subsystem-config-file ${BASE_HOME}/build/subsystem_config.json --root-source-dir ${BASE_HOME} get_build_warning_list echo "post_process" } ``` 执行编译后任务,打印处理耗时,管理缓存,记录警告信息等。 #### 9.3 主函数,执行编译任务 ``` function main() { # build ohos do_make_ohos if [[ "${build_only_gn}" == true ]]; then return fi build_target=$(echo ${build_target} | xargs) echo "build_target='${build_target}'" if [[ "${build_target}" == "build_ohos_sdk" ]]; then echo -e "\033[32m build ohos-sdk successful.\033[0m" return fi ohos_build_root_dir="${OHOS_ROOT_PATH}/out/release" if [[ "${target_cpu}" == "arm" ]]; then ohos_build_root_dir="${OHOS_ROOT_PATH}/out/ohos-arm-release" fi # build images build/adapter/images/build_image.sh --device-name ${device_name} \ --ohos-build-out-dir ${ohos_build_root_dir}/packages/phone } main ``` 函数do_make_ohos编译文件系统。 ``` if [[ "${build_only_gn}" == true ]]; then return fi ``` 如果仅仅通过gn编译系统,执行完do_make_ohos后就可以退出了。 ``` build_target=$(echo ${build_target} | xargs) echo "build_target='${build_target}'" if [[ "${build_target}" == "build_ohos_sdk" ]]; then echo -e "\033[32m build ohos-sdk successful.\033[0m" return fi ``` 如果是编译ohos-sdk,到这一步也可以退出了,就不用再执行build_image.sh编译系统镜像了。 ``` ohos_build_root_dir="${OHOS_ROOT_PATH}/out/release" if [[ "${target_cpu}" == "arm" ]]; then ohos_build_root_dir="${OHOS_ROOT_PATH}/out/ohos-arm-release" fi # build images build/adapter/images/build_image.sh --device-name ${device_name} \ --ohos-build-out-dir ${ohos_build_root_dir}/packages/phone ``` 设置编译镜像存放的根目录为out/ohos-arm-release/packages/phone, 脚本build_image.sh 开始执行镜像编译工作。 ##### 9.3.1 脚本build_image.sh执行过程,将编译好的文件打包成镜像 ``` while test $# -gt 0; do case "$1" in --device-name) shift device_name="$1" ;; --ohos-build-out-dir) shift ohos_build_out_dir="$1" ;; -* | *) echo "Unrecognized option: $1" exit 1 ;; esac shift done if [[ "${device_name}x" == "x" ]]; then echo "Error: the device_name cannot be empty." exit 1 fi ``` 解析device_name和ohos_build_out_dir参数,device_name为hi3516dv300 ,ohos_build_out_dir为out/ohos-arm-release/packages/phone。 ``` function build_vendro_image() { cp -arf prebuilts/aosp_prebuilt_libs/minisys/vendor ${ohos_build_out_dir}/images/ if [[ -d "${ohos_build_out_dir}/vendor" ]]; then cp -arf ${ohos_build_out_dir}/vendor/* ${ohos_build_out_dir}/images/vendor/ fi # remove img rm -rf ${ohos_build_out_dir}/images/vendor.img # build system image PATH=prebuilts/aosp_prebuilt_libs/host_tools/bin:$PATH prebuilts/aosp_prebuilt_libs/host_tools/releasetools/build_image.py \ ${ohos_build_out_dir}/images/vendor \ prebuilts/aosp_prebuilt_libs/minisys/vendor_image_info.txt \ ${ohos_build_out_dir}/images/vendor.img \ ${ohos_build_out_dir}/images/system if [[ "${PIPESTATUS[0]}" -ne 0 ]]; then echo "\033[31m build: build vendor image error.\033[0m" exit 1 fi echo -e "\033[32m build vendor image successful.\033[0m" } ``` 拷贝相关的预编译的文件到输出目录,删除旧镜像,调用build_image.py生成vendor.img。 ``` function _update_build_prop() { local system_build_prop_file=${ohos_build_out_dir}/images/system/build.prop local ohos_build_prop_file=${OHOS_ROOT_PATH}/build/ohos_system.prop if [[ -f "${ohos_build_prop_file}" ]] && [[ -f "${system_build_prop_file}" ]]; then echo '' >> ${system_build_prop_file} cat ${ohos_build_prop_file} >> ${system_build_prop_file} fi } ``` 把build/ohos_system.prop文件内容附加到out/ohos-arm-release/packages/phone/images/system/build.prop。 ``` function build_system_images_for_musl() { cp ${ohos_build_out_dir}/../../common/common/sh ${ohos_build_out_dir}/images/system/bin/sh cp ${ohos_build_out_dir}/../../common/common/toybox ${ohos_build_out_dir}/images/system/bin/toybox toybox_creater=${OHOS_ROOT_PATH}/build/adapter/images/create_init_toybox.sh ${toybox_creater} ${ohos_build_out_dir}/images/system/bin } ``` musl是一个全新的 Linux 基本系统实现的标准库,特点是轻量级、快速、简单、免费、标准兼容和安全。函数build_system_images_for_musl采用musl机制生成一个系统工具集。 ``` function copy_init() { cp ${ohos_build_out_dir}/system/etc/init.Hi3516DV300.cfg ${ohos_build_out_dir}/images/root/init.Hi3516DV300.cfg cp ${ohos_build_out_dir}/system/etc/init.cfg ${ohos_build_out_dir}/images/root/init.cfg cp ${ohos_build_out_dir}/system/etc/init.usb.cfg ${ohos_build_out_dir}/images/root/init.usb.cfg cp ${ohos_build_out_dir}/system/etc/init.usb.configfs.cfg ${ohos_build_out_dir}/images/root/init.usb.configfs.cfg cp ${ohos_build_out_dir}/system/etc/init.Hi3516DV300.usb.cfg ${ohos_build_out_dir}/images/root/init.Hi3516DV300.usb.cfg # It will be deleted after the musl compilation is completely successful if [[ $USE_OHOS_INIT != true ]]; then cp ${OHOS_ROOT_PATH}/prebuilts/aosp_prebuilt_libs/minisys/system/bin/init ${ohos_build_out_dir}/images/system/bin/init cp ${OHOS_ROOT_PATH}/prebuilts/aosp_prebuilt_libs/minisys/system/bin/reboot ${ohos_build_out_dir}/images/system/bin/reboot cp ${OHOS_ROOT_PATH}/prebuilts/aosp_prebuilt_libs/minisys/system/bin/sh ${ohos_build_out_dir}/images/system/bin/sh cp ${OHOS_ROOT_PATH}/prebuilts/aosp_prebuilt_libs/minisys/system/bin/toybox ${ohos_build_out_dir}/images/system/bin/toybox fi } ``` 拷贝一些初始化的配置文件,和基础工具与命令。 ``` function build_system_image() { if [[ ! -d "${ohos_build_out_dir}/images" ]]; then mkdir ${ohos_build_out_dir}/images fi cp -arf prebuilts/aosp_prebuilt_libs/minisys/system ${ohos_build_out_dir}/images/ cp -arf ${ohos_build_out_dir}/system/* ${ohos_build_out_dir}/images/system/ # update build.prop _update_build_prop # build for init copy_init if [[ $BUILD_WITH_MUSL == true ]]; then build_system_images_for_musl fi # remove img rm -rf ${ohos_build_out_dir}/images/system.img # build system image PATH=prebuilts/aosp_prebuilt_libs/host_tools/bin:$PATH prebuilts/aosp_prebuilt_libs/host_tools/releasetools/build_image.py \ ${ohos_build_out_dir}/images/system \ prebuilts/aosp_prebuilt_libs/minisys/system_image_info.txt \ ${ohos_build_out_dir}/images/system.img \ ${ohos_build_out_dir}/images/system if [[ "${PIPESTATUS[0]}" -ne 0 ]]; then echo "\033[31m build: build system image error.\033[0m" exit 1 fi echo -e "\033[32m build system image successful.\033[0m" } ``` 编译system.img镜像,首先检查输出目录是否存在,拷贝预编译文件到输出目录,更新属性文件,拷贝init脚本和命令,删除旧的system.img镜像,最后调用build_image.py生成system.img。 ``` function build_userdata_image() { if [[ -d "${ohos_build_out_dir}/images/data" ]]; then rm -rf ${ohos_build_out_dir}/images/data fi mkdir ${ohos_build_out_dir}/images/data mkdir ${ohos_build_out_dir}/images/data/media cp -r applications/standard/photos/demos ${ohos_build_out_dir}/images/data/media/ # build userdat image PATH=prebuilts/aosp_prebuilt_libs/host_tools/bin:$PATH prebuilts/aosp_prebuilt_libs/host_tools/releasetools/build_image.py \ ${ohos_build_out_dir}/images/data \ prebuilts/aosp_prebuilt_libs/minisys/userdata_image_info.txt \ ${ohos_build_out_dir}/images/userdata.img \ ${ohos_build_out_dir}/images/system if [[ "${PIPESTATUS[0]}" -ne 0 ]]; then echo "\033[31m build: build userdata image error.\033[0m" exit 1 fi echo -e "\033[32m build userdata image successful.\033[0m" } ``` 编译userdata.img镜像,具体过程和system.img的生成过程类似。 ``` function prepare_root() { if [[ -d "${ohos_build_out_dir}/images/root" ]]; then rm -rf ${ohos_build_out_dir}/images/root fi cp -arf prebuilts/aosp_prebuilt_libs/minisys/root ${ohos_build_out_dir}/images/ local dir_list=(acct apex cache config data debug_ramdisk dev mnt oem proc sbin storage sys system vendor) pushd ${ohos_build_out_dir}/images/root for _path in ${dir_list[@]} do if [[ ! -d "${_path}" ]]; then mkdir ${_path} fi done popd } ``` 准备root文件系统,同样是检查输出目录,拷贝预编译文件,然后创建指定目录集合。 ``` prepare_root build_vendro_image build_system_image build_userdata_image if [[ "${device_name}" == "hi3516dv300" ]]; then source ${OHOS_ROOT_PATH}/build/adapter/images/updater/build_updater_image.sh fi ``` 逐次调用函数,生成对应镜像,如果设备是hi3516dv300,还要生成系统升级镜像updater.img,用于系统进入升级模式。 ### 10. 检查系统编译结果,打印彩色提示文字 ``` if [[ "${PIPESTATUS[0]}" -ne 0 ]]; then echo -e "\033[31m=====build ${product_name} error.\033[0m" exit 1 fi echo -e "\033[32m=====build ${product_name} successful.\033[0m" date +%F' '%H:%M:%S echo "++++++++++++++++++++++++++++++++++++++++" ```
评论 (
0
)
登录
后才可以发表评论
状态
意向
意向
已确认
方案设计
UI设计
开发中
待测试
测试中
待演示
待上线
已上线
已验收
已拒绝
挂起
负责人
未设置
标签
未设置
项目
未立项任务
未立项任务
里程碑
未关联里程碑
未关联里程碑
Pull Requests
未关联
未关联
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
未关联
未关联
master
develop
开始日期   -   截止日期
-
置顶选项
不置顶
置顶等级:高
置顶等级:中
置顶等级:低
优先级
不指定
严重
主要
次要
不重要
预计工期
(小时)
参与者(1)
其他
1
https://gitee.com/gitee-community/OHZW210809.git
[email protected]
:gitee-community/OHZW210809.git
gitee-community
OHZW210809
OpenHarmony组件开发大赛-有奖征文
点此查找更多帮助
搜索帮助
Git 命令在线学习
如何在 Gitee 导入 GitHub 仓库
Git 仓库基础操作
企业版和社区版功能对比
SSH 公钥设置
如何处理代码冲突
仓库体积过大,如何减小?
如何找回被删除的仓库数据
Gitee 产品配额说明
GitHub仓库快速导入Gitee及同步更新
什么是 Release(发行版)
将 PHP 项目自动发布到 packagist.org
评论
仓库举报
回到顶部
登录提示
该操作需登录 Gitee 帐号,请先登录后再操作。
立即登录
没有帐号,去注册