Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

入门

We recommend you developing the kernel in Debian-12.0+ or Ubuntu-24.04+ environment to get the best tooling support.

准备基本的开发环境

repo

我们使用repo来管理内核项目。请按照https://mirrors.tuna.tsinghua.edu.cn/help/git-repo/安装repo。

gn

我们使用GN来组织和构建BlueOS项目,而不是Rust生态系统的官方包管理器cargo。gn提供比cargo更好的多语言支持和更快的构建速度。你可以从https://gn.googlesource.com/gn/#getting-a-binary下载预构建的gn二进制文件。将下载的二进制文件放入一个目录,并确保该目录在你的${PATH}中。

在Linux上安装包

安装由发行版提供的软件包。

sudo apt install build-essential cmake ninja-build pkg-config \
                 libssl-dev gdb-multiarch curl git wget \
                 libslirp-dev python3 python3-pip meson \
                 libglib2.0-dev flex bison libfdt-dev \
                 gcc-riscv64-unknown-elf clang llvm lld \
                 python3-kconfiglib python3-tomli

此外,下载并安装arm toolchains

wget https://developer.arm.com/-/media/Files/downloads/gnu/14.3.rel1/binrel/arm-gnu-toolchain-14.3.rel1-x86_64-arm-none-eabi.tar.xz
tar xvf arm-gnu-toolchain-14.3.rel1-x86_64-arm-none-eabi.tar.xz -C <install-path>
wget https://developer.arm.com/-/media/Files/downloads/gnu/14.3.rel1/binrel/arm-gnu-toolchain-14.3.rel1-x86_64-aarch64-none-elf.tar.xz
tar xvf arm-gnu-toolchain-14.3.rel1-x86_64-aarch64-none-elf.tar.xz -C <install-path>

将 <install-path>/arm-gnu-toolchain-14.3.rel1-x86_64-aarch64-none-elf/bin 和 <install-path>/arm-gnu-toolchain-14.3.rel1-x86_64-arm-none-eabi/bin 添加到你的 $PATH 中。

Build and install QEMU on Linux

下载QEMU源代码tarball,

wget https://download.qemu.org/qemu-10.0.2.tar.xz
tar xvf qemu-10.0.2.tar.xz
cd qemu-10.0.2
mkdir build && cd build
../configure --prefix=<install-path> --enable-slirp && \
    make -j$(nproc) install

将 <install-path>/bin 添加到你的 $PATH 中。

在macOS上安装包

brew install coreutils llvm@19 lld@19 gcc-arm-embedded cmake ninja qemu
brew tap riscv-software-src/riscv
brew install riscv-tools riscv64-elf-gcc riscv64-elf-binutils riscv64-elf-gdb
python3 -m pip install --user --break-system-packages --upgrade kconfiglib

For aarch64 toolchain, please refer to arm-gnu-toolchain. It’s recommended to download tarballs rather than pkgs. For RISC-V toolchain on macOS, please refer to homebrew-riscv.

代码格式化工具

我们使用相应编程语言的代码格式化工具来保持代码风格一致。这些格式化工具可以通过

# 在Linux上sudo apt install clang-format yapf3
# 在 macOS 上brew install clang-format yapf

以下是格式命令及其对应编程语言的表格。

语言格式化命令
Rustrustfmt
C/C++clang-format
Pythonyapf3
GNgn format

初始化和同步项目

使用以下命令初始化项目。

mkdir blueos-dev
cd blueos-dev

If you have configured public ssh key on github, please use following commands

repo init -u git@github.com:vivoblueos/manifests.git -b main -m manifest.xml

otherwise, please try

repo init -u https://github.com/vivoblueos/manifests.git -b main -m manifest.xml

然后同步项目中的所有仓库。

repo sync

您可以通过在上面的命令后添加-j$(nproc)来加速同步。

Building the Rust toolchain for BlueOS kernel

We have forked upstream Rust compiler to support BlueOS kernel targeted to *-vivo-blueos-* and BlueOS kernel’s Rust-std.

我们最终将我们的更改贡献给上游仓库,并使*-vivo-blueos-*成为Rust的tier目标。

克隆下游仓库

Run If you have configured public ssh key on github, please use following commands,

git clone git@github.com:vivoblueos/rust.git  
git clone git@github.com:vivoblueos/cc-rs.git  
git clone git@github.com:vivoblueos/libc.git

otherwise, please try

