背景

完成了openharmony5.0对龙芯2k300小型系统的适配,进入系统发现可用的命令很少,例如(ip、stty等命令都不可用)。原因是开鸿用的是toybox,而toybox相较于busybox更轻便、简洁,维护也方便,但是命令也少,所以,笔者萌发了集成busybox的想法。
注:busybox采用 ‌GPLv2‌ 协议,要求衍生作品开源。本文移植busybox只为自行使用和分享移植方法,无任何商业用途。

下载busybox源码

下载链接:busybox download
笔者下载的当前最新版1.37.0版本
下载完成后,将压缩包解压至third_party/目录下,并重命名为busybox

# your_openharmony_root_dir 为读者的开鸿项目根目录
tar -zjf busybox-1.37.0.tar.bz2 -C your_openharmony_root_dir/third_paty/
cd your_openharmony_root_dir/third_paty/
mv busybox-1.37* busybox

为busybox创建编译脚本

busybox官方构建方式是类linux的编译构建模式,主要通过make命令来完成编译构建,而开鸿使用的是gn+ninja的编译构建模式,所以,笔者采用python脚本传递当前项目的交叉编译工具链,让python脚本来拉起busybox自身的编译系统的方式

cd your_openharmony_root_dir/third_paty/busybox/
vi build_busybox.py
# build_busybox.py文件内容如下,读者可依据自己需求,按开鸿的构建规则修改

build_busybox.py

#!/usr/bin/env python3
# Copyright (c) 2026

import argparse
import os
import pathlib
import shutil
import subprocess
import sys


def run(cmd, cwd, env):
    print("[busybox-build] run:", " ".join(cmd))
    subprocess.run(cmd, cwd=cwd, env=env, check=True)


def list_source_newfiles(src_dir):
    tracked = set()
    git_dir = src_dir / ".git"
    if git_dir.exists():
        out = subprocess.check_output(
            ["git", "ls-files", "--others", "--exclude-standard", "--cached"],
            cwd=src_dir,
            text=True,
        )
        for line in out.splitlines():
            line = line.strip()
            if line:
                tracked.add(line)
    return tracked


def disable_config_flags(config_file, disabled_flags):
    if not config_file.exists():
        raise FileNotFoundError(f"config file not found: {config_file}")

    lines = config_file.read_text(encoding="utf-8").splitlines()
    out = []
    seen = set()
    for line in lines:
        replaced = False
        for flag in disabled_flags:
            if line.startswith(f"{flag}=") or line == f"# {flag} is not set":
                out.append(f"# {flag} is not set")
                seen.add(flag)
                replaced = True
                break
        if not replaced:
            out.append(line)
    for flag in disabled_flags:
        if flag not in seen:
            out.append(f"# {flag} is not set")
    config_file.write_text("\n".join(out) + "\n", encoding="utf-8")


def _parse_busybox_links_file(links_file):
    """Return absolute POSIX paths (leading '/') from busybox.links."""
    if not links_file.exists():
        raise FileNotFoundError(f"busybox.links not found: {links_file}")
    paths = []
    for raw in links_file.read_text(encoding="utf-8").splitlines():
        line = raw.strip()
        if not line or line.startswith("#"):
            continue
        path = line if line.startswith("/") else f"/{line}"
        paths.append(path)
    return paths


def _busybox_link_path_to_usr_relpath(path_posix):
    """
    Map BusyBox link path (as in busybox.links, e.g. /bin/ls) to a path
    relative to product root_out_dir using merged-/usr layout:
    /bin, /usr/bin -> usr/bin/... ; /sbin, /usr/sbin -> usr/sbin/...
    """
    p = path_posix.strip()
    if not p.startswith("/"):
        p = f"/{p}"
    rel = p.lstrip("/")
    if not rel or ".." in pathlib.PurePosixPath(rel).parts:
        return None
    if rel.startswith("usr/bin/"):
        return pathlib.Path("usr/bin") / rel[len("usr/bin/"):]
    if rel.startswith("usr/sbin/"):
        return pathlib.Path("usr/sbin") / rel[len("usr/sbin/"):]
    if rel.startswith("bin/"):
        return pathlib.Path("usr/bin") / rel[len("bin/"):]
    if rel.startswith("sbin/"):
        return pathlib.Path("usr/sbin") / rel[len("sbin/"):]
    # LINK/ROOT/... entries produce a single path segment (e.g. /foo).
    if "/" not in rel:
        return pathlib.Path("usr/bin") / rel
    return None


