Skip to content

# 3.1. Build 环境建立

根据 开发环境准备 搭建好开发环境后，下载代码到开发环境中，可使用以下方式进行整体编译。

集成.config文件：

    make lunch

    make -C /work/pull/build_tools/kbuild_tools/src O=/work/pull/out/host/kbuild_tools config
    make[1]: Entering directory '/work/pull/build_tools/kbuild_tools/src'
    GEN     /work/pull/out/host/kbuild_tools/Makefile
    make[3]: Nothing to be done for 'config'.
    make[1]: Leaving directory '/work/pull/build_tools/kbuild_tools/src'
    You're in configuration
    Lunch menu...pick a combo:

    J6
            1 ---> j6e_debug_defconfig
            2 ---> j6e_debug_gcc9_defconfig
            3 ---> j6e_debug_ubuntu_defconfig
            4 ---> j6e_release_defconfig

    Which would you like?

备注

1. 目前默认config所用的编译器版本为gcc12.2。

配置环境变量：

    source envsetup.sh

    =========================================
    BOARD_CONFIG=j6e_release_defconfig
    ARCH=arm64
    CROSS_COMPILE=/usr/bin/aarch64-linux-gnu-
    HR_TARGET_VENDOR=horizon
    HR_TARGET_PROJECT=j6e
    HR_TARGET_MODE=release
    HR_TARGET_BIT=64
    KERNEL_VER=6.1.94
    =========================================
    Shortcut commands:
            build.sh docker: bd, bdall, bdm, bdkmhb, bdkmk
            kernel menuconfig: bkmc
            kernel targets: build.sh k_modules bkt <kernel-target>
                kenrel-target:
                    all       - Build all targets marked with [*]
                    vmlinux   - Build the bare kernel
                    modules   - Build all modules
                    modules_install - Install all modules to INSTALL_MOD_PATH (default: /)
                    dir/            - Build all files in dir and below
                    dir/file.[ois]  - Build specified target only
    =========================================

执行编译动作：

    bdall

生成的文件位于目录 `./out/release-gcc_12.2-64/target/product/img_packages/` 下。

备注

1. `make lunch` 非交互选择project的指令为 `make CHIP_DIR=j6 j6e_release_defconfig` 。

2. 建议 `make lunch` 选择时使用菜单名，防止数字发生变化。使用 `make lunch` 交互时，注意 `make lunch <number>` 所对应菜单名的一致性。

3. 除了 `make lunch`，也可以使用 `make menuconfig` 来手动修改配置生成所需环境变量。

1. `make lunch` 时使用的 j6e_release_defconfig；是由 `make menuconfig` 修改相应配置生成的 “.config” 文件拷贝而成。

2. 使用export直接修改的环境变量无法在docker中生效，如果需要特殊设置可以通过 `make menuconfig` 修改或直接生成的 “.config” 文件。

3. 修改 “.config” 文件后，请重新执行 `make lunch` 与 `source envsetup.sh`。

# 3.2. Build 命令支持

以下命令均需要集成.config文件并配置环境变量，命令行形式为：

    source envsetup.sh lunch

## 3.2.1. 编译

1. 编译所有模块

编译所有模块，对编译生成的文件打包并生成各个分区镜像，最常用编译指令。

    bdall

2. 编译所有模块并生成disk镜像

编译所有模块，对编译生成的文件打包并生成各个分区镜像，同时生成flash_disk、emmc_disk以及ufs_disk等整包镜像。

    bddisk

3. 打印所有可编译模块

    bd print_mod all

4. 单独编译某个独立模块以及依赖

“独立模块” 指的是kernel以及hobot-drivers外通过Kconfig接入到build中的模块，如hbre下的otaupdate。

    bdm <module_name>

    # For example, otaupdate
    bdm otaupdate

5. 单独编译kernel模块

需先完成对kernel的编译，编译方法：

    bdm kernel

编译在kernel中的模块:

    bdkmk <kmod_name>

编译在hobot-drivers中的模块:

    bdkmhb <kmod_name>

6. 清理模块输出

调用每个模块的clean接口进行清理。

清理单个模块：

    bd clean <module_name>

清理所有模块：

    bd cleanall

7. 清理out目录

    bd clean_out

8. 清理ccache

    bd clean_ccache

## 3.2.2. 打包

1. 打包所有分区镜像

打包所有分区镜像，需先完成所有模块的编译。

    bd img all

2. 打包单个分区镜像

打包单个分区镜像，需先完成分区对应模块的编译。

    bd img <part_name>

    # For example, uboot
    bd img uboot_a

备注

1. 建议在整编后的环境上作为调试功能使用。

2. 分区需要使用真实分区名并使用该分区的第一个分区名，例如AB需要分区需要增加_a后缀。

3. 打包所有disk镜像

生成各个分区的img以及flash_disk、emmc_disk以及ufs_disk等整包镜像，需要先编译并打包所有分区镜像。

    bdall
    bd img disk

4. 打包单个disk镜像

打包flash_disk镜像，镜像flash_disk所需的基础文件位于hbbin模块，需先编译hbbin，再处理并打包img。

    bdm hbbin
    bd img flash_disk

打包emmc_disk镜像，需要先编译并打包所有分区镜像。

    bdall
    bd img emmc_disk

打包ufs_disk镜像，需要先编译并打包所有分区镜像。

    bdall
    bd img ufs_disk

## 3.2.3. 制作 OTA 包

1. 制作OTA包

制作OTA升级包，需要先编译所有模块并生成各个分区镜像，生成的文件位于目录 `./out/release-gcc_12.2-64/target/product/ota_packages/` 下。

OTA 包可以制作全量包、系统包以及app包等。

制作全量包以及系统包：

    bd otapackage all

制作app包：

    bd otapackage app

制作middleware包：

    bd otapackage middleware

## 3.2.4. 其他常用命令

1. 打印编译log

查看某个模块编译log：

    bd print_log <module_name>

查看镜像打包log：

    bd print_log package

