Navigation

    全志在线开发者论坛

    • Register
    • Login
    • Search
    • Categories
    • Tags
    • 在线文档
    • 社区主页

    請教現在的工程是如何生成 xr_system.img 的?

    Wireless & Analog Series
    2
    3
    1199
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • iysheng
      iysheng LV 5 last edited by

      最近看了幾天的編譯構建,目前還是沒有搞清楚 xr_system.img 是如何編譯出來的?希望有懂的人可以指點一下。

      1 Reply Last reply Reply Quote Share 0
      • iysheng
        iysheng LV 5 last edited by

        最近幾天學習了構建的過程,再次記錄描述一下。

        該文件記錄 XR806 工程的編譯過程

        涉及到的組件
        * [GN](https://gn.googlesource.com/gn/+/main/docs/reference.md)
        * [ninja](https://ninja-build.org/)
        
        編譯命令
        階段一: 配置以及編譯扳機的原生靜態庫
        1. cd device/xradio/xr806/xr_skylark 切換到處理器層級目錄
        2. cp project/demo/audio_demo/gcc/defconfig .config 複製 demo 工程的配置文件
        3. make menuconfig;make build_clean 根據 demo 配置工程,並進行工程清理, 實際的目標定義在 project/prject.mk 文件中
          1. <a id='SDKconfig'>配置工程</a>,生成 ../liteos_m/SDKconfig.gni 文件,這一步很重要
          menuconfig: mconf
              $< ./Kconfig
              python config.py
          
          特別地在 python config.py 過程中會根據 .config 文件生成 ../liteos_m/SDKconfig.gni 文件,這個文件在後續構建鴻蒙的時候會用到.
          2.
          #  在文件 gcc/Makefile 中定義的 ROOT_PATH
          #  絕對路徑 device/xradio/xr806/xr_skylark 是處理器級別的
          ROOT_PATH := ../../../..
          build_clean: clean lib_clean lib_install_clean
          	-$(Q)-rm -rf $(ROOT_PATH)/out/*
          # 刪除編譯的工程文件
          clean:
          	$(Q)-rm -f $(PROJECT_LD) $(PROJECT).* *.bin $(OBJS) $(DEPS)
          	$(Q)-rm -rf $(ROOT_PATH)/out/*
          lib_clean:
          # 進入到 **src** 目錄執行對應的清除動作
          	$(Q)$(MAKE) $(S) -C $(ROOT_PATH)/src clean
          lib_install_clean:
          	$(Q)$(MAKE) $(S) -C $(ROOT_PATH)/src install_clean
          
        4. make lib -j 編譯設備層級的 lib 文件,这些 lib 文件在当前处理器级别的 src 目录,并且会将这些 .a 文件复制到当前处理器界别的 lib 目录。
          lib: __lib
          
          # 根據 .config 生成對應的 autoconf.h 頭文件
          # 執行的命令是 $(CONF) --syncconfig $(Kconfig) 文件就可以生成 include/generated/autoconf.h 頭文件
          include/generated/autoconf.h:
          	$(Q)cd $(ROOT_PATH); $(MAKE) prj=$(PRJ_PARENT_DIR)/$(PROJECT) $@; cd -
          
          ifeq ($(sdk_cfg_rdy), y)
          # 第二次開始編譯庫並執行對應 src 目錄的 Makefile
          __lib:
          # 执行 src 目录的 Makefile
          	$(Q)$(MAKE) $(S) -C $(ROOT_PATH)/src install
          else
          # 第一次生成頭文件
          __lib: include/generated/autoconf.h
          	$(Q)$(MAKE) $(S) $@ sdk_cfg_rdy=y
          endif
          
          install:
          	$(Q)$(MAKE) _install TARGET=install
          
          _install: $(SUBDIRS)
          
          # 分别去对应当的 SUBDIRS 中执行 install
          $(SUBDIRS):
          	$(Q)$(MAKE) $(S) -C $@ $(TARGET)
          
          # 以 src/console/ 目錄下的 Makefile 爲例
          
          # 编译 lib 的目标就是 install, 該目標定義在 src/lib.mk 文件中
          #  如果没有特殊指定,那么 lib 的安装目录文件在處理器級別的 lib/ 目录
          INSTALL_PATH ?= $(ROOT_PATH)/lib
          install: $(LIBS)
          	# 将生成的 .a 库文件移动到指定的目录
          	$(Q)$(CP) -t $(INSTALL_PATH) $^
          
        階段二: 配置鴻蒙系統
        1. cd - 进入到整个鸿蒙工程的根目录
        2. hb set 调用 hb 工具的 set 参数。 hb 是鸿蒙构建写的一个 python 工具集合,这个工具的源码在 XR806 这个鸿蒙的工程中 build/lite/hb 目录。 里面 main.py 文件的 main() 函数是这个工具的入口函数。
          1. 针对 set 参数,会 import hb.set.set module, 执行该模块的 exec_command 函数
          2. hb.set.set 模块的 exec_command 函数会提示配置 product 目录。配置完成后,会配置 hb 的唯一的 config 对象(Config 类的单例模式). 关键的的内容为(以选择 wifi_skylark@xradio 为例子):
            • config.product = wifi_skylark
            • config.product_path = vendor/xradio/xr806
            • config.board config.kernel config.kernel_version config.dev_company 都是从 config.product 目录的 config.json 文件中获取的参数信息
            • config.device_path = device/xradio/xr806/liteos_m 这个路径比较重要!!!
        階段三: 編譯鴻蒙系統
        1. hb build -f -c gcc, 備註,因爲我使用的是 gcc 工具鏈,所以額外添加了參數 -c gcc. 執行的 hb.build.build 的 exec_command 函數. 這個是入口,該函數在文件 build/lite/build/build.py 中定義。特別地,在加載對應 module 的時候,會首先執行對應 module 的 add_options 函數,添加針對這個 module 的參數列表,下面步驟截取關鍵的內容展開描述:
          1. build.register_args('ohos_build_type', args.build_type[0]) # 如果沒有 -b 指定編譯類型,默認是 debug, 設置 build 對象的 ohos_build_type 爲 debug
          2. build.compiler = args.compiler[0] # 如果通過 -c 選項指定了編譯器類型,那麼會使用新的編譯器類型複製給 build 對象的 compiler 參數
          3. 如果帶有 -f 選項, args.full = true
          4. return build.build(args.full, cmd_args=cmd_args) 執行 build 構建過程,在文件 build/build_process.py 文件中定義
            1. self.check_in_device()
              • 使用階段二配置的鴻蒙工程參數,設置 build 對象的 product_path,device_path,ohos_kernel_type
              • 設置全局的 config 的 out_path 爲鴻蒙工程根目錄/out/${board}/${product} 目錄
              • 將 product_path 的 config.json 文件內容相關的參數列表,添加到 build 對象的 args_list 參數列表
            2. cmd_list = self.get_cmd(full_compile, ninja) -> self.get_cmd(True, True)
              • return [self.gn_build, self.ninja_build] 執行 build 對象的 gb_build 和 ninja_build
                • gn_build -> gn_path gen out/${board}/${product} --root=鴻蒙的根目錄 --dotfile=鴻蒙的根路徑/build/lite/.gn --script-executable=python3 --args=參數列表
                • ninja_build -> ninja_path -w dupbuild=warn -C out/${board}${product} + ninja_args
                  特別地,因爲在整個編譯過程中不是執行編譯腳本,而是通過 hb.common.utils 工具的 exec_command 函數調用的這些動作,這個函數的一個重要作用是實現了輸出的重定向, 將編譯的過程輸出重定向到了文件中. 不管是 gn_build 還是 ninja_build 都是將這些編譯日志信息重定向到了文件 out/${board}/${product}/build.log 中.
              # 這裏對輸出進行了重定向, 講具體的編譯過程重定向到指定的文件中
              def exec_command(cmd, log_path='out/build.log', **kwargs):
                  ...
              
        階段四:執行構建生成 img 文件

        經過前三個命令後就可以在 product 級別的 out 目錄 device/xradio/xr806/xr_skylark/out/ 中找到生成的 xr_system.img 文件了,具體這個文件是怎麼生成的呢。這個階段對階段三的 gn 構建部分進行展開闡述。

        • gn gen out/xradio/xr806 --root=. --dotfile=build/lite/.gn 參數列表
          1. --root 參數指定了 gn 構建的根目錄
          2. --dotfile 參數明確指定了構建的 .gn 文件, 默認會在 root 目錄查找 .gn 文件, 特別地 .gn 文件也會定義 root 變量,確定 gn 構建的根目錄. build/lite/.gn 文件內容是:
          # The location of the build configuration file.
          # gn build 的 config 文件
          buildconfig = "//build/lite/config/BUILDCONFIG.gn"
          # The source root location.
          # 重新定義了構建的 root 目錄
          root = "//build/lite"
          
          build/lite/config/BUILDCONFIG.gn 文件內容是:
          import("//build/lite/ohos_var.gni")
          import("${device_path}/config.gni") -> import("//device/xradio/xr806/liteos_m/config.gni")
          ...
          
          device/xradio/xr806/liteos_m/config.gni 文件內容是,涉及到 SDKconfig.gni 文件生成:
          import("//device/xradio/xr806/liteos_m/SDKconfig.gni") -> 這個文件是在階段一配置過程中生成的
          ...
          # Board related headfiles search path.
          SDKpath = "//device/xradio/xr806" -> 定義了 SDK 的路徑
          ...
          board_include_dirs += [
              ...
              "${SDKpath}/xr_skylark/project/${ProjectPath}", 添加工程路徑用來搜索頭文件
              ...
          ]
          ...
          
          device/xradio/xr806/liteos_m/SDKconfig.gni 文件內容是:
          ...
          ProjectPath = "demo/audio_demo" -> 定義了變量 ProjectPath
          ...
          
          到此,gn 的 config 過程暫時告一段落, 主要就是配置了一些環境變量、編譯選項等內容。下一步就是真正的構建了.因爲 root_path 是 //build/lite, 構建會去該目錄查找 BUILD.gn 文件.
          //build/lite/BUILD.gn 文件內容是:
          # 定义了 ohos 这个组
          group("ohos") {
            deps = []
            # 默認這裏爲空
            if (ohos_build_target == "") {
              # Step 1: Read product configuration profile.
              # 讀取 product_path 目錄下的 json 配置文件
              # product 級別 vendor/xradio/xr806 目錄下的 config.json 文件
              product_cfg = read_file("${product_path}/config.json", "json")
              # kernel 是 product_cfg 文件配置的類型 liteos_m
              kernel = product_cfg.kernel_type
              # Step 2: Loop subsystems configured by product.
              # 遍歷 config.json 文件中定義的所有的 subsystems
              foreach(product_configed_subsystem, product_cfg.subsystems) {
                subsystem_name = product_configed_subsystem.subsystem
                subsystem_info = {
                }
                # Step 3: Read OS subsystems profile.
                # 根據對應的的 subsystem, 讀取 components 對應目錄的 json 配置文件
                # subsystem 對應的配置文件保存在 //build/lite/components 目錄, 讀取對應名稱的 json 配置文件
                subsystem_info =
                    read_file("//build/lite/components/${subsystem_name}.json", "json")
                # Step 4: Loop components configured by product.
                # 遍歷具體的 subsystem 的 components 組件
                foreach(product_configed_component,
                        product_configed_subsystem.components) {
                  # Step 5: Check whether the component configured by product is exist.
                  # 查找是否找到了對應的 component, 如果至少找到一個就複製 component_found 爲 true
                  component_found = false
                  foreach(system_component, subsystem_info.components) {
                    if (product_configed_component.component ==
                        system_component.component) {
                      component_found = true
                    }
                  }
                  # 如果沒有找到那麼斷言錯誤
                  assert(
                      component_found,
                      "Component \"${product_configed_component.component}\" not found" +
                          ", please check your product configuration.")
                  # Step 6: Loop OS components and check validity of product configuration.
                  # 如果在對應的 subsystem 的 json 配置文件中找到了對應的定義的 component 組件
                  # 繼續從頭遍歷該 subsystem
                  foreach(component, subsystem_info.components) {
                    kernel_valid = false
                    board_valid = false
                    # Step 6.1: Skip component which not configured by product.
                    # 如果找到了這個 component
                    if (component.component == product_configed_component.component) {
                      # Step 6.1.1: Loop OS components adapted kernel type.
                      # kernel 根據 product 的 json 配置文件已經初始化爲 liteos_m
                      # 遍歷這個 subsystem 的 component 的所有 adapted_kernel 配置參數
                      # 如果這個 component 已經適配了指定的 kernel 類型。 表示 component 有效
                      foreach(component_adapted_kernel, component.adapted_kernel) {
                      # 如果有定義的 kernel 類型,表示匹配,並且該 kernel 類型有效
                        if (component_adapted_kernel == kernel && kernel_valid == false) {
                          kernel_valid = true
                        }
                      }
                      # 如果該組件沒有適配指定的內核類型,那麼打印錯誤信息
                      assert(
                          kernel_valid,
                          "Invalid component configed, ${subsystem_name}:${product_configed_component.component} " + "not available for kernel: $kernel!")
                      # Step 6.1.2: Add valid component for compiling.
                      # 將該 component 的 targets 添加到 deps 依賴
                      foreach(component_target, component.targets) {
                        deps += [ component_target ]
                      }
                    }
                  }
                }
              }
              # Step 7: Add device and product target by default.
              # 添加設備和 product 的依賴 !!! 這裏是重點
              # xr_system.img 文件生成靠的是依賴 ${device_path}/../ 路徑下的 BUILD.gn 文件
              deps += [ 
                "${device_path}/../",
                "${product_path}" ]
            } else {
              deps += string_split(ohos_build_target, "&&")
            }
          }
          
          以上內容將需要構建的依賴都包含 deps 中了,最終生成的 xr_system.img 的入口還是 ${device_path}/../ 這個依賴,下面進行詳細闡述。
          /device/xradio/xr806/liteos_m/../BUILD.gn 文件內容是:
          ...
          import("//build/lite/config/component/lite_component.gni") -> 定義了 build_ext_command 模板類
          ...
          build_ext_component("libSDK") {
            exec_path = rebase_path(".", root_build_dir) -> 將當前目錄設置爲相對內置變量 root_build_dir 的相對路徑
            outdir = rebase_path("$root_out_dir") -> 將 root_out_dir 設置爲相對系統絕對路徑的路徑格式,轉換到 ourdir 目錄
            # 构建的命令, 模板類展開後會通過這個 build.sh 腳本完成 img 的構建
            command = "./build.sh ${outdir}"
            deps = [
              "//build/lite/:ohos",
              "//kernel/liteos_m:kernel",
              "os:liteos_glue",
            ]
            if (IsBootloader == "false") {
              deps += [
                "adapter/hals:adapter",
                "adapter/console:app_console",
                "ohosdemo:ohosdemo" -> 這個是我測試自己添加的一個組件
              ]
            }
          }
          
          上述文件通過 build_ext_component 引用了一個模板類,具體的定義在文件 //build/lite/config/component/lite_component.gni, 內容爲:
          # 定义了一个模板类
          template("build_ext_component") {
            if (defined(invoker.version)) {
              print(invoker.version)
            }
            # 定義了同名的 action
            action(target_name) {
                  ... -> 在這裏會調用到定義的 command 對象,執行 ./build.sh /系統絕對路徑/out/xr806/wifi_skylark
            }
            ...
          }
          
          通過 ./build.sh 最終就可以生成 xr_system.img 文件了。最終的實現在 project/project.mk 文件中定義, 整個 Makefile 的入口是在 device/xradio/xr806/xr_skylark/Makefile。在此僅僅闡述最後生成 img 執行的動作,截取自文件 device/xradio/xr806/xr_skylark/project/project.mk。內容爲:
          cd $(IMAGE_PATH) && \
          chmod a+r *.bin && \
          $(Q)$(CC) -E -P -CC $(CC_SYMBOLS) -I$(ROOT_PATH)/../include/generated -include autoconf.h -o $(PROJECT_IMG_CFG) - < $(IMAGE_CFG) && \
          $(SIGNPACK_GEN_CERT) && \
          chmod 777 $(IMAGE_TOOL) && $(IMAGE_TOOL) $(IMAGE_TOOL_OPT) -c $(PROJECT_IMG_CFG) -o $(IMAGE_NAME).img
          # 講 project 中的 bin 文件複製到頂層目錄下的 out 目錄中
          @test -d "$(ROOT_PATH)/out" || mkdir -p "$(ROOT_PATH)/out"
          $(Q)$(CP) -t $(ROOT_PATH)/out/ $(IMAGE_PATH)/*.bin $(IMAGE_PATH)/$(IMAGE_NAME).img *.map
          
        1 Reply Last reply Reply Quote Share 1
        • xiaowenge
          DOT小文哥 LV 8 last edited by

          非常感谢分享,很详细👍
          (不过master仓库的打包方式好像变了
          (但是思路没变 很有参考意义

          1 Reply Last reply Reply Quote Share 0
          • 1 / 1
          • First post
            Last post

          Copyright © 2024 深圳全志在线有限公司 粤ICP备2021084185号 粤公网安备44030502007680号

          行为准则 | 用户协议 | 隐私权政策