def _install_busybox_applet_symlinks(root_out_dir, busybox_bin, links_file):
    """
    Create applet symlinks under <root_out>/usr/bin and <root_out>/usr/sbin
    (matches fs.yml source_dir usr/bin and usr/sbin).

    The cross-built busybox binary must not be executed on the host; use
    busybox.links from the object tree instead of ``busybox --list``.
    """
    root_out = root_out_dir.resolve()
    busybox_bin = busybox_bin.resolve()
    for path_posix in sorted(set(_parse_busybox_links_file(links_file))):
        rel_under_out = _busybox_link_path_to_usr_relpath(path_posix)
        if rel_under_out is None:
            continue
        link_path = (root_out / rel_under_out).resolve()
        try:
            link_path.relative_to(root_out)
        except ValueError:
            continue

        link_path.parent.mkdir(parents=True, exist_ok=True)
        target = os.path.relpath(busybox_bin, start=link_path.parent)
        if link_path.exists() or link_path.is_symlink():
            if link_path.is_symlink() and os.readlink(link_path) == target:
                continue
            link_path.unlink()
        os.symlink(target, link_path)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--src-dir", required=True)
    parser.add_argument("--work-dir", required=True)
    parser.add_argument("--root-out-dir", required=True)
    parser.add_argument("--clang-base", required=True)
    parser.add_argument("--abi-target", required=True)
    parser.add_argument("--sysroot", required=True)
    parser.add_argument("--jobs", type=int, default=0)
    args = parser.parse_args()

    src_dir = pathlib.Path(args.src_dir).resolve()
    work_dir = pathlib.Path(args.work_dir).resolve()
    build_dir = work_dir / "out"
    install_dir = work_dir / "install"
    root_out_dir = pathlib.Path(args.root_out_dir).resolve()
    stamp_file = work_dir / ".stamp"

    build_dir.mkdir(parents=True, exist_ok=True)
    install_dir.mkdir(parents=True, exist_ok=True)

    # GN passes relative paths rebased to root_build_dir (e.g. out/.../loongsmall).
    # Resolve those relative paths against current working directory to get
    # stable absolute paths.
    root_build_dir = pathlib.Path.cwd().resolve()

    clang_base = pathlib.Path(args.clang_base)
    if not clang_base.is_absolute():
        clang_base = (root_build_dir / clang_base).resolve()

    sysroot = pathlib.Path(args.sysroot)
    if not sysroot.is_absolute():
        sysroot = (root_build_dir / sysroot).resolve()

    cc_path = (clang_base / "bin" / "clang").resolve()
    cxx_path = (clang_base / "bin" / "clang++").resolve()
    if not cc_path.exists():
        raise FileNotFoundError(f"clang not found: {cc_path}")
    if not cxx_path.exists():
        raise FileNotFoundError(f"clang++ not found: {cxx_path}")

    cc_bin = str(cc_path)
    cxx_bin = str(cxx_path)
    ar = f"{args.clang_base}/bin/llvm-ar"
    nm = f"{args.clang_base}/bin/llvm-nm"
    ar = str((clang_base / "bin" / "llvm-ar").resolve())
    nm = str((clang_base / "bin" / "llvm-nm").resolve())
    strip_bin = str((clang_base / "bin" / "llvm-strip").resolve())
    objcopy_bin = str((clang_base / "bin" / "llvm-objcopy").resolve())
    hostcc = shutil.which("cc") or shutil.which("gcc") or "cc"

    env = os.environ.copy()
    # Isolate busybox build from outer build system flags (sanitizers, -Werror, etc).
    # busybox configuration controls its own CFLAGS/LDFLAGS via .config.
    for k in [
        "CFLAGS",
        "CPPFLAGS",
        "CXXFLAGS",
        "LDFLAGS",
        "ASFLAGS",
        "LDLIBS",
        "LIBS",
        "ARFLAGS",
        "RUSTFLAGS",
        "EXTRA_CFLAGS",
        "EXTRA_LDFLAGS",
        "SANITIZE_CFLAGS",
        "SANITIZE_LDFLAGS",
    ]:
        env.pop(k, None)
    target_flags = f"--target={args.abi_target} --sysroot={sysroot}"
    include_flags = f"-isystem {sysroot}/usr/include/{args.abi_target}"
    env["CC"] = cc_bin
    env["CXX"] = cxx_bin
    env["AR"] = ar
    env["NM"] = nm
    env["STRIP"] = strip_bin
    env["LD"] = cc_bin
    env["HOSTCC"] = hostcc
    env["KCONFIG_NOTIMESTAMP"] = "1"
    env["KCONFIG_NOSILENTUPDATE"] = "1"
    env["KBUILD_OUTPUT"] = str(build_dir)
    env["KCONFIG_CONFIG"] = str(build_dir / ".config")
    # Busybox allyesconfig enables many legacy applets; keep build non-blocking by
    # demoting selected warnings from errors in busybox only.
    env["EXTRA_CFLAGS"] = (
        "-Wno-error=deprecated-declarations -Wno-deprecated-declarations "
        "-Wno-error=format-truncation -Wno-format-truncation "
        "-Wno-error=format-overflow -Wno-format-overflow "
        "-Wno-error=string-plus-int -Wno-string-plus-int "
        "-Wno-error=unused-but-set-variable -Wno-unused-but-set-variable "
        "-Wno-error=int-in-bool-context -Wno-int-in-bool-context "
        "-Wno-error=misleading-indentation -Wno-misleading-indentation "
        "-Wno-error=pointer-bool-conversion -Wno-pointer-bool-conversion "
        "-Wno-error=incompatible-pointer-types-discards-qualifiers "
        "-Wno-incompatible-pointer-types-discards-qualifiers "
        "-Wno-error=self-assign -Wno-self-assign "
        "-D__MUSL__ "
        "-Wno-unknown-warning-option"
    )
    env["EXTRA_CFLAGS"] = f"{target_flags} {include_flags} " + env["EXTRA_CFLAGS"]
    env["EXTRA_LDFLAGS"] = target_flags

    before = list_source_newfiles(src_dir)

    # Busybox Makefile uses STRIP in an objcopy-like way:
    #   $(STRIP) -s --remove-section=... input -o output
    # llvm-objcopy expects: llvm-objcopy [options] input [output]
    # so provide a wrapper that translates -s and -o.
    strip_wrapper = work_dir / "strip.sh"
    strip_wrapper.write_text(
        "#!/bin/sh\n"
        "LLVM_OBJCOPY=\"$1\"\n"
        "shift\n"
        "out=\"\"\n"
        "in=\"\"\n"
        "args=\"\"\n"
        "while [ \"$#\" -gt 0 ]; do\n"
        "  a=\"$1\"\n"
        "  shift\n"
        "  if [ \"$a\" = \"-s\" ]; then\n"
        "    args=\"$args --strip-all\"\n"
        "    continue\n"
        "  fi\n"
        "  if [ \"$a\" = \"-o\" ]; then\n"
        "    out=\"$1\"\n"
        "    shift\n"
        "    continue\n"
        "  fi\n"
        "  case \"$a\" in\n"
        "    --*) args=\"$args '$a'\";;\n"
        "    -*)  args=\"$args '$a'\";;\n"
        "    *)   if [ -z \"$in\" ]; then in=\"$a\"; else args=\"$args '$a'\"; fi;;\n"
        "  esac\n"
        "done\n"
        "if [ -z \"$in\" ]; then\n"
        "  echo \"strip wrapper: missing input\" >&2\n"
        "  exit 2\n"
        "fi\n"
        "cmd=\"$LLVM_OBJCOPY $args '$in'\"\n"
        "if [ -n \"$out\" ]; then\n"
        "  cmd=\"$cmd '$out'\"\n"
        "fi\n"
        "eval \"$cmd\"\n",
        encoding="utf-8",
    )
    os.chmod(strip_wrapper, 0o755)

    make_vars = [
        f"CC={cc_bin}",
        f"CXX={cxx_bin}",
        f"LD={cc_bin}",
        f"AR={ar}",
        f"NM={nm}",
        f"STRIP={strip_wrapper} {objcopy_bin}",
        f"HOSTCC={hostcc}",
    ]

    run(["make", f"O={build_dir}", *make_vars, "distclean"], cwd=src_dir, env=env)
    run(["make", f"O={build_dir}", *make_vars, "allyesconfig"], cwd=src_dir, env=env)
    disable_config_flags(
        build_dir / ".config",
        [
            "CONFIG_INETD",
            "CONFIG_FEATURE_INETD_RPC",
            "CONFIG_PAM",
            "CONFIG_DEBUG_SANITIZE",
            # musl sysroot doesn't provide glibc GNU regex symbols used by these applets.
            "CONFIG_GREP",
            "CONFIG_EGREP",
            "CONFIG_FGREP",
            # Keep vi enabled, but turn off regex mode to avoid GNU regex dependency.
            "CONFIG_FEATURE_VI_REGEX_SEARCH",
            "CONFIG_SELINUX",
            "FEATURE_SELINUX",
            "CONFIG_FEATURE_SELINUX",
            "CONFIG_CHCON",
            "CONFIG_GETENFORCE",
            "CONFIG_GETSEBOOL",
            "CONFIG_LOAD_POLICY",
            "CONFIG_MATCHPATHCON",
            "CONFIG_RUNCON",
            "CONFIG_SELINUXENABLED",
            "CONFIG_SESTATUS",
            "CONFIG_SETENFORCE",
            "CONFIG_SETFILES",
            "CONFIG_FEATURE_SETFILES_CHECK_OPTION",
            "CONFIG_RESTORECON",
            "CONFIG_SETSEBOOL",
            "CONFIG_FEATURE_MOUNT_NFS",
            # Avoid -static, which is incompatible with ASan/UBSan in many toolchains.
            "CONFIG_STATIC",
        ],
    )
    jobs = args.jobs if args.jobs > 0 else (os.cpu_count() or 8)
    run(["make", f"O={build_dir}", *make_vars, f"-j{jobs}"], cwd=src_dir, env=env)
    run(
        [
            "make",
            f"O={build_dir}",
            *make_vars,
            f"CONFIG_PREFIX={install_dir}",
            "install",
        ],
        cwd=src_dir,
        env=env,
    )

    after = list_source_newfiles(src_dir)
    leaked = sorted(after - before)
    if leaked:
        raise RuntimeError(
            "build artifact leaked into source dir: " + ", ".join(leaked[:20])
        )

    busybox_bin = install_dir / "bin" / "busybox"
    if not busybox_bin.exists():
        raise FileNotFoundError(f"busybox binary not found: {busybox_bin}")

    usr_bin = root_out_dir / "usr" / "bin"
    usr_bin.mkdir(parents=True, exist_ok=True)

    target_busybox = usr_bin / "busybox"
    if target_busybox.exists() or target_busybox.is_symlink():
        target_busybox.unlink()
    shutil.copy2(busybox_bin, target_busybox)

    links_file = build_dir / "busybox.links"
    _install_busybox_applet_symlinks(root_out_dir, target_busybox, links_file)

    stamp_file.write_text("busybox build done\n", encoding="utf-8")
    return 0