2. 制作AppSDK

制作AppSDK，需要先编译所有模块，生成的文件位于目录 `./out/release-gcc_12.2-64/target/product/AppSDK/` 下。

    bdsdk appsdk

## 3.2.5. Docker 环境执行命令

Build 系统支持使用docker环境执行命令。

1. 使用docker环境执行特定命令

使用docker环境执行特定命令：

    bd sys <cmd>

当执行docker编译时用ctrl-c停止或编译出错可能会导致文件权限问题，可使用docker环境执行任何命令恢复权限：

    bd sys chown -R 11135925 xxxxxx

其中，xxx为目录或文件名。

备注

- 权限问题或有特殊需求均可进入docker环境操作。

# 3.3. 环境变量配置

## 3.3.1. 环境变量配置功能介绍

编译参数以环境变量的形式存在，当前build系统的环境变量的配置是通过Kbuild工具来实现的，Kbuild工具的源码放在源码根目录 `./build_tools/kbuild_tools` 下。

每次配置环境变量时会先使用host工具链编译Kbuild工具中的conf和mconf。我们修改了Kbuild工具的源码，使得Kbuild工具生成的的 “.config” 文件中数据的形式是：

    export var_name=var_value

Kbuild工具的Kconfig文件在源码根目录 `./device/horizon/j6/kconfigs` ，有已经配置的文件：

    .
    ├── board_Kconfig        # 板级环境变量配置文件
    ├── out_dir_Kconfig      # 输出目录配置
    ├── package_Kconfig      # 模块编译配置
    ├── partition_Kconfig    # 分区配置
    ├── project_Kconfig      # 项目配置
    ├── sample_Kconfig       # sample 编译配置
    ├── src_dir_Kconfig      # 源码目录配置
    └── toolchain_Kconfig    # 工具链信息配置

每个加入整体编译的模块都需要有一个Kconfig文件用来表示模块的配置，hbre模块的Kconfig在package_Kconfig文件中通过source引用，sample模块的Kconfig在sample_Kconfig通过source引用。例如：

    menu "hbre"

    config HB_COMP_HBRE
        bool "Build all packages in component hbre"
        default y

    source "hbre/secure_storage/Kconfig"

    source "hbre/otaupdate/Kconfig"

    source "hbre/boot-utils/hb-init-scripts/Kconfig"

    source "hbre/boot-utils/hb-configs/Kconfig"

    source "hbre/otaupdate/Kconfig"

    source "hbre/libhbmem/Kconfig"

    ***

## 3.3.2. 环境变量导出

make lunch

通过make lunch命令可以列出 `./device/horizon/j6/defconfig` 中的所有配置文件，并且每个配置文件后都跟有一个序号，通过输入序号可引用对应的配置文件，生成.config文件。

make CHIP_DIR=<chip>_<xxx>_defconfig

此命令可以指定对应的芯片和配置文件名，然后去引用 `./device/horizon/j6/defconfig/<chip>_<xxx>_defconfig` ，生成对应的.config文件，用户可自由修改配置文件里的编译参数。

make menuconfig

此命令可以打开上面两个命令生成的.config文件，以可视化的形式对其中一些默认环境变量的值进行修改。

上述命令只是配置环境变量，导出需要另外执行命令 `source envsetup.sh`

# 3.4. 镜像分区配置

## 3.4.1. 配置文件说明

配置文件为：device/horizon/j6/partition_config_files/j6e-gpt.json

    {
        "global": {
            "antirollbackUpdate_host": true,
            "antirollbackUpdate_hsm": false,
            "ab_sync": true,
            "backup_dir": "/tmp/ota/backup",
            "AB_part_a": "_a",
            "AB_part_b": "_b",
            "BAK_part_bak": "_bak"
        },
        "fpt": "fpt_common",                 //此部分往下是flash上的分区表
        "recovery": "recovery_common",
        "misc": "misc_common",
        "HB_APDP":"HB_APDP_common",
        "keystorage": "keystorage_common",
        "HSM_FW": "HSM_FW_common",
        "HSM_RCA": "HSM_RCA_common",
        "keyimage": "keyimage_common",
        "SBL": "SBL_common",
        "scp": "scp_common",
        "spl": "spl_common",
        "MCU": "MCU_common",

        "quickboot": "quickboot_common",    //此部分往下是emmc&ufs上的分区表
        "veeprom": "veeprom_common",
        "ubootenv": "ubootenv_common",
        "acore_cfg": "acore_cfg_common",
        "bl31": "bl31_common",
        "optee": "optee_common",
        "uboot": "uboot_common",
        "vbmeta": "vbmeta_common",
        "boot": "boot_common",
        "system": "system_common",
        "basesystem": "basesystem_common",
        "system_verity": "system_verity_common",
        "middleware": "middleware_common",
        "app": "app_common",
        "minidump": "minidump_common",
        "app_param": "app_param_common",
        "ota": "ota_common",
        "log": "log_common",
        "backhaul": "backhaul_common",
        "userdata": "userdata_common",
        "map": "map_common"
    }

整体分区表的配置分为全局共享配置和分区单独配置，其中全局共享配置放在”global”字段中，这是对所有分区都有效的配置。

支持的全局参数：

- antirollbackUpdate_host：是否更新host的anti-rollback版本，true或false

- antirollbackUpdate_hsm：是否更新hsm的anti-rollback版本，true或false

- ab_sync：ota是否ab分区同步更新，true或false

- backup_dir：hsm备份目录

- AB_part_a：AB分区的A分区后缀

- AB_part_b：AB分区的B分区后缀

- BAK_part_bak：备份分区后缀名，备份分区完整分区名为part + BAK_part_bak+backup_start_index