git clone https://github.com/vivoblueos/rust.git  
git clone https://github.com/vivoblueos/cc-rs.git  
git clone https://github.com/vivoblueos/libc.git 

blueos-dev 分支已设置为默认,因此无需手动切换分支。

设置Rust镜像站点

在中国,我们推荐你使用 crates.io 和 rustup 的镜像站点。将以下行添加到你的 ~/.bashrc

export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static
export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup

然后输入

source ~/.bashrc

通过 x 脚本安装

在您的bash shell中运行以下命令。这些指令适用于Linux和macOS平台:

export CARGO_NET_GIT_FETCH_WITH_CLI=true
export DESTDIR=<选择您的安装前缀>
cd rust
cp config.blueos.toml config.toml
./x.py install -i --stage 1 compiler/rustc
./x.py install -i --stage 1 library/std --target aarch64-vivo-blueos-newlib
./x.py install -i --stage 1 library/std --target thumbv7m-vivo-blueos-newlibeabi
./x.py install -i --stage 1 library/std --target thumbv8m.main-vivo-blueos-newlibeabihf
./x.py install -i --stage 1 library/std --target riscv64-vivo-blueos
./x.py install -i --stage 1 library/std --target riscv32-vivo-blueos
./x.py install -i --stage 1 library/std --target riscv32imc-vivo-blueos
./x.py install -i --stage 0 rustfmt
./x.py install -i --stage 0 rust-analyzer
./x.py install -i --stage 0 clippy

您还必须安装主机的标准库和LLVM工具。

对于Linux:

./x.py install -i --stage 1 library/std --target x86_64-unknown-linux-gnu
cp -rav build/x86_64-unknown-linux-gnu/llvm/{bin,lib} ${DESTDIR}/usr/local

对于macOS:

./x.py install -i --stage 1 library/std --target aarch64-apple-darwin
cp -av build/aarch64-apple-darwin/llvm/{bin,lib} ${DESTDIR}/usr/local

要使用内核工具链,请将以下内容添加到您的环境中:

export PATH=${DESTDIR}/usr/local/bin:${PATH}

Or if you want to manage the blueos toolchain using rustup, you can try:

ln -s ${DESTDIR}/usr/local ~/.rustup/toolchains/blueos-dev
rustup default blueos-dev

构建内核镜像

内核目前支持多种板子。

  • qemu_mps2_an385
  • qemu_mps3_an547
  • qemu_virt64_aarch64
  • qemu_riscv64

为指定板(如 qemu_mps2_an385)构建内核镜像,使用命令

gn gen out/qemu_mps2_an385.release/ --args='build_type="release" board="qemu_mps2_an385"'
ninja -C out/qemu_mps2_an385.release

要运行测试,输入

ninja -C out/qemu_mps2_an385.release check_all

参数及其语义。

参数语义
构建类型构建使用的配置
开发板目标开发板名称

在VSCode中使用rust-analyzer

We recommend you to use rust-analyzer extension in VSCode to support development of BlueOS kernel.

然而,由于我们使用gn而非Cargo.toml来管理项目,VSCode中的rust-analyzer扩展可能无法开箱即用。你应该使用以下命令1来使扩展正常工作

gn gen out/qemu_mps2_an385 --export-rust-project
ln -sfn out/qemu_mps2_an385/rust-project.json

  1. https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/rust.md#使用-vscode ↩

QEMU检查器

大多数内核代码通过QEMU进行测试。我们引入QEMU检查器以协助测试内核。

QEMU检查器必须提供一个QEMU运行脚本和一个包含检查指令的输入文件。QEMU检查器运行该脚本并捕获其输出行。如果输出行匹配检查指令中指定的条件或正则表达式,检查器将采取相应操作。指令应写在输入文件的头部。

指令动作
// 检查-失败: <regex>记录此行并在退出时报告。
// 检查-成功: <regex>记录此行并在退出时报告。
// 断言失败:<regex>退出 runner 并报告失败。
// 断言-成功: <regex>退出 runner 并报告成功。
// 新建行超时: <数字>检查器读取下一行时超时。如果发生超时,报告失败。
// 总超时: <数字>本次运行的总超时时间。

例子

假设我们有一个用于测试的内核镜像,名为blueos_foo_test。首先我们需要为它生成一个qemu运行脚本。

gen_qemu_runner("runner_for_blueos_foo_test") {
  img = ":blueos_foo_test"
  qemu = "$qemu_exe"
  board = "$board"
}