if __name__ == "__main__":
    sys.exit(main())

为busybox组件创建BUILD.gn和bundle.json文件

cd your_openharmony_root_dir/third_paty/busybox/
vi BUILD.gn
vi bundle.json
# BUILD.gn和bundle.json文件内容如下,读者可依据自己需求,按开鸿的构建规则修改

BUILD.gn

import("//build/config/clang/clang.gni")
import("//build/config/ohos/config.gni")
import("//build/ohos.gni")
import("//build/config/ohos/musl.gni")

_busybox_gen_dir = "$root_gen_dir/third_party/busybox"

action("busybox_install") {
  script = "build_busybox.py"
  outputs = [
    "$_busybox_gen_dir/.stamp",
    "$_busybox_gen_dir/install/bin/busybox",
  ]
  args = [
    "--src-dir",
    rebase_path(".", root_build_dir),
    "--work-dir",
    rebase_path(_busybox_gen_dir, root_build_dir),
    "--root-out-dir",
    rebase_path("$root_out_dir", root_build_dir),
    "--clang-base",
    rebase_path(clang_base_path, root_build_dir),
    "--abi-target",
    abi_target,
    "--sysroot",
    # Use product sysroot (contains usr/include/<abi>/... like byteswap.h).
    rebase_path("$root_out_dir/sysroot", root_build_dir),
  ]
}