分区单独配置使用”分区名”: “分区配置类型”的形式，可以根据需要选择不同的分区配置类型。 例如：boot分区就需要首先在分区全局配置中添加boot分区的描述，然后在 `device/horizon/j6/partition_config_files/sub_config` 中创建boot.json，并在其中定义boot_common：

    {
        "boot_common": {
            "components":[
                "${TARGET_DEPLOY_DIR}/rootfs/boot/:60m"
            ],
            "pre_cmd": "pack_boot.sh",
            "post_cmd": "pack_avb_img.sh boot boot",
            "fs_type": "ext4",
            "medium": "emmc",
            "ota_is_update": true,
            "ota_update_mode": "image",
            "part_type": "AB",
            "part_type_guid": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
            "size": "60m"
        }
    }

支持的分区参数：

- depends：表示分区依赖关系，当前分区的制作需要依赖哪些分区制作完成，列表的形式，在其中添加依赖的分区名

- components：表示当前分区需要包含哪些内容，可以是子镜像路径或文件目录，文件目录会做成文件系统，当前支持的文件系统有ext4和fat16，在路径后使用“:”表示当前这一部分在分区中所占的大小。子镜像可以为多个，子镜像建议放在 `out/xxx/deploy` 下的对应分区的目录中

- components_nose：非安全启动镜像组件

- pre_cmd：在根据component内容制作镜像以前需要执行的命令

- post_cmd：在根据component内容制作完镜像之后需要执行的命令

- fs_type：镜像类型，None/ext4/fat16/misc

- medium：镜像位于emmc/nor

- ota_is_update：全量ota包是否包含

- ota_update_mode：分区的OTA升级方式；image为镜像升级，默认值，image_diff为镜像差分

- is_rootfs：是否是rootfs分区

- part_type：分区类型，当前支持AB，BAK，PERMANENT

- size：表示分区的大小，单位可以是k, m, g

- magic：表示分区的magic，只针对添加flash镜像头的分区镜像有效

- have_anti_ver：当前分区镜像中是否包含antirollback版本，true或false

- load_addr：表示镜像的加载地址，只针对添加flash镜像头的分区镜像有效

- entry_addr：表示镜像的入口地址，只针对添加flash镜像头的分区镜像有效

- nose_support：非安全启动镜像支持

备注

1. Json 中分区顺序即为真实的分区表排列顺序。

2. 分区size需根据介质扇区的对齐要求设置。

## 3.4.2. 分区表

分区表分为flash分区表以及emmc/ufs分区表

Flash分区表

序号 | 分区名 | 二级分区 | 起始地址 | 大小 | 用途
--- | --- | --- | --- | --- | ---
1 | fpt |  | 0 | 256K | 包含多分个区：hsm、xspi、key以及ivt_host等
|  | IVT_HSM | 0 | 128B | 放置 ivt for hsm
|  | IVT_HSM_bak | 0x80 | 128B | 放置 ivt for hsm backup
|  | XSPICP_a | 0x100 | 512B | 放置 XSPI 参数A，用于bootroom中flash切高速
|  | XSPICP_bak | 0x300 | 512B | 放置 XSPI 参数B，用于bootroom中flash切高速
|  | flash_key | 0x500 | 256B | 放置 flash 烧写验签公钥
|  | flash_key_bak | 0x600 | 256B | 放置 flash 烧写验签公钥
|  | IVT_HOST_a | 0x700 | 512B | Host 的 ivt a 分区镜像信息
|  | IVT_HOST_b | 0x900 | 512B | Host 的 ivt b 分区镜像信息
|  | IVT_HOST_R | 0xb00 | 512B | Host 的 ivt recovery 分区镜像信息
|  | IVT_HSM_RAW | 0xd00 | 128B | IVT_HSM分区镜像的明文副本，仅nonscure版本存在该镜像
|  | IVT_HSM_RAW_bak | 0xd80 | 128B | IVT_HSM_bak分区镜像的明文副本，仅nonscure版本存在该镜像
2 | recovcery | HSM_RCA_r | 0x40000 | 256k | 存放地平线HSM RCA证书的镜像，HSM启动时将该证书导入到HSM中
|  | keyimage_r | 0x80000 | 256K | 存放用户密钥，HSM启动时将密钥导入到HSM中
|  | SBL_r | 0xc0000 | 512k | 运行在MCU上的启动镜像，负责加载AUTOSAR和Acore镜像
|  | scp_r | 0x140000 | 512k | 运行在MCU上的scp电源管理镜像
|  | spl_r | 0x1c0000 | 768K | Acore的启动镜像
|  | MCU_r | 0x280000 | 3.5M | MCU recovery镜像，由客户定制
3 | misc |  | 0x600000 | 256K | 存储 slot ab 状态机
4 | HB_APDP |  | 0x640000 | 256K | 放置客户调试开关的分区，提供各种调试机制是否需要打开的开关
5 | keystorage |  | 0x680000 | 512K | 用于存储用户密钥
6 | HSM_FW |  | 0x700000 | 512K | HSM运行镜像，主分区
7 | HSM_FW_bak |  | 0x780000 | 512K | HSM运行镜像，备份分区
8 | HSM_RCA |  | 0x800000 | 256K | 放置HSM RCA证书的镜像，SM启动时将密钥导入到HSM中，主分区
9 | HSM_RCA_bak |  | 0x840000 | 256K | 放置HSM RCA证书的镜像，SM启动时将密钥导入到HSM中，备份分区
10 | keyimage |  | 0x880000 | 256K | 放置用户密钥的镜像，SM启动时将密钥导入到HSM中，主分区
11 | keyimage_bak |  | 0x8c0000 | 256K | 放置用户密钥的镜像，SM启动时将密钥导入到HSM中，备份分区
12 | SBL |  | 0x900000 | 512K | 运行在MCU上的启动镜像，负责加载AUTOSAR和Acore镜像
13 | SBL_bak |  | 0x980000 | 512K | 运行在MCU上的启动镜像，负责加载AUTOSAR和Acore镜像
14 | scp_a |  | 0xa00000 | 512K | 运行在MCU上的scp电源管理固件，对acore时钟/电源的初始化
15 | scp_b |  | 0xa80000 | 512K | 运行在MCU上的scp电源管理固件，对acore时钟/电源的初始化
16 | spl_a |  | 0xb00000 | 768K | spl分区，存储acore的启动镜像，启动包含ddr功能相关镜像
17 | spl_b |  | 0xbc0000 | 768K | spl分区，存储acore的启动镜像，启动包含ddr功能相关镜像
18 | MCU_a |  | 0xc80000 | 6.5M | MCU镜像，由客户定制开发
19 | MCU_b |  | 0x1300000 | 6.5M | MCU镜像，由客户定制开发
20 | nvm & reserved | reserved | 0x1980000 | 6M | 内部用作boot_flag的调试，该区域MCU可见，acore不可见
|  | nvm_hobot | 0x1F8000 | 512k | 内部用作nvm的调试，该区域MCU可见，acore不可见