然后,我们为上述runner制作一个checker。

run_qemu_checker("check_blueos_foo_test") {
  img = ":blueos_foo_test"
  runner = ":runner_for_blueos_foo_test"
  checker = "//kernel/kernel/tests/integration_test.rs"
}

必须指出,上述两个目标必须放在同一个 BUILD.gn 中。检查指令应放在 integration_test.rs 的头部。

// NEWLINE-TIMEOUT: 15  
// ASSERT-SUCC: Kernel test end.
// ASSERT-FAIL: Backtrace in Panic.*

在CI中集成检查

CI runner 仅运行两个顶层目标,default 和 check_all。要在 CI 期间运行检查,你必须将你的检查器目标放入顶层 BUILD.gn 中 check_all 组的 deps 里。

group("check_all") {
  deps = [ ":check_kernel" ]
}

运行覆盖率测试

我们在编译时通过添加-Cinstrument-coverage来生成覆盖率统计所需的代码,并通过集成minicov和semihosting来生成覆盖率数据。最后,我们使用grcov工具生成可读的HTML格式覆盖率数据。所有这些都已集成到我们的构建系统中,可以使用以下命令生成覆盖率数据:

gn gen out/qemu_riscv64.cov/ --args=build_type="coverage" board="qemu_riscv64"
ninja -C out/qemu_riscv64.cov check_coverage

如果你收到提示说grcov未找到,可以通过

cargo install grcov

构建并运行后,可以在 ./out/qemu_riscv64.cov/cov_report 目录中找到合并的覆盖率报告。打开该目录中的 index.html 文件以查看覆盖率数据。

Run shell in QEMU

We have implemented a simple shell to demonstrate the functionality of the BlueOS kernel. First let’s build the shell executable.

gn gen out/shell_test --args='board="qemu_mps2_an385" build_type="release"'
ninja -C out/shell_test shell_runner

The ninja will output a line at last, which is like

Generated /build/vivoblueos/out/shell_test/bin/shell_runner-qemu.sh

Now we can run the shell via the generated script. If everything is going smooth, we can see

Hello, shell!
>

Please type help for playing with this shell.

Add third party crates

We manage all third party crates in the https://github.com/vivoblueos/external repository. All crates are transformed into GN’s BUILD.gn files via gnrt which is initially created by the Chromium project.

Add dependency in //external/Cargo.toml

To add a new third party crate, for example unwinding, add following code in //external/Cargo.toml

[dependencies.unwinding]
version = "=0.2.4"
default-features = false
features = [
    "unwinder"
]

Then run ninja -C <out> run_gnrt. After the above two steps, developers can add //external/vendor/unwinding-0.2.4:unwinding to the deps field in the target defintions in BUILD.gn.

添加一个新的syscall

在内核中添加一个新的syscall很容易。

有3个步骤。

  1. Add new syscall number for the new syscall in header/src/lib.rs.
pub enum NR {
    Read,
}
  1. 在kernel/src/syscall_handlers/mod.rs中实现新的syscall。

这通常通过define_syscall_handler!完成,如果系统调用是简单的。例如