ohos_prebuilt_executable("busybox") {
  source = "$_busybox_gen_dir/install/bin/busybox"
  deps = [ ":busybox_install" ]
  subsystem_name = "thirdparty"
  part_name = "busybox"
  # Install paths are generated by build_busybox.py under $root_out_dir/usr/{bin,sbin}
  # for fs.yml (top-level usr/bin, usr/sbin). GN's system image layout is system/bin,
  # so disable default prebuilt install to avoid a duplicate under system/.
  install_images = [
    "system",
    "ramdisk",
    "updater",
  ]
  install_enable = false
}

bundle.json

{
    "name": "@ohos/busybox",
    "description": "busybox",
    "version": "1.0",
    "license": "Public Domain",
    "publishAs": "code-segment",
    "segment": {
        "destPath": "third_party/busybox"
    },
    "dirs": {},
    "scripts": {},
    "licensePath": "",
    "readmePath": {
        "en": ""
    },
    "component": {
        "name": "busybox",
        "subsystem": "thirdparty",
        "syscap": [],
        "features": [],
        "adapted_system_type": [
            "standard",
            "small",
            "mini"
        ],
        "rom": "73KB",
        "ram": "146KB",
        "deps": {
            "components": [
            ],
            "third_party": []
        },
        "build": {
            "sub_component": [
                "//third_party/busybox:busybox"
            ],
            "inner_kits": [
                {
                    "name" : "//third_party/busybox:busybox"
                }
            ],
            "test": [
            ]
        }
    }
}