EMMC/UFS分区表

序号 | 分区名 | 大小 | 文件系统 | 用途
--- | --- | --- | --- | ---
1 | gpt | 512K|4k | none | 通用分区表头(emmc–512k | ufs–4k)
2 | quickboot | 1M | none | ddr快速启动参数分区
3 | veeprom | 1M | none | 存储系统配置分区（ota标志，网络配置等）
4 | ubootenv | 1M | none | Uboot环境变量配置分区
5 | acore_cfg_a | 2M | none | Acore config
6 | acore_cfg_b | 2M | none |
7 | bl31_a | 2M | none | Arm Trusted Environment Firmware
8 | bl31_b | 2M | none |
9 | optee_a | 8M | none | Open Portable Trusted Execution Environment
10 | optee_b | 8M | none |
11 | uboot_a | 8M | none | uboot
12 | uboot_b | 8M | none |
13 | vbmeta_a | 1M | none | 用于系统AVB启动校验的签名镜像
14 | vbmeta_b | 1M | none |
15 | boot_a | 60M | EXT4 | Kernel镜像（image+dtb）和initramfs
16 | boot_b | 60M | EXT4 |
17 | system_a | 1228M | EXT4 | 系统运行必要的库、命令等，rootfs
18 | system_b | 1228M | EXT4 |
19 | basesystem_a | 300M | EXT4 | HOBOT工具和库分区，挂载到/usr/hobot
20 | basesystem_b | 300M | EXT4 |
21 | system_verity_a | 128M | none | 分区system哈希树校验
22 | system_verity_b | 128M | none |
23 | middleware_a | 1638M | EXT4 | 存放中间件文件，挂载到/middleware
24 | middleware_b | 1638M | EXT4 |
25 | app_a | 4608M | EXT4 | app
26 | app_b | 4608M | EXT4 |
27 | minidump | 256M | none | minidump 工具
28 | app_param | 500M | none | 存放app运行参数
29 | ota | 8192M | none | 存储ota升级缓存包
30 | log | 4096M | none | 存储日志文件
31 | backhaul | 3072M | none | 存放回传相关数据
32 | userdata | 2048M | none | 存储用户数据
33 | map | 26G | none | 存储地图相关数据，根据实际具体需求分配

## 3.4.3. 分区修改说明

J6X支持变更分区emmc&ufs上的分区，只需添加或删除或修改分区配置对应的分区字段。对于flash分区，如有变更需求，请联系地平线。

当修改emmc&ufs上的分区时，有以下注意事项：

- 启动分区：块设备分区表中system_verity之前的分区（包括system_verity）为启动分区，原则上不应当修改这些分区。如果开发者对于J6X 启动流程较为熟悉则可以尝试修改，地平线不负责相关技术支持。

- app分区：此分区一般用来存储应用，原则上只能调整大小。如果删除该分区，可能会导致ota app 功能不可用。

- log 分区：J6X默认使用log分区用来存储log，如果开发者删除该分区，则需要调整runtime系统配置以重新选择log存放目录

- ota分区：J6X默认使用ota分区用来完成ota升级相关的操作，如果开发者删除该分区，则需要调整runtime系统配置以重新选择ota工作目录

      ###file: ~/device/horizon/j6/runtime_config_files/j6e_evm_runtime_config
      OTA_DEPLOY_DIR=/ota   ###指定ota的工作目录，比如调整为/userdata/ota
      LOG_DEPLOY_DIR=/log   ###指定ota的工作目录，比如调整为/userdata/log

- 其它用户分区：原则上这类分区可以自由调整（增删，或调整大小），但要注意如果涉及到挂载分区调整，则需要调整fstab以适配新的分区调整

      ###file：~/device/horizon/j6/fstab_config_files/j6e-evm-hb-fstab
      # UNCONFIGURED FSTAB FOR BASE SYSTEM
      /dev/block/platform/by-name/basesystem_cur  /usr/hobot  ext4 defaults,noatime,ro  0  1
      /dev/block/platform/by-name/middleware_cur /middleware  ext4 defaults,noatime,ro  0  1
      /dev/block/platform/by-name/app_cur  /app  ext4 defaults,noatime,ro  0  1
      /dev/block/platform/by-name/userdata /userdata ext4 defaults,noatime,discard  0  1
      /dev/block/platform/by-name/ota /ota ext4 defaults,noatime,discard  0  1  ### 如果新增test分区，且需要挂载，可以仿造这里增加fstab项
      /dev/block/platform/by-name/app_param /app_param ext4 defaults,noatime,discard  0  1
      /dev/block/platform/by-name/map /map ext4 defaults,noatime,discard  0  1
      /dev/block/platform/by-name/log /log ext4 defaults,noatime,discard  0  1  ###如果删了ota分区，这里也要对应删除
      /dev/block/platform/by-name/backhaul /backhaul ext4 defaults,noatime,discard  0  1

# 3.5. 源码接入

## 3.5.1. 编译系统调度简介

整体编译调度由”paral_build.py”脚本负责，该脚本会对模块依赖进行识别，形成并行编译任务并且根据当前系统的cpu数进行编译任务的进程创建和执行。

模块的编译入口支持以下两种文件：

1. build.sh：高优先级，默认使用build.sh作为编译入口

