bitbake 是典型的 C/S 架构,bitbake 客户端与 bitbake-server 之间通过 unix socket 进行通信,bitbake-server 没有提供简单的方式在前台运行(通过 bitbake --server-only
或者 bitbake 客户端启动的 bitbake-server 都运行在后台),因此调试运行(或者前台运行)并不是很方便。
standalone bitbake server
根据 bitbake-server 代码(bitbake/bin/bitbake-server),可以通过自己实现 bitbake-server 逻辑进行规避:
https://github.com/runsisi/obmc-utils/blob/master/obmc-server.py
#!/usr/bin/python3
# coding: utf-8
import argparse
import os
import sys
import logging
class Tee:
def __init__(self, *files):
self._files = files
def __getattr__(self, attr, *args):
return self._wrap(attr, *args)
def _wrap(self, attr, *args):
def g(*a, **kw):
for f in self._files:
r = getattr(f, attr, *args)(*a, **kw)
return r
return g
def parse_args():
parser = argparse.ArgumentParser('obmc-server')
parser.add_argument(
'-b', '--build',
dest='build',
required=True,
help='openbmc build dir'
)
return parser.parse_args()
def run(build):
libpath = os.path.abspath(os.path.join(build, '../../bitbake/lib'))
sys.path.insert(0, libpath)
import bb.event
import bb.utils
import bb.server.process
bb.utils.check_system_locale()
logfile = os.path.join(build, 'bitbake-cookerdaemon.log')
lockname = os.path.join(build, 'bitbake.lock')
sockname = os.path.join(build, 'bitbake.sock')
lock = bb.utils.lockfile(lockname, False, False)
if not lock:
print('bitbake server is already running, please run `bitbake -m` to kill')
sys.exit(1)
lockfd = lock.fileno()
readypipe, readypipeinfd = os.pipe()
timeout = -1
profile = False
xmlrpcinterface = (None, '0')
# Replace standard fds with our own
with open('/dev/null', 'r') as si:
os.dup2(si.fileno(), sys.stdin.fileno())
so = open(logfile, 'a+')
sys.stdout = Tee(sys.stdout, so)
# Have stdout and stderr be the same so log output matches chronologically
# and there aren't two seperate buffers
sys.stderr = sys.stdout
logger = logging.getLogger("BitBake")
# Ensure logging messages get sent to the UI as events
handler = bb.event.LogHandler()
logger.addHandler(handler)
bb.server.process.execServer(lockfd, readypipeinfd, lockname, sockname, timeout, xmlrpcinterface, profile)
os.close(readypipe)
if __name__ == '__main__':
args = parse_args()
build = os.path.abspath(os.path.expanduser(args.build))
if not os.path.exists(build):
print("build dir does not exist", file=sys.stderr)
sys.exit(1)
run(build)
运行之后效果如下(客户端执行了 bitbake bmcweb -c listtasks
命令):
❯ ./obmc-server.py -b ~/working/bmc/openbmc/build/romulus
89324 23:09:24.415613 --- Starting bitbake server pid 89324 at 2023-12-29 23:09:24.415595 ---
89324 23:09:24.416181 Started bitbake server pid 89324
89324 23:09:24.416343 Entering server connection loop
89324 23:09:24.416386 Lockfile is: /home/runsisi/working/bmc/openbmc/build/romulus/bitbake.lock
Socket is /home/runsisi/working/bmc/openbmc/build/romulus/bitbake.sock (True)
89324 23:09:40.267400 Accepting [<socket.socket fd=7, family=1, type=1, proto=0, laddr=bitbake.sock>] ([])
89324 23:09:40.268237 Processing Client
89324 23:09:40.268299 Connecting Client
89324 23:09:40.268572 Running command ['setFeatures', [2, 1]]
89324 23:09:40.268619 Sending reply (None, None)
89324 23:09:40.268681 Command Completed (socket: True)
89324 23:09:40.269027 Running command ['updateConfig', {'halt': True, 'force': False, 'invalidate_stamp': None, 'dry_run': False, 'dump_signatures': [], 'extra_assume_provided': [], 'profile': False, 'prefile': [], 'postfile': [], 'server_timeout': None, 'nosetscene': False, 'setsceneonly': False, 'skipsetscene': False, 'runall': [], 'runonly': None, 'writeeventlog': None, 'build_verbose_shell': False, 'build_verbose_stdout': False, 'default_loglevel': 20, 'debug_domains': {}}, {'HOME': '/home/runsisi', 'LOGNAME': 'runsisi', 'PATH': '/home/runsisi/working/bmc/openbmc/scripts:/home/runsisi/working/bmc/openbmc/poky/bitbake/bin:/home/runsisi/working/cxx/depot_tools:/home/runsisi/.bun/bin:/opt/cni/bin:/home/runsisi/go/bin:/home/runsisi/.npm/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/home/runsisi/working/cxx/depot_tools:/home/runsisi/.dotnet/tools:/opt/flutter/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/usr/lib/rustup/bin:/opt/flutter/bin:/home/runsisi/.local/bin', 'PWD': '/home/runsisi/working/bmc/openbmc/build/romulus', 'SHELL': '/bin/zsh', 'SSH_AUTH_SOCK': '/run/user/1000/keyring/ssh', 'USER': 'runsisi', 'BBPATH': '/home/runsisi/working/bmc/openbmc/build/romulus', 'KUBECONFIG': '/etc/rancher/k3s/k3s.yaml', 'BUN_INSTALL': '/home/runsisi/.bun', 'EDITOR': 'vim', 'PYTHONPATH': '/home/runsisi/working/bmc/openbmc/poky/bitbake/lib:', 'OE_ADDED_PATHS': '/home/runsisi/working/bmc/openbmc/scripts:/home/runsisi/working/bmc/openbmc/poky/bitbake/bin:', 'BUILDDIR': '/home/runsisi/working/bmc/openbmc/build/romulus', '_': '/home/runsisi/working/bmc/openbmc/poky/bitbake/bin/bitbake'}, ['/home/runsisi/working/bmc/openbmc/poky/bitbake/bin/bitbake', 'bmcweb', '-c', 'listtasks']]
89324 23:09:40.493238 Base config valid
89324 23:09:40.494288 Sending reply (None, None)
89324 23:09:40.494367 Command Completed (socket: True)
89324 23:09:40.494531 Running command ['getVariable', 'BBINCLUDELOGS']
89324 23:09:40.494585 Sending reply ('yes', None)
89324 23:09:40.494627 Command Completed (socket: True)
89324 23:09:40.494719 Running command ['getVariable', 'BBINCLUDELOGS_LINES']
89324 23:09:40.494933 Sending reply (None, None)
89324 23:09:40.494973 Command Completed (socket: True)
89324 23:09:40.495044 Running command ['getSetVariable', 'BB_CONSOLELOG']
89324 23:09:40.495104 Sending reply ('/home/runsisi/working/bmc/openbmc/build/romulus/tmp/log/cooker/romulus/20231229150940.log', None)
89324 23:09:40.495142 Command Completed (socket: True)
89324 23:09:40.495214 Running command ['getSetVariable', 'BB_LOGCONFIG']
89324 23:09:40.495257 Sending reply (None, None)
89324 23:09:40.495303 Command Completed (socket: True)
89324 23:09:40.496214 Running command ['getUIHandlerNum']
89324 23:09:40.496247 Sending reply (1, None)
89324 23:09:40.496287 Command Completed (socket: True)
89324 23:09:40.496373 Running command ['setEventMask', 1, 20, {'BitBake.SigGen.HashEquiv': 19, 'BitBake.RunQueue.HashEquiv': 19}, ['bb.runqueue.runQueueExitWait', 'bb.event.LogExecTTY', 'logging.LogRecord', 'bb.build.TaskFailed', 'bb.build.TaskBase', 'bb.event.ParseStarted', 'bb.event.ParseProgress', 'bb.event.ParseCompleted', 'bb.event.CacheLoadStarted', 'bb.event.CacheLoadProgress', 'bb.event.CacheLoadCompleted', 'bb.command.CommandFailed', 'bb.command.CommandExit', 'bb.command.CommandCompleted', 'bb.cooker.CookerExit', 'bb.event.MultipleProviders', 'bb.event.NoProvider', 'bb.runqueue.sceneQueueTaskStarted', 'bb.runqueue.runQueueTaskStarted', 'bb.runqueue.runQueueTaskFailed', 'bb.runqueue.sceneQueueTaskFailed', 'bb.event.BuildBase', 'bb.build.TaskStarted', 'bb.build.TaskSucceeded', 'bb.build.TaskFailedSilent', 'bb.build.TaskProgress', 'bb.event.ProcessStarted', 'bb.event.ProcessProgress', 'bb.event.ProcessFinished']]
89324 23:09:40.496410 Sending reply (True, None)
89324 23:09:40.496445 Command Completed (socket: True)
89324 23:09:40.496509 Running command ['setConfig', 'cmd', 'listtasks']
89324 23:09:40.496534 Sending reply (None, None)
89324 23:09:40.496566 Command Completed (socket: True)
89324 23:09:40.496630 Running command ['buildTargets', ['bmcweb'], 'listtasks']
89324 23:09:40.496675 Registering idle function <bound method Command.runAsyncCommand of <bb.command.Command object at 0x7f4eeca2f7d0>>
89324 23:09:40.496691 Sending reply (True, None)
89324 23:09:40.496724 Command Completed (socket: True)
89324 23:09:40.558883 Parsing started
89324 23:09:43.279924 Parse cache valid
89324 23:09:44.949872 Registering idle function <function BBCooker.buildTargets.<locals>.buildTargetsIdle at 0x7f4ed6536fc0>
89324 23:09:44.950021 Removing idle function <bound method Command.runAsyncCommand of <bb.command.Command object at 0x7f4eeca2f7d0>>
89324 23:09:46.747136 Removing idle function <function BBCooker.buildTargets.<locals>.buildTargetsIdle at 0x7f4ed6536fc0> at idleFinish
89324 23:09:46.997835 Processing Client
89324 23:09:46.998027 Disconnecting Client (socket: True)
89324 23:09:46.998373 Parse cache invalidated
需要注意的是,bitbake-server 内部显式忽略了 CTRL-C
的处理:
// bitbake/lib/bb/server/process.py
def idle_commands(self, delay, fds=None):
try:
return select.select(fds,[],[],nextsleep)[0]
except InterruptedError:
# Ignore EINTR
return []
如需退出需要按两次 CTRL-C
。
PyCharm pydevd
bitbake-server 其实也不是真正最终干事的,在它后面还有 bitbake-worker 工作进程,由于 Pycharm 和 VSCode 支持自动 attach 子进程,因此在 bitbake-worker 上设置的断点可以自动触发,从而实现 bitbake-server 的全流程调试。
需要注意的是如果系统的 Python 版本是 3.11+ 则稍微有点麻烦,Pycharm 的调试后端 pydevd 需要更新如下补丁(相比 pydevd 上游的补丁稍有修改,因为 pydevd-pycharm 用的是更老一些的版本),否则无法触发子进程的断点:
--- plugins/python/helpers/pydev/_pydev_bundle/pydev_monkey.py.orig 2023-12-30 23:06:12.368331536 +0800
+++ plugins/python/helpers/pydev/_pydev_bundle/pydev_monkey.py 2023-12-30 23:09:39.446791415 +0800
@@ -582,6 +582,37 @@
return new_warn_fork_exec
+def create_subprocess_fork_exec(original_name):
+ """
+ subprocess._fork_exec(args, executable_list, close_fds, ... (13 more))
+ """
+
+ def new_fork_exec(args, *other_args):
+ import subprocess
+ args = patch_args(args)
+ send_process_created_message()
+
+ return getattr(subprocess, original_name)(args, *other_args)
+
+ return new_fork_exec
+
+
+def create_subprocess_warn_fork_exec(original_name):
+ """
+ subprocess._fork_exec(args, executable_list, close_fds, ... (13 more))
+ """
+
+ def new_warn_fork_exec(*args):
+ try:
+ import subprocess
+ warn_multiproc()
+ return getattr(subprocess, original_name)(*args)
+ except:
+ pass
+
+ return new_warn_fork_exec
+
+
def create_CreateProcess(original_name):
"""
CreateProcess(*args, **kwargs)
@@ -728,6 +759,12 @@
monkey_patch_module(_posixsubprocess, 'fork_exec', create_fork_exec)
except ImportError:
pass
+
+ try:
+ import subprocess
+ monkey_patch_module(subprocess, '_fork_exec', create_subprocess_fork_exec)
+ except AttributeError:
+ pass
else:
# Windows
try:
@@ -767,6 +804,12 @@
monkey_patch_module(_posixsubprocess, 'fork_exec', create_warn_fork_exec)
except ImportError:
pass
+
+ try:
+ import subprocess
+ monkey_patch_module(subprocess, '_fork_exec', create_subprocess_warn_fork_exec)
+ except AttributeError:
+ pass
else:
# Windows
try:
On Python 3.11 _fork_exec
now needs to be monkey-patched for subproceses
https://github.com/fabioz/PyDev.Debugger/commit/2e02c252af89c2cd1056de432f791403f7a00c26
Cannot attach to subprocess when debugging with Python 3.11, 3.12
https://youtrack.jetbrains.com/issue/PY-61217
pydevd-pycharm
https://pypi.org/project/pydevd-pycharm/
bitbake debug
设置 PATH
环境变量加上 bitbake/bin 目录,则不会有如下的警告(不过不加也不影响调试,因为服务端是我们自己手工启动的):
WARNING: bitbake binary is not found in PATH, did you source the script?
OpenBMC 源代码下面的 bitbake, meta 等多个文件夹和文件实际上都是 poky 下文件夹的符号链接:
❯ ll | grep -- '->'
lrwxrwxrwx 1 runsisi runsisi 13 Nov 3 21:21 bitbake -> poky/bitbake/
lrwxrwxrwx 1 runsisi runsisi 12 Nov 3 21:21 LICENSE -> poky/LICENSE
lrwxrwxrwx 1 runsisi runsisi 9 Nov 3 21:21 meta -> poky/meta
lrwxrwxrwx 1 runsisi runsisi 15 Nov 3 21:21 meta-poky -> poky/meta-poky/
lrwxrwxrwx 1 runsisi runsisi 18 Nov 3 21:21 meta-skeleton -> poky/meta-skeleton
lrwxrwxrwx 1 runsisi runsisi 22 Nov 3 21:21 oe-init-build-env -> poky/oe-init-build-env
lrwxrwxrwx 1 runsisi runsisi 13 Nov 3 21:21 scripts -> poky/scripts/
在 PyCharm 中增加 bitbake/bin 以及 bitbake/lib, meta/lib 目录时最好添加 poky 目录下的文件夹,这样断点和调试时的源代码对应关系会显示的更明确一些:
bitbake-server debug
exec task
bb, bbclass 等文件中定义的 python 函数和 shell 函数的执行逻辑如下:
// bitbake/lib/bb/buid.py
def exec_task(fn, task, d, profile = False):
return _exec_task(fn, task, d, quieterr)
def _exec_task(fn, task, d, quieterr):
exec_func(task, localdata)
def exec_func(func, d, dirs = None):
// runfile 即 temp 目录下的 run.do_XXX.PID 文件
if ispython:
exec_func_python(func, d, runfile, cwd=adir)
else:
exec_func_shell(func, d, runfile, cwd=adir)
def exec_func_python(func, d, runfile, cwd=None):
code = _functionfmt.format(function=func)
with open(runfile, 'w') as script:
bb.data.emit_func_python(func, script, d)
// 与 exec_func_shell 不同,生成的 run.do_XXX.PID python 脚本并不是直接运行,
// 而是通过 python 的内置函数 `exec` 执行
comp = utils.better_compile(code, func, "exec_func_python() autogenerated")
utils.better_exec(comp, {"d": d}, code, "exec_func_python() autogenerated")
def exec_func_shell(func, d, runfile, cwd=None):
with open(runfile, 'w') as script:
script.write(shell_trap_code())
// 将 func 代码写入之前会将 meta/conf/bitbake.conf 中定义的 export 变量
// 在 shell 脚本中 export 出来(典型的如 `export PATH`)
bb.data.emit_func(func, script, d)
// 直接执行生成的 shell 脚本
cmd = runfile
bb.process.run(cmd, shell=False, stdin=stdin, log=logfile, extrafiles=[(fifo,readfifo)])
appendix
测试过程中倒是发现了 bitbake-server 的一个 bug:
diff --git a/poky/bitbake/lib/bb/server/process.py b/poky/bitbake/lib/bb/server/process.py
index d495ac6245..38c86fb2ce 100644
--- a/poky/bitbake/lib/bb/server/process.py
+++ b/poky/bitbake/lib/bb/server/process.py
@@ -373,7 +373,7 @@ class ProcessServer():
while not lock:
i = 0
lock = None
- if not os.path.exists(os.path.basename(lockfile)):
+ if not os.path.exists(os.path.dirname(lockfile)):
serverlog("Lockfile directory gone, exiting.")
return
最后修改于 2024-01-02