在产品配置中添加busybox组件

cd your_openharmony_root_dir/
vi vendor/company/borad_name/config.json # eg. vi vendor/hisilicon/hispark_taurus_linux/config.json

找到config.json文件中的third_party子系统配置部分"subsystem": "thirdparty",添加busybox组件{ "component": "busybox", "features": []}
若config.json文件中没有third_party子系统的配置就自行添加
示例:

    {
      "subsystem": "thirdparty",
      "components": [
        { "component": "busybox", "features": []}
      ]
    },

编译构建

完成这些后,按照开鸿官方的构建方法编译构建即可,笔者使用的是build,sh脚本来构建,hb构建工具在笔者环境中不能正常使用,就没有尝试

cd your_openharmony_root_dir/
./build.sh --product-name xxx --ccache # xxx 为config.json中product_name键的值,即读者要编译构建的目标

结束

编译构建完成后,out目录下找到对应的rootfs目录(这个目录就是项目编译出来的实际的文件系统),在rootfs目录下执行

ls usr/bin
# 正常情况会看到busybox这个可执行文件和一些命令的符号链接

然后就可以烧录到板卡上去验证了,笔者当前是可以正常使用的
若大家有更好的移植方法或文章有误的地方,欢迎大家在评论区指出

Logo

更多推荐