2. Makefile：在代码目录下没有找到build.sh时使用Makefile作为编译入口，默认编译目标为”all”，也就是说，编译开始时，默认使用”make -C <module dir> all”进行编译

## 3.5.2. 编译入口文件介绍

要求：通过编译入口可以直接生成该模块的所有输出并安装到指定目录。

推荐使用Makefile，当前编译系统提供了一个Makefile.in作为Makefile的环境搭建辅助文件。

1. Makefile.in

    # 配置编译的参数和通用目标
    ifneq (${SRC_DEVICE_COMMON_DIR},)
    include ${SRC_DEVICE_COMMON_DIR}/Makefile
    else # 独立编译时需要配置的变量
    CROSS_COMPILE = # 编译器路径
    CC = ${CROSS_COMPILE}gcc
    CXX = $(CROSS_COMPILE)g++
    AR = ${CROSS_COMPILE}ar
    AS = ${CROSS_COMPILE}as
    LD = ${CROSS_COMPILE}ld
    STRIP = ${CROSS_COMPILE}strip
    RANLIB = ${CROSS_COMPILE}ranlib
    OBJCOPY = ${CROSS_COMPILE}objcopy
    OBJDUMP = ${CROSS_COMPILE}objdump
    READELF = ${CROSS_COMPILE}readelf
    NM = ${CROSS_COMPILE}nm

    CFLAGS = -Wall
    CXXFLAGS = -Wall
    CFLAGS_DYNAMIC = -fPIC -shared
    CFLAGS_STATIC = -rcs
    LDFLAGS = # Build提供的第三方开源库动态库所在目录
    INCS = # Build提供的第三方开源库头文件所在目录
    LIBS = # 具体的库
    endif

    ifeq ($(BUILD_VERBOSE),1)
    Q =
    else
    Q = @
    endif

    # 定义源码路径
    SRC_PATH = $(shell pwd)
    # 模块名字
    MODULE_NAME = $(notdir ${SRC_PATH})
    # MODULE_OUT_DIR 在paral_build.py中定义并输出
    ifneq ($(OUTPUT_BUILD_DIR),)
    MODULE_OUT_DIR = ${OUTPUT_BUILD_DIR}/$(shell realpath --relative-to=${HR_TOP_DIR} ${SRC_PATH})
    else
    # 定义模块输出路径，默认为源码目录
    MODULE_OUT_DIR = ${SRC_PATH}/out
    endif
    # deb输出路径
    PRODUCT_DEB_DIR ?= ${MODULE_OUT_DIR}
    MODULE_RUNTIME_DIR = ${MODULE_OUT_DIR}/${MODULE_NAME}-runtime
    MODULE_DEV_DIR = ${MODULE_OUT_DIR}/${MODULE_NAME}-dev

    export CROSS_COMPILE CC CXX AR AS LD STRIP RANLIB OBJCOPY OBJDUMP READELF NM \
        CFLAGS CXXFLAGS CFLAGS_DYNAMIC CFLAGS_STATIC LDFLAGS INCS LIBS \
        MODULE_NAME SRC_PATH MODULE_OUT_DIR Q MODULE_RUNTIME_DIR MODULE_DEV_DIR

    #deb:
    #ifeq ($(wildcard ${MODULE_OUT_DIR}/${MODULE_NAME}), )
    #    $(error Please run make install)
    #endif
    deb:
        $(if $(wildcard DEBIAN/control),,$(error Please creat control file))
    ifeq ($(HB_PKG_OTAUPDATE_RUNTIME_DEB), y)
        $(if $(wildcard ${MODULE_RUNTIME_DIR}),,$(error Please run make install))
        mk_deb_package.py --name ${MODULE_NAME} \
                            --pkg_type runtime \
                            --control ${SRC_PATH}/DEBIAN/control \
                            --ori_dir ${MODULE_RUNTIME_DIR} \
                            --out_dir ${PRODUCT_DEB_DIR}
    endif
    ifeq ($(HB_PKG_OTAUPDATE_DEV_DEB), y)
        $(if $(wildcard ${MODULE_DEV_DIR}),,$(error Please run make install))
        mk_deb_package.py --name ${MODULE_NAME} \
                            --pkg_type dev \
                            --control ${SRC_PATH}/DEBIAN/control \
                            --ori_dir ${MODULE_DEV_DIR} \
                            --out_dir ${PRODUCT_DEB_DIR}
    endif
    ifeq ($(HB_PKG_OTAUPDATE_SRC_TAR), y)
        $(if $(wildcard ${MODULE_RUNTIME_DIR}),,$(error Please run make install))
        mk_deb_package.py --name ${MODULE_NAME} \
                            --pkg_type src \
                            --ori_dir ${SRC_PATH} \
                            --out_dir ${PRODUCT_DEB_DIR}
    endif