define_syscall_handler!(
read(fd: i32, buf: *mut i8, len: usize) -> c_long {
    // 实现你的 vfs 读取。});
  1. 在kernel/src/syscall_handlers/mod.rs中注册新的syscall。

在 syscall_table! 中添加一个新条目。例如,

(Read, read),

必须注意,第一个操作数必须是 NR 枚举的非限定名称。

调用一个syscall

BlueOS kernel offers two modes of invoking syscall.

  1. 软件中断

这通常见于大多数OS内核中,用于从用户空间切换到内核空间。

  1. 直接调用

在此模式下,syscall处理程序通过函数调用直接调用。

在两种模式下,如果需要调用syscall,请使用blueos_scal crate中的bk_syscall!宏。例如,

use blueos_scal::bk_syscall;
use blueos_header::syscalls::NR::{Read};

fn read_something(fd: i32, buf: *mut i8, len: usize) -> i32 {
    bk_syscall!(Read, buf, len) as i32
}

切换到另一种模式时无需更改代码。默认使用SWI模式。如需使用直接调用模式,只需在构建内核时传入--cfg direct_syscall_handler即可。

内核的基本数据类型

弧

为内核实现了一个定制的Arc(infra/src/tinyarc.rs)。与alloc::sync::Arc相比,差异不大,只是它仅有strong_count因而减小了体积,对嵌入式设备更友好。此外其内存布局对blueos_infra是已知的,因此我们的侵入式列表可以轻松与之协作。

侵入式列表

blueos_infra的ilist(infra/src/list/typed_ilist.rs)是类型化且不安全的。它类似于C风格的ilist,但我们建议开发者不要直接使用它,而是使用智能指针。我们在上述提到的Arc基础上实现了ArcList,并保证了安全性。

如何使用 blueos 仓库

与独立仓库之间的关系

此前, 项目按组件拆分为 kernel、build、libc、book 等多个独立 Git 仓库。 各仓库的地址、版本和本地目录由 manifests 仓库描述, 开发者通过 repo init 和 repo sync 在本地组成完整的源码工作区。 工作区中的每个组件仍是独立 Git 仓库, 分别维护自己的分支、提交历史和 PR。

但这带来了不便, 如果需要进行多个仓库的修改, 就需要在每个涉及到的仓库中, 都单独推送分支与PR, 这导致了审查与CI构建检查上的不便.

blueos 仓库将这些组件整合在一起, 其组件目录与先前由 repo init 构建的本地工作区一致, 并通过 Josh 保留导入的提交历史。

基本使用原则

  1. 对于尚未开始的工作, 通常直接从中心仓库的 main 创建分支即可.
  2. 对于已经在子仓库中开展的工作, 推荐将分支导入中心仓库, 并整合完整的中心历史后继续开发, 随后重新提交中心仓库分支与PR。

选择开发与 PR 入口

尽可能将同一项工作的开发分支和 PR 统一维护在中心仓库, 减少同时在中心仓库和子仓库维护同一分支。 如果原分支由多人共用, 应先协商迁移方式和后续开发入口。

工作情况建议方式
新工作, 仅依赖各仓库默认分支从完整的中心 main 开始开发。
已有子仓库分支或未合并 PR按本章流程导入, 再作为中心分支和 PR 发布。
依赖其他尚未合并的分支或 PR将当前工作和所需依赖整合到同一个中心分支。
同时涉及多个子仓库在中心仓库统一开发、验证和提交 PR。

仅修改 book 等不依赖其他仓库的内容时, 也可以直接在子仓库提交 PR。 存在未合并依赖时, 应将依赖一起导入和整合; 如果依赖已经是完整的中心分支, 可以直接使用 Git 合并或移植相关提交。 迁移已有 PR 后, 应及时关闭原 PR 并进行说明, 避免两边重复合并。

导入子仓库分支

安装工具

需要 Python 3、Git、josh 和 josh-filter。 以下安装命令会同时安装后两个程序:

cargo +stable install josh-cli --locked \
    --git https://github.com/josh-project/josh.git \
    --tag r26.07.19

安装时使用普通 Rust 工具链, 避免使用 blueos 定制的 Cargo。

配置远端并获取分支

Josh 远端将子仓库内容放到中心仓库对应目录中。 例如, :prefix=build 将子仓库根目录映射到中心仓库的 build/。

子仓库名称, 即脚本的 --repoJosh 远端过滤表达式
apps_example:prefix=apps/example
apps_shell:prefix=apps/shell
book:prefix=book
build:prefix=build
external:prefix=external
kernel:prefix=kernel
libc:prefix=libc
librs:prefix=librs

配置所需远端, 已配置过的远端无需重复添加:

完整模板见附录。

josh remote add fork-build \
    https://github.com/{{YOUR_USERNAME}}/build.git ':prefix=build'
josh remote add fork-kernel \
    https://github.com/{{YOUR_USERNAME}}/kernel.git ':prefix=kernel'

josh fetch --remote fork-build
josh fetch --remote fork-kernel
git --no-pager branch --list --all

这里的远端名 fork-build 和 fork-kernel 是本地自定义名称。 fetch 后可用 fork-build/branch-name 等远端跟踪引用选定导入的分支。 配置上游仓库时使用同样的方式, 只需更换远端名和 URL。

josh fetch 更新远端跟踪引用, 不移动当前本地开发分支, 也不会自动将子仓库分支整合到完整的中心历史。

将导入分支整合到完整 blueos 仓库

导入分支只包含对应子仓库的内容。 需要将它整合到包含所有组件的中心历史上, 再作为完整的中心分支使用并发布中心仓库 PR。

中心仓库的早期历史逐步导入了各个子仓库。 在 vivoblueos/blueos 中, 完成这些初始导入的提交是 a0d6549f6078a624dc513d4d2a34f5cf47e02f13。 通常选择中心 main, 或上一轮已经整合完成的中心分支作为起点。 整合后, 可以检查该导入完成提交是否是结果的祖先:

git merge-base --is-ancestor \
    a0d6549f6078a624dc513d4d2a34f5cf47e02f13 \
    branch-name

命令退出码为 0 表示祖先关系成立。 这个检查用于确认历史关系, 整合后的文件和构建仍需按修改内容进行验证。

可以先在子仓库实验对分支的 merge 或 rebase 更新。 中心仓库可能包含尚未同步回子仓库的修改, 因此中心整合时仍需处理冲突并验证结果。

使用 rebase 更新导入分支

使用 Git rebase 将导入分支的修改移植到中心仓库的 main 上。 示例假定被 rebase 排除的子仓库 main 基线修改, 已经包含在中心仓库的整合起点中。

# 将 build 的 miri/allocator 分支移植到完整中心历史。
git switch -c import/build-miri-rebased fork-build/miri/allocator
git rebase --onto main fork-build/main
# 该结果现在可以作为完整的中心分支使用。

# 在上一轮结果上叠加 kernel 的业务修改。
git switch -c blueos/miri/allocator-rebased fork-kernel/miri/allocator
git rebase --onto import/build-miri-rebased fork-kernel/main

git push origin blueos/miri/allocator-rebased

使用 merge 更新导入分支

直接把josh导入的分支 merge 到中心 main, 会将导入分支的完整历史一起带入。 已经同步到中心仓库的修改, 在不同转换下可能具有不同 SHA, 因而仍可能作为额外提交出现在分支和 PR 中。

可以使用 merge-subtree-branches.py 脚本来处理这个问题. 每次调用时会使用merge方式更新一个导入分支, 可以依次导入多个分支:

# 以中心仓库 main 为起点导入 build 的 miri/allocator 分支。
python3 tools/josh/merge-subtree-branches.py \
    --repo build \
    --source-remote fork-build \
    --source-branch miri/allocator \
    --target-base main \
    --target-branch blueos/miri/allocator

python3 tools/josh/merge-subtree-branches.py \
    --repo kernel \
    --source-remote fork-kernel \
    --source-branch miri/allocator \
    --target-base blueos/miri/allocator \
    --target-branch blueos/miri/allocator
参数含义
--repo子仓库名称, 决定中心目录, 必须与远端的 prefix 对应。
--source-remote已配置的 Josh 远端名。
--source-branch子仓库中需要导入的分支名。
--target-base完整中心历史中的本轮起点, 默认 main。
--target-branch中心仓库中要创建或追加 merge 的本地分支名。
--repo-root中心仓库路径, 默认脚本所在仓库。

--target-base 支持分支名、标签、完整或唯一缩写 SHA, 以及 HEAD~1 等 Git 提交表达式;两个 branch 参数只接受分支名。 脚本在 merge 前固定起点, 因此 --target-base 可以与目标分支同名。

目标分支不存在时, 脚本从起点创建分支; 已经存在时追加 merge, 起点必须是目标分支的祖先或自身。 脚本保留目标分支中已有的提交, 不会重置分支。

运行前提交已跟踪文件的修改, 或用 git stash 保存。 脚本拒绝已跟踪文件的未提交修改, 以及正在进行的 merge、rebase 等操作。 它在当前工作区切换分支并合并, 成功后停留在目标分支。

将修改推回子仓库分支

如果后续只在中心仓库维护分支, 可以跳过这一节。 需要继续维护子仓库业务分支时, 使用 push-subtree-branch.py 导出对应目录。

# 选择 --source-base(排除) 和 --source-rev(包含) 之间的提交。
#
# 添加 --push 才会实际推送, 非快进更新需要 --force。
python3 tools/josh/push-subtree-branch.py \
    --source-base main \
    --source-rev blueos/miri/allocator \
    --repo build \
    --target-remote fork-build \
    --target-base main \
    --target-branch miri/allocator \
    --force

选择导出范围和子仓库基线

用两个中心提交参数指定范围:

  • --source-base:业务修改之前的中心基线, 排除该提交及其祖先。
  • --source-rev:最终中心分支头, 包含该提交。

范围是 Git 的 SOURCE_BASE..SOURCE_REV。 对于 merge 分支, 它也包含分支头可达、但基线不可达的其他合入提交, 因此线性分支和 merge 分支使用同一种表达方式。 每次只过滤一个目录, 在最终分支上继续开发的相关修改也会包含在导出结果中。

两个中心参数支持分支名、标签、完整或唯一缩写 SHA, 以及 HEAD、HEAD~1 等提交表达式, 运行开始时固定为具体 SHA。 --source-base 必须是 --source-rev 的祖先或自身; 两者相同表示空范围。

--target-base 指定子仓库中承接这些修改的基线分支, 通常是其 PR 目标分支。 它与 --source-base 必须一起使用。 中心基线在所选目录中的文件, 必须与子仓库基线的文件一致。 两个基线不需要同名, 脚本也不会自动选择或保存中心基线。

脚本将过滤后的范围重放到子仓库基线上, 再用所得结果更新 --target-branch。脚本只推送该目标分支; 当它与 --target-base 不同时, 基线分支不会被修改。 范围模式会展平过滤后的 merge。 发生重放冲突或最终文件与源目录不一致时, 脚本返回错误, 不会实际推送。

预演和推送

先预演 build 的回推结果:

python3 tools/josh/push-subtree-branch.py \
    --source-base main \
    --source-rev blueos/miri/allocator \
    --repo build \
    --target-remote fork-build \
    --target-base main \
    --target-branch miri/allocator \
    --force

从同一个最终中心分支回推 kernel, 只需更换目录和远端:

python3 tools/josh/push-subtree-branch.py \
    --source-base main \
    --source-rev blueos/miri/allocator \
    --repo kernel \
    --target-remote fork-kernel \
    --target-base main \
    --target-branch miri/allocator \
    --force
参数含义
--source-base排除的中心基线;与 --target-base 配对。
--source-rev最终中心提交, 包含该提交;必填。
--repo子仓库名称, 目录映射与 merge 脚本相同。
--target-remote已配置的目标 Josh 远端名。
--target-base子仓库承接导出范围的基线分支名。
--target-branch子仓库中要更新或创建的分支名。
--repo-root中心仓库路径, 默认脚本所在仓库。
--force 或 -f使用 Josh 普通强推, 允许非快进更新。
--push预演成功后实际推送;省略时只预演。

回推脚本的 --target-base 和 --target-branch 只接受子仓库分支名。 默认只预演, 也不强制推送;添加 --force 仍然只是预演。 历史重写后需要非快进更新时使用 --force.

输出中:

  • BEFORE (current remote) 是本次 fetch 得到的更新前子分支。
  • AFTER (proposed, not pushed) 是尚未推送的候选结果。

检查预演结果后, 在同一条命令后添加 --push。 例如, 实际强推 build 分支:

python3 tools/josh/push-subtree-branch.py \
    --source-base main \
    --source-rev blueos/miri/allocator \
    --repo build \
    --target-remote fork-build \
    --target-base main \
    --target-branch miri/allocator \
    --force --push

只有实际推送成功后才会显示 Push completed。 脚本仅处理已提交内容, 不移动中心开发分支, 也不修改当前工作区。 用于重放的 detached 临时工作区会自动清理, 不创建临时分支。 过滤结果保存在 refs/export/<remote>/<target-branch>, 范围模式还使用 refs/export-bases/<remote>/<target-branch>; 这些辅助引用每次重新生成, 与导入脚本没有回推进度联动。

示例使用 merge 流程生成的 blueos/miri/allocator。 如果采用 rebase 流程, 将 --source-rev 改为 blueos/miri/allocator-rebased。

导出完整历史

需要过滤最终提交的完整可达历史时, 省略两个基线参数, 只指定 --source-rev:

python3 tools/josh/push-subtree-branch.py \
    --source-rev blueos/miri/allocator \
    --repo build \
    --target-remote fork-build \
    --target-branch miri/allocator

此模式可能保留中心导入 merge 和旧同步提交。 希望排除业务修改之前的历史时, 应使用明确基线的范围模式。 SHA 只是选定一个提交, 不能单独表达业务修改的起止边界。

从子仓库获取更新

josh fetch 更新子仓库的远端跟踪引用, 不移动本地开发分支。 如果分支已经迁移并只在中心仓库维护, 按普通中心仓库分支继续开发即可。 仍需获取子仓库业务分支更新时, 要区分下面两种情况。

远端正常快进

对于仍沿用josh导入历史的分支, 可以按普通 Git 方式合并新的远端跟踪引用。

对于已经整合完整中心历史的开发分支, 应先重新映射或移植更新, 避免直接 merge 原始导入引用而带回旧历史。 采用 merge 流程时, 可以以当前中心分支为起点继续调用导入脚本:

python3 tools/josh/merge-subtree-branches.py \
    --repo build \
    --source-remote fork-build \
    --source-branch miri/allocator \
    --target-base blueos/miri/allocator \
    --target-branch blueos/miri/allocator

脚本会自行 fetch, 再映射和合并来源。 采用 rebase 流程时, 需明确上次已导入的子仓库提交, 将它之后的新修改移植到当前中心分支;当前子仓库 main 不一定就是这个边界。

远端发生 rebase 或强推

追加 merge 会保留中心分支中原有的旧版本历史。 如果需要移除远端已经删除或改写的旧提交, 应从完整中心基线重新整合最新子仓库分支, 再重放需要保留的本地新增修改和其他子仓库的业务修改。

重建时应保留原分支供对照, 并核对提交范围、最终文件和构建结果。 这种更新仍涉及子仓库历史到完整中心历史的转换, 不能只根据 fetch 是否成功判断。

两个脚本的完整参数说明和辅助引用行为见 辅助分支操作工具。

附录

导入子仓库分支

cd blueos
josh remote add upstream-apps_example \
    https://github.com/vivoblueos/apps_example.git ':prefix=apps/example'
josh remote add upstream-apps_shell \
    https://github.com/vivoblueos/apps_shell.git ':prefix=apps/shell'
josh remote add upstream-book \
    https://github.com/vivoblueos/book.git ':prefix=book'
josh remote add upstream-build \
    https://github.com/vivoblueos/build.git ':prefix=build'
josh remote add upstream-external \
    https://github.com/vivoblueos/external.git ':prefix=external'
josh remote add upstream-kernel \
    https://github.com/vivoblueos/kernel.git ':prefix=kernel'
josh remote add upstream-libc \
    https://github.com/vivoblueos/libc.git ':prefix=libc'
josh remote add upstream-librs \
    https://github.com/vivoblueos/librs.git ':prefix=librs'

josh fetch --remote upstream-apps_example
josh fetch --remote upstream-apps_shell
josh fetch --remote upstream-book
josh fetch --remote upstream-build
josh fetch --remote upstream-external
josh fetch --remote upstream-kernel
josh fetch --remote upstream-libc
josh fetch --remote upstream-librs

josh remote add fork-apps_example \
    https://github.com/{{YOUR_USERNAME}}/apps_example.git ':prefix=apps/example'
josh remote add fork-apps_shell \
    https://github.com/{{YOUR_USERNAME}}/apps_shell.git ':prefix=apps/shell'
josh remote add fork-book \
    https://github.com/{{YOUR_USERNAME}}/book.git ':prefix=book'
josh remote add fork-build \
    https://github.com/{{YOUR_USERNAME}}/build.git ':prefix=build'
josh remote add fork-external \
    https://github.com/{{YOUR_USERNAME}}/external.git ':prefix=external'
josh remote add fork-kernel \
    https://github.com/{{YOUR_USERNAME}}/kernel.git ':prefix=kernel'
josh remote add fork-libc \
    https://github.com/{{YOUR_USERNAME}}/libc.git ':prefix=libc'
josh remote add fork-librs \
    https://github.com/{{YOUR_USERNAME}}/librs.git ':prefix=librs'

josh fetch --remote fork-apps_example
josh fetch --remote fork-apps_shell
josh fetch --remote fork-book
josh fetch --remote fork-build
josh fetch --remote fork-external
josh fetch --remote fork-kernel
josh fetch --remote fork-libc
josh fetch --remote fork-librs
git --no-pager branch --list --all

提交 PR

新工作

fork vivoblueos/blueos, 然后按常规 GitHub 流程提交 PR。

已有子仓库 PR

对于已有子仓库分支或尚未合并的 PR, 参考如何使用 blueos 仓库导入分支并整合完整的中心历史, 然后继续开发并提交中心仓库 PR。尚未合并的依赖也应整合到同一个中心分支中。

发布替代的中心仓库 PR 后, 关闭原子仓库 PR 并附上新 PR 的链接, 避免同一项修改被重复合并。如果原分支由多人共用, 应先协商迁移方式和后续开发入口。