2. 根目录Makefile

    include Makefile.in
    .PHONY: all src test install deb clean # 把构建的target定义在这里

    all: src install deb

    src:
        $(Q)$(MAKE) build -C src

    # 测试的应用程序在编译的输出目录test下，需要owner把测试程序拷贝到板端
    test: src
        $(Q)$(MAKE) -C test

    clean:
    # make clean 和rm命令选一个
        $(Q)$(MAKE) clean -C src
        rm -rf ${MODULE_OUT_DIR}/*
        rm -f ${PRODUCT_DEB_DIR}/**/otaupdate*.deb
        rm -f ${PRODUCT_DEB_DIR}/**/otaupdate*.tar.xz

    # MODULE_OUT_DIR为此模块的编译输出根目录，下面命令会将根目录清空
    ifneq (${MODULE_OUT_DIR},)
        $(Q)rm -rf ${MODULE_OUT_DIR}/*
    endif

    install:
        $(Q)$(MAKE) install -C src

3. 子目录(含源码的目录)Makefile

    # 定义src目录的编译输出目录，如果是当前Makefile在src的子目录，例如src/aaa/Makefile，
    #OUT_DIR = ${SUB_MODULE_OUT_DIR}/aaa 否则
    OUT_DIR = ${MODULE_OUT_DIR}/src
    SUB_MODULE_OUT_DIR = $(OUT_DIR)

    .PHONY: build install clean  # 把构建的target定义在这里

    LIB_VERSION_MAJOR := 1
    LIB_VERSION_MINOR := 0
    LIB_VERSION_PATCH := 1

    LINKNAME = <lib_name>so
    SONAME = $(LINKNAME).$(OTA_LIB_VERSION_MAJOR)
    RELNAME = $(SONAME).$(OTA_LIB_VERSION_MINOR).$(OTA_LIB_VERSION_PATCH)

    LIB_NAME_A = <lib_name>.a
    BIN_NAME = <test1>
    SUB_DIRS  := ddd aaa bbb # src下的子目录
    SRCS     = $(wildcard ./*.c)                     # 定义C/CPP源码变量
    OBJS     = $(addprefix ${OUT_DIR}/, $(patsubst %.c, %.o, ${SRCS})) # 定义对应源码c文件的.o文件

    # 编译单一输出产物
    OBJECT := ${OUT_DIR}/hbpkg
    # 如果有多个目标：
    LIB_SO = ${OUT_DIR}/$(RELNAME)
    LIB_A = ${OUT_DIR}/${LIB_NAME_A} # 定义静态库文件输出
    BIN_TEST1 = ${OUT_DIR}/${BIN_NAME}     # 定义可执行文件
    OBJECT = $(LIB_SO) $(LIB_A) $(BIN_TEST1)

    CFLAGS += -Wall
    CXXFLAGS += -Wall
    CFLAGS_DYNAMIC += -fPIC -shared
    CFLAGS_STATIC += -rcs
    LDFLAGS = -Lxxx ${LDFLAGS} # 本模块编译输出目录放前面，防止去找out/hbrootfs下旧的库文件，注意前面是"等号"
    INCS = -I${SRC_PATH}/include $(INCS) # 本模块目录放前面，先找本地目录的头文件，防止本地修改而out下没修改导致编译问题，注意前面是"等号"
    LIBS +=

    # 向子Makefile声明变量
    export SUB_MODULE_OUT_DIR

    build: SUB_DIR #${OBJECT}

    # 没有对子目录工程依赖的情况下，可以使用forloop开始编译子目录的工程
    SUB_DIR:
        $(foreach name, $(SUB_DIRS),\
            ${MAKE} -C $(name) ||\
            { echo $$?; echo "Make $(name) failed!"; exit 1; };\
        )
    # 上层Makefile对子目录工程有依赖的情况下，建议单独定义规则
    # 动态库
    $(LIB_SO): $(OBJS)
        $(Q)mkdir -p $(abspath $(dir $@))
        $(Q)echo CC $@
        $(Q)$(CC) $(CFLAGS_DYNAMIC) $(INCS) $^ $(LDFLAGS) $(LIBS) -Wl,-soname,$(SONAME) -o $@
        ln -rsf $@ ${OUT_DIR}/$(LINKNAME)

    # 静态库
    $(LIB_A): $(OBJS)
        $(Q)mkdir -p $(abspath $(dir $@))
        $(Q)echo AR $@
        $(Q)$(AR) $(CFLAGS_STATIC) $@ $<

    # 应用程序
    # 特别注意：编译依赖文件（$^ $< $(OBJS_XX)）必须在变量LIBS之前，简单程序可以直接使用SRC_XX，规则与OBJS_XX一致，需要放在LIBS前
    $(OBJECT): $(OBJS)
        $(Q)mkdir -p $(abspath $(dir $@))
        $(Q)echo CC $@
        $(Q)${CC} ${CFLAGS} ${INCS} $^ ${LDFLAGS} $(LIBS) -o $@

    # 编译中间.o文件，使用"-c"跳过ld环节，使用"$<"来使用第一个输入文件
    ${OUT_DIR}/%.o: %.c
        $(Q)mkdir -p $(abspath $(dir $@))
        $(Q)echo CC $@
        $(Q)${CC} -c $< -o $@

    clean:
        @$(foreach name, $(SUB_DIRS),\
            $(Q)${MAKE} -C $(name) clean;\
        )

        rm -rf *.o $(OBJECT)

    install:
    # 对外的库、应用程序、头文件、配置等需要拷贝至deb目录，制作deb包
    # 动态库的软链接不要在编译输出目录创建，在安装目录deb中再创建，否则install会把软链接安装为实体文件
    # 各动态库，应用程序的所属组、权限模式、所有者请各owner考虑，install -g设定所属组，-m设定权限模式，-o设定所有者
    # 制作runtime包需要准备的文件, runtime包安装在板端
    ifeq ($(HB_PKG_OTAUPDATE_RUNTIME_DEB), y)
        # 动态库，*.so.1和*.so.1.0.1
        $(Q)install -d ${MODULE_RUNTIME_DIR}/usr/hobot/lib/
        install $(LIB_SO) -m 644 ${MODULE_RUNTIME_DIR}/usr/hobot/lib/
        ln -srf ${MODULE_RUNTIME_DIR}/usr/hobot/lib/$(RELNAME) \
            ${MODULE_RUNTIME_DIR}/usr/hobot/lib/$(SONAME)
        # 服务程序
        $(Q)install -d ${MODULE_RUNTIME_DIR}/usr/hobot/bin
        install $(OBJ_OUT) ${MODULE_RUNTIME_DIR}/usr/hobot/bin
        # 配置文件/帮助文件
    endif

    # 制作dev包需要准备的文件，dev包安装在host主机上
    ifeq ($(HB_PKG_OTAUPDATE_DEV_DEB), y) # 制作runtime包需要准备的文件
        # 动态库，*.so.1 *.so.1.0.1和*.so
        $(Q)install -d ${MODULE_DEV_DIR}/usr/hobot/lib/
        install $(LIB_SO) -m 644 ${MODULE_DEV_DIR}/usr/hobot/lib/
        ln -srf ${MODULE_DEV_DIR}/usr/hobot/lib/$(RELNAME) \
            ${MODULE_DEV_DIR}/usr/hobot/lib/$(SONAME)
        ln -srf ${MODULE_DEV_DIR}/usr/hobot/lib/$(RELNAME) \
            ${MODULE_DEV_DIR}/usr/hobot/lib/$(LINKNAME)

        # 静态库
        install $(LIB_A) -m 644 $${MODULE_DEV_DIR}/usr/hobot/lib/ko

        # 头文件
        $(Q)install -d ${MODULE_DEV_DIR}/usr/hobot/include/
        install ${H_FILE} ${MODULE_DEV_DIR}/usr/hobot/include/

        # sample
    endif

    # 模块公共动态库和对应头文件需要软连接至out/**/hbrootfs下，供其他模块使用
    @if [ -n "${OUTPUT_HBROOTFS_DIR}" ]; then \
        mkdir -p ${OUTPUT_HBROOTFS_DIR}/usr/lib/; \
        mkdir -p ${OUTPUT_HBROOTFS_DIR}/include/; \
        ln -srf $(LIB_SO) ${OUTPUT_HBROOTFS_DIR}/usr/lib/${LINKNAME}; \
        ln -srf $(LIB_SO) ${OUTPUT_HBROOTFS_DIR}/usr/lib/${SONAME}; \
        ln -srf ${SRC_PATH}/include/A.h ${OUTPUT_HBROOTFS_DIR}/include/A.h; \
    fi \

    $(foreach name, $(SUB_DIRS),\
        ${MAKE} install -C $(name) || { echo $$?; echo "Make $(name) failed!"; exit 1; };\
    )

## 3.5.3. 如何添加模块到编译系统

如果要增加Kbuild的环境变量配置项，先确定要增加的环境变量的属性，再去对应文件中添加，添加环境变量配置项需要熟悉Kbuild语法。

如 hbre/otapackage，otapackage在package_Kconfig中通过 "source "hbre/otaupdate/Kconfig"" 导入Kconfig，Kconfig内容如下：

    menu "otaupdate" # 模块名，每个模块都有各自的menu

    config HB_PKG_OTAUPDATE      # 模块编译开关，前缀必须为HB_PKG_，后缀必须是上面menu名的大写，如果menu名中有"-"，在这里使用"_"代替
        depends on HB_COMP_HBRE  # 依赖hbre编译总开关，直接复制到这里就行
        select HB_PKG_LIBLOG     # 编译依赖模块，build系统会优先编译依赖模块，select + 依赖模块的编译开关名
        bool "Build package otaupdate"  # 显示menuconfig中的字段
        default y                       # 默认编译otaupdate

    config HB_PKG_OTAUPDATE_USE_REMOTE  # 不编译代码，从源拉包下来
        depends on HB_PKG_OTAUPDATE     # 依赖hbre编译总开关，直接复制到这里就行
        bool "Using remote packages"    # 显示menuconfig中的字段
        default y                       # 默认编译otaupdate

    config HB_PKG_OTAUPDATE_VERSION  # 模块deb包的版本
        depends on HB_PKG_OTAUPDATE  # 依赖当前模块的编译开关，如果不编译，这里就不显示
        string "deb version"         # 显示menuconfig中的字段
        default "1.0.1"              # 默认版本，三段式，和so的版本保持一致

    config HB_PKG_OTAUPDATE_DEV_DEB  # 编译dev-deb包的开关
        depends on HB_PKG_OTAUPDATE && !HB_PKG_OTAUPDATE_USE_REMOTE # 依赖当前模块的编译开关，如果不编译，这里就不显
        bool "dev-deb"

    config HB_PKG_OTAUPDATE_RUNTIME_DEB  # 编译runtime-deb包的开关
        depends on HB_PKG_OTAUPDATE && !HB_PKG_OTAUPDATE_USE_REMOTE # 依赖当前模块的编译开关，如果不编译，这里就不显
        bool "runtime-deb"
        default y

    config HB_PKG_OTAUPDATE_SRC_TAR  # 编译src源码包的开关
        depends on HB_PKG_OTAUPDATE && !HB_PKG_OTAUPDATE_USE_REMOTE # 依赖当前模块的编译开关，如果不编译，这里就不显
        bool "src-tar"

    config HB_PKG_OTAUPDATE_PATH  # 配置模块源码路径，变量名由此模块的编译开关名+"_PATH"组成
        depends on HB_PKG_OTAUPDATE && !HB_PKG_OTAUPDATE_USE_REMOTE # 依赖当前模块的编译开关，如果不编译，这里就不显示
        string "Path to package otaupdate" # 显示menuconfig中的字段
        default "hbre/otaupdate"  # 模块源码路径的默认值

    endmenu  # 对应 menu "otaupdate"

## 3.5.4. 服务、工具、库、头文件、配置安装说明

内容 | 安装路径
--- | ---
底软依赖的公共库 | /system
底软相关配置文件 | /usr/hobot/etc
底软自研/工具的服务、命令 | /usr/hobot/bin
底软自研库/工具链 | /usr/hobot/lib
Camera sensor驱动 | /usr/hobot/lib/sensor
底软头文件（dev库） | /usr/hobot/include

# 3.6. 包管理与根文件系统

## 3.6.1. 制作deb包并添加到build系统

如有对特殊包的需求，需通过deb包的形式接入到build系统。

对于动态库的开发包，deb包分为runtime包和dev包，runtime中不包含头文件和*.so，dev包中包含头文件、.so以及.a，但是没有.so的实体文件，dev包的需要依赖runtime包。

1. 运行包为runtime包，将会装在正式的rootfs或者其他自定义目录下，这个rootfs会被制作为system.img，板端使用的rootfs是这个。

2. 开发包为dev包，将会装在rootfs-dev，供开发者编译时使用头文件和库文件，rootfs-dev不会放到板端。

运行包runtime包的包名就是自身，一般无后缀；dev包的包名为”包名-dev”。

1. 文件安装位置

对于系统软件的开发者，文件安装应该遵循以下规则，以下为package_dir的目录结构：

    .
    ├── DEBIAN
    │   └── control    # deb 的control文件
    └── usr
        ├── bin
        │   └── tool   # 工具放在这
        ├── include    # dev包中头文件放在这
        └── lib
            └── aarch64-linux-gnu
                ├── libavcodec.a   # dev中静态库放在这
                ├── libavcodec.so -> libavcodec.so.58.91.100    # dev中创建软链接so
                ├── libavcodec.so.58 -> libavcodec.so.58.91.100 # soname 软连接
                ├── libavcodec.so.58.91.100                     # 库的实体文件，权限为644

2. control文件

    Package: hb-ffmpeg-dev
    Version: 4.3.6
    Architecture: arm64
    Maintainer: hobot
    Homepage: https://www.ffmpeg.org/releases/ffmpeg-4.3.6.tar.xz
    Description: Tools for transcoding, streaming and playing of multimedia files
    FFmpeg is the leading multimedia framework, able to decode, encode, transcode,
    mux, demux, stream, filter and play pretty much anything that humans and
    machines have created. It supports the most obscure ancient formats up to the
    cutting edge.  包的介绍
    .
    commit ID: 当前源码版本的 commitID
    toolchain：编译源码的编译器
    compile scripts: 编译源码的命令

3. 生成deb包

    dpkg-deb -b --root-owner-group ./path_to_package_dir/ .

备注

1. `path_to_package_dir` 目录下需包含 `DEBIAN/control` 以及要安装的文件。

4. 添加deb包到build

    ./prebuilts/deb_package/
    .
    ├── hbre_external
    │   ├── android-tools-adbd-runtime
    │   │   └── android-tools-adbd-runtime_5.1.1.r38-1.1_arm64.deb
    │   ├── hb-ffmpeg
    │   │   ├── hb-ffmpeg-dev_4.3.6_arm64.deb
    │   │   └── hb-ffmpeg-runtime_4.3.6_arm64.deb
    │   └── wayland-protocols-dev
    │       └── wayland-protocols-dev_1.31_arm64.deb
    ├── Kconfig
    └── toolchain
        └── aarch64-linux-hb-gcc_9.3_amd64.deb

将deb包放入指定位置

    ./prebuilts/deb_package/hbre_external/hb-ffmpeg/hb-ffmpeg-dev_4.3.6_arm64.deb
    ./prebuilts/deb_package/hbre_external/hb-ffmpeg/hb-ffmpeg-runtime_4.3.6_arm64.deb

增加kconfig

    # ./prebuilts/deb_package/Kconfig

    menu "hb-ffmpeg"

    config HB_PKG_HB_FFMPEG
        bool "Build package for hb-ffmpeg"
        default y

    config HB_PKG_HB_FFMPEG_USE_PREBUILT
        depends on HB_PKG_HB_FFMPEG
        bool "Using prebuilt packages"
        default y

    config HB_PKG_HB_FFMPEG_VERSION
        depends on HB_PKG_HB_FFMPEG
        string "deb version"
        default "4.3.6"

    endmenu

## 3.6.2. 添加工具到rootfs系统

可通过deb包的形式将相关工具安装到rootfs系统，不建议直接将包加入原生的rootfs。

## 3.6.3. 添加工具到initramfs系统

将需要的工具填写到 `./device/horizon/j6/rootfs_config_files/normal_ramfs_recipe_extern.json` 中。

    {
        "help": [
            "1. This section is help information, don't edit it",
            "2. Support adding existing scripts, binaries, and kernel modules",
            "from the normal rootfs to initramfs",
            "3. For binaries and kernel modules, their dependencies will be automatically added",
            "4. For kernel modules, fill in the ko name, such as mmc.ko, fill in mmc",
            "5. This recipe file type is \"initramfs\""
        ],
        "scripts": [
            "etc/init.d/by-name.sh",
            "etc/services"
        ],
        "binaries": [
            "usr/bin/rz",
            "usr/bin/sz",
            "usr/bin/ping",
            "usr/bin/ip",
            "usr/sbin/dropbear",
            "usr/bin/ssh",
            "usr/bin/scp",
            "usr/sbin/route",
            "usr/bin/mount",
            "usr/bin/umount",
            "usr/sbin/vconfig",
            "usr/sbin/modprobe",
            "usr/sbin/insmod",
            "usr/bin/tftp"
        ],
        "modules": [

        ]
    }

# 3.7. 如何使用 Build

## 3.7.1. 添加版本信息

编译时，为了方便追溯底软版本，可以指定版本信息，版本信息形式如下：

    # xxxxx release 20240422-180059

    ${TAG_NAME} ${HR_TARGET_MODE} $(date '+%Y%m%d-%H%M%S')

只需要在配置文件中对相关环境变量赋值即可，如在j6e_release_defconfig中赋值：

    export TAG_NAME=xxxxx

可在板端执行，使用如下命令查看：

    root@hobot:/# cat /etc/version

    xxxxx release 20240422-180059

## 3.7.2. 整编输出

目前由 build 通过 `bdall` 生成的文件位于： `out/release-gcc_12.2-64/target/product/img_packages` 。

## 3.7.3. OTA 包输出

目前由 build 通过 `bd otapackage` 生成的 ota 相关位于： `out/release-gcc_12.2-64/target/product/ota_packages` 。

文件名 | 说明
--- | ---
all_in_one.zip | 系统软件包
all_in_one.signature | 系统软件包签名
all_in_one_full.zip | 全镜像包
all_in_one_full.signature | 全镜像包签名

# 3.8. Build 常见问题

docker 权限问题

当执行docker编译时用ctrl-c停止或编译出错可能会导致文件权限问题，可使用docker环境执行任何命令恢复权限：

    bd sys chown -R 11135925 xxxxxx

其中，xxx为目录或文件名

备注

- 有特殊需求均可进入docker环境操作。