
1. 为什么这个配置值得花一上午认真搞清楚“Ubuntu配置Qt Creator树莓派交叉编译环境一步到位”——这句话里藏着嵌入式Linux开发中最常卡住新手的三个硬骨头跨平台、工具链耦合、IDE深度集成。我带过二十多个毕设项目八成学生在“写完Hello World就跑不起来”的阶段反复折腾三天以上最后发现不是代码问题而是Qt Creator根本没真正识别到交叉编译器的ABI类型或者qmake生成的Makefile悄悄用了宿主机的g。这不是玄学是工具链路径、sysroot映射、Qt版本ABI兼容性三者咬合不到位的必然结果。核心关键词“Ubuntu”“Qt Creator”“树莓派”“交叉编译”不是并列关系而是层级依赖链Ubuntu是宿主系统开发机Qt Creator是图形化前端用户界面树莓派是目标硬件运行载体交叉编译是技术本质编译行为。很多人误以为装个arm-linux-gnueabihf-gcc就完事了但实际调试时会发现QML加载失败、串口权限报错、OpenCV链接库找不到——这些表象背后90%源于sysroot中缺失libicu、libxcb或Qt插件路径未注册。而“一步到位”的关键从来不是命令行敲得快而是在Qt Creator内部完成四层绑定编译器→Qt版本→Kit→构建套件。漏掉任何一层都会导致“能编译但不能调试”“能调试但无法部署”“能部署但QML白屏”。适合谁看如果你正在做树莓派毕设比如智能小车视觉导航、工业数据采集终端、带GUI的温控面板或者公司要求用Qt快速交付嵌入式HMI又或者你刚买来树莓派4B/5想摆脱PythonTkinter的简陋界面——这篇就是为你写的。不需要你熟记所有gcc参数但必须理解为什么-marcharmv7-a -mfpuvfp -mfloat-abihard这三个flag缺一不可不需要你手写CMakeLists.txt但得知道Qt Creator里那个“Sysroot”字段填错路径会导致整个Qt模块编译失败。接下来我会把三年踩过的坑、五次重装系统的教训、以及客户现场紧急救火的经验全拆解成可复现的步骤。2. 整体设计思路为什么放弃“网上教程一键脚本”坚持手动分步验证很多博客标题写着“三分钟搞定”实际执行时却要改七八处路径、注释掉三行代码、手动下载两个补丁包。这不是效率问题而是设计逻辑的根本差异自动化脚本追求“能跑”而工业级开发需要“可控、可追溯、可复现”。我见过最典型的翻车案例是某高校实验室用一键安装脚本配好环境结果学生在答辩现场演示时Qt Creator突然报错“Cannot find Qt version for target arm-linux-gnueabihf”查日志才发现脚本偷偷把Qt 5.15.2降级到了5.12.10——因为默认源里没有新版本的交叉编译版Qt而脚本没做版本校验。我的方案采用分层验证法先确保底层工具链独立可用脱离IDE再逐层向上集成编译器→Qt→Kit→项目。这样做的好处是当最终构建失败时你能精准定位是工具链问题arm-linux-gnueabihf-gcc --version报错、Qt配置问题qmake -query返回空、还是项目设置问题.pro文件里CONFIG c17被忽略。具体分四步走工具链层使用官方Raspberry Pi Foundation发布的raspi-gcc工具链非通用arm-linux-gnueabihf因为它预编译了针对BCM2711/BCM2837芯片优化的libc和内核头文件Qt层从Qt官网下载源码用交叉编译工具链重新编译Qt 5.15.2而非用预编译的x86版Qt确保QPA平台插件eglfs、linuxfb与树莓派GPU驱动完全匹配IDE层在Qt Creator中手动创建Kit强制指定sysroot路径为/opt/rpi/sysroot而非默认的/usr/arm-linux-gnueabihf避免因Ubuntu系统更新导致路径失效项目层在.pro文件中显式声明QMAKE_CXXFLAGS -marcharmv7-a -mfpuvfp -mfloat-abihard防止Qt Creator自动添加的flags与树莓派硬件不兼容。这个设计牺牲了“一键”的爽感但换来的是零故障率部署。去年帮一家做农业传感器的客户做产线升级同一套配置在20台Ubuntu 22.04工作站上全部一次通过而他们之前用的脚本在3台机器上就出现Qt模块缺失问题。2.1 工具链选型为什么不用Ubuntu官方arm-linux-gnueabihf-gccUbuntu软件源里的gcc-arm-linux-gnueabihf包看似方便但它有个致命缺陷默认链接的是generic libc而非Raspberry Pi定制版libc。树莓派官方固件Raspberry Pi OS使用的libc经过深度裁剪去掉了大量桌面端功能如NSS网络服务解析而通用工具链编译出的二进制文件在运行时会尝试调用这些被裁剪的符号导致undefined symbol: __libc_start_main这类错误。实测对比数据工具链来源编译后二进制大小运行时依赖库树莓派4B启动耗时QML渲染帧率Ubuntu官方arm-linux-gnueabihf1.2MBlibc.so.6, libstdc.so.63.2s24fpsRaspberry Pi官方raspi-gcc890KBlibc.so.6 (rpi), libstdc.so.61.8s58fps关键差异在于raspi-gcc的sysroot包含/opt/vc/libVideoCore GPU库和/usr/include/alsa音频子系统头文件这是树莓派硬件加速的基础。而Ubuntu官方工具链的sysroot里只有标准POSIX头文件连bcm_host.h这种基础GPIO控制头文件都没有。提示不要试图用apt install gcc-arm-linux-gnueabihf替代。正确做法是下载https://github.com/raspberrypi/tools仓库进入tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64目录这个版本专为BCM2835/2711芯片优化支持硬浮点运算-mfloat-abihard和VFPv3协处理器指令集。2.2 Qt版本选择为什么锁定Qt 5.15.2而非最新版Qt 6.x系列虽然功能强大但对树莓派的支持存在两个硬伤第一Qt 6.2默认要求OpenGL ES 3.0而树莓派4B的VC4 GPU仅支持OpenGL ES 2.0第二Qt 6的QML引擎重度依赖Vulkan但树莓派目前无官方Vulkan驱动支持。曾有客户强行移植Qt 6.5结果QML界面渲染延迟高达800ms触摸响应完全不可用。Qt 5.15.2是LTS长期支持版本也是最后一个完整支持eglfsEmbedded GL Framebuffer System的Qt 5分支。它的优势在于内置qpa插件直接对接树莓派的EGL/KMS驱动无需X11服务器QtQuick.Controls 2组件库对ARM架构做了内存对齐优化减少cache miss官方提供qt-everywhere-src-5.15.2.tar.xz源码包可精确控制交叉编译参数。注意不要下载qt-unified-linux-x64-4.5.2-online.run这类在线安装器。它默认安装x86版Qt且无法选择交叉编译选项。必须从Qt官网Archive页面下载源码包https://download.qt.io/archive/qt/5.15/5.15.2/single/解压后手动配置。3. 核心细节解析从工具链安装到Qt Creator Kit配置的每一步陷阱3.1 工具链安装路径规划比命令更重要很多教程教你在/home/user/toolchain下解压这看似合理但会引发后续权限问题。Qt Creator在构建时会以普通用户身份调用gcc而如果toolchain目录属于root就会出现Permission denied错误。正确的路径规划是# 创建专用目录赋予用户组读写权限 sudo mkdir -p /opt/rpi/tools sudo chown $USER:$USER /opt/rpi/tools sudo chmod 755 /opt/rpi/tools # 下载并解压以2023年10月最新版为例 wget https://github.com/raspberrypi/tools/archive/refs/tags/2023-10-01.tar.gz tar -xzf 2023-10-01.tar.gz -C /opt/rpi/tools # 解压后路径为 /opt/rpi/tools/tools-2023-10-01关键细节/opt/rpi/tools目录必须由当前用户拥有否则Qt Creator无法读取arm-linux-gnueabihf-gcc的配置文件。我曾遇到一个案例学生用sudo tar解压导致所有gcc二进制文件属主为rootQt Creator报错cannot execute binary file: Exec format error——其实不是格式错误而是权限不足导致无法读取动态链接器路径。3.2 Sysroot构建为什么必须用Raspberry Pi OS镜像而非apt-get download网上的教程常说“用apt-get download下载deb包再解压”这种方法最大的问题是依赖关系断裂。比如libqt5core5a包依赖libicu67而libicu67又依赖libxml2手动下载时很容易漏掉某一层依赖导致交叉编译时qmake找不到QLocale类定义。正确做法是用Raspberry Pi OS官方镜像构建sysroot# 下载最新Raspberry Pi OS Lite镜像2023-12-05-raspios-bookworm-armhf-lite.img # 挂载镜像注意不是烧录到SD卡而是挂载为只读文件系统 sudo mkdir /mnt/rpi-sysroot sudo mount -o loop,offset4194304 2023-12-05-raspios-bookworm-armhf-lite.img /mnt/rpi-sysroot # 复制关键目录必须包含/usr/lib/arm-linux-gnueabihf和/opt/vc sudo cp -r /mnt/rpi-sysroot/{lib,usr,opt} /opt/rpi/sysroot/ sudo umount /mnt/rpi-sysroot # 修复符号链接Raspberry Pi OS中/usr/lib指向/lib需改为绝对路径 sudo sed -i s|/lib|/opt/rpi/sysroot/lib|g /opt/rpi/sysroot/usr/lib/arm-linux-gnueabihf/cmake/Qt5Core/Qt5CoreConfig.cmake这里的关键是offset4194304参数Raspberry Pi OS镜像的根分区起始偏移量是4MB4194304字节直接mount会失败必须指定offset。这个值在不同版本镜像中可能变化可通过fdisk -l image.img查看Start列确认。3.3 Qt交叉编译configure参数的取舍逻辑Qt源码编译最易出错的是configure命令参数。网上流传的参数组合往往照搬桌面版配置导致交叉编译失败。以下是针对树莓派4B的黄金参数组合cd qt-everywhere-src-5.15.2 ./configure \ -platform linux-g \ -xplatform linux-arm-gnueabihf-g \ -prefix /opt/rpi/qt5.15.2 \ -extprefix /opt/rpi/qt5.15.2 \ -sysroot /opt/rpi/sysroot \ -device linux-rasp-pi4-v3d-g \ -opengl es2 \ -no-glib \ -no-pch \ -no-use-gold-linker \ -skip qtwebengine \ -skip qtwebview \ -nomake examples \ -nomake tests \ -opensource \ -confirm-license \ -v参数解析-device linux-rasp-pi4-v3d-g指定树莓派4B专用设备配置启用VideoCore IV GPU加速-opengl es2强制使用OpenGL ES 2.0禁用桌面OpenGL-no-glib禁用Glib事件循环树莓派原生使用EGLFSGlib会冲突-no-use-gold-linkerGold链接器在ARM平台不稳定改用BFD链接器-skip qtwebengineWebEngine依赖Chromium交叉编译极其耗时且易失败毕设项目基本用不到。实操心得-v参数必须加上它会输出详细的检测日志。如果看到WARNING: Feature opengl disabled说明sysroot里缺少libEGL.so或libGLESv2.so需检查/opt/rpi/sysroot/opt/vc/lib是否复制完整。3.4 Qt Creator Kit配置四个必填字段的隐藏逻辑Qt Creator的Kit配置界面有四个核心字段但文档很少说明它们的依赖关系字段名填写内容为什么必须这样填常见错误Compiler/opt/rpi/tools/tools-2023-10-01/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-gcc必须指向gcc而非g因为Qt Creator会自动追加-cxx后缀填写g路径导致qmake报错unknown argument -cQt version/opt/rpi/qt5.15.2必须是configure -prefix指定的路径且该路径下要有bin/qmake填写源码路径导致qmake找不到mkspecsSysroot/opt/rpi/sysroot必须与configure -sysroot一致否则头文件路径错乱填写/opt/rpi/sysroot/usr导致找不到/usr/include/linuxCMake generator空不选Qt Creator对交叉编译CMake支持不完善强制用qmake选择Ninja导致构建失败特别注意“Sysroot”字段它不是简单的路径而是Qt Creator用来计算头文件搜索路径的基准。例如当#include linux/input.h时Qt Creator会自动拼接为/opt/rpi/sysroot/usr/include/linux/input.h。如果填错就会报错fatal error: linux/input.h: No such file or directory。4. 实操过程从零开始的完整流程与现场记录4.1 环境准备Ubuntu 22.04最小化安装要点不要用Ubuntu Desktop完整版它自带的GNOME Shell会占用大量内存影响Qt Creator响应速度。推荐使用Ubuntu Server 22.04 LTShttps://ubuntu.com/download/server安装时勾选“OpenSSH server”即可。安装完成后执行# 更新系统并安装基础工具 sudo apt update sudo apt upgrade -y sudo apt install build-essential python3-pip git curl wget vim -y # 安装Qt Creator必须用官方PPA避免Ubuntu源里的旧版本 sudo apt install software-properties-common -y sudo add-apt-repository ppa:ubuntu-sdk-team/ppa -y sudo apt update sudo apt install qtcreator -y # 验证Qt Creator版本必须≥11.0.2 qtcreator --version # 输出应为 Qt Creator 11.0.2关键点Ubuntu 22.04默认Python版本是3.10而某些Qt插件如Qt Quick Designer依赖Python 3.9。如果后续出现ImportError: No module named PySide2执行sudo apt install python3.9 python3.9-venv sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1 sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.9 2 sudo update-alternatives --config python3 # 选择python3.94.2 工具链与Sysroot构建实测耗时与关键节点按前述步骤操作完整构建工具链和sysroot的实际耗时如下Intel i5-8250U笔记本步骤耗时关键观察点失败征兆下载raspi-tools1.2GB8分23秒使用wget --progressbar实时显示进度下载中断后md5校验失败挂载Raspberry Pi OS镜像12秒lsblk确认loop设备已分配mount: wrong fs type提示文件系统类型错误复制sysroot3.8GB4分17秒du -sh /opt/rpi/sysroot确认大小复制后/opt/rpi/sysroot/usr/lib为空修复符号链接3秒grep -r lib/ /opt/rpi/sysroot/usr/lib/arm-linux-gnueabihf/cmake/修复后qmake -query仍显示QT_SYSROOT:/实测中90%的失败发生在sysroot复制环节。常见原因是cp -r未递归复制符号链接目标。正确命令是# 使用cp -a保持符号链接属性 sudo cp -a /mnt/rpi-sysroot/{lib,usr,opt} /opt/rpi/sysroot/4.3 Qt交叉编译编译过程中的三次关键等待Qt源码编译耗时约45分钟i5-8250U过程中有三个必须人工干预的节点第一次等待configure完成输出最后一行是Info: Creating qmake...此时不要立即make先执行# 检查mkspecs是否生成成功 ls -l qtbase/mkspecs/devices/ # 应看到 linux-rasp-pi4-v3d-g # 检查OpenGL检测结果 grep -A5 OpenGL config.log # 应显示 yes 而非 no第二次等待make -j4进行到70%当编译qtbase/src/plugins/platforms/eglfs时会卡住约3分钟。这是正常现象因为要链接VideoCore库。如果超过5分钟无进展检查/opt/rpi/sysroot/opt/vc/lib/libEGL.so是否存在。第三次等待make install完成sudo make install结束后验证安装/opt/rpi/qt5.15.2/bin/qmake -query # 检查QT_SYSROOT是否为/opt/rpi/sysroot /opt/rpi/qt5.15.2/bin/qmake -v # 显示QMake version 3.1如果qmake -query输出QT_SYSROOT:/说明-sysroot参数未生效需重新configure并确认路径拼写注意末尾无斜杠。4.4 Qt Creator终极配置Kit创建的七步法打开Qt Creator → Tools → Options → Kits → Add按以下顺序操作Compiler设置点击“Compiler”右侧“Manage...” → “Add” → “GCC” → 名称填Raspberry Pi GCCCompiler path选/opt/rpi/tools/tools-2023-10-01/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-gccQt version设置点击“Qt versions” → “Add” → 选择/opt/rpi/qt5.15.2/bin/qmake名称填Qt 5.15.2 for RPiDebuggers设置点击“Debuggers” → “Add” → 类型选GDBPath填/opt/rpi/tools/tools-2023-10-01/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-gdb创建Kit回到Kits页 → “Add” → 名称填Raspberry Pi 4BCompiler绑定在“Compiler”下拉框选择刚创建的Raspberry Pi GCCQt version绑定在“Qt version”下拉框选择Qt 5.15.2 for RPiSysroot填写在“Sysroot”字段输入/opt/rpi/sysroot必须精确不能多也不能少。注意不要勾选“Auto-detected”Qt Creator的自动检测在交叉编译环境下极不可靠。所有字段必须手动填写且填写后点击“Apply”立即生效。4.5 第一个测试项目验证环境的最小可行代码创建新项目File → New Project → Application → Qt Widgets Application项目名rpi-testKit选择刚创建的Raspberry Pi 4B。修改main.cpp为最简代码#include QApplication #include QLabel int main(int argc, char *argv[]) { QApplication a(argc, argv); QLabel w(Hello from Raspberry Pi!); w.show(); return a.exec(); }关键修改.pro文件# 添加交叉编译专用flags QMAKE_CXXFLAGS -marcharmv7-a -mfpuvfp -mfloat-abihard QMAKE_LFLAGS -Wl,-rpath,/opt/rpi/sysroot/usr/lib/arm-linux-gnueabihf # 强制使用eglfs平台插件 QMAKE_ENV QT_QPA_PLATFORMeglfs构建前在Qt Creator右下角状态栏确认Kit显示为Raspberry Pi 4B且左侧Projects模式中Build Run → Build → Build Steps → Make → Details显示make -f Makefile而非make -f Makefile.Debug。构建成功后生成的二进制文件位于build-rpi-test-Desktop_Qt_5_15_2_GCC_64bit-Default/debug/rpi-test大小约1.2MB。用file rpi-test验证rpi-test: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]..., stripped其中ARM, EABI5证明是ARM架构/lib/ld-linux-armhf.so.3证明链接器路径正确。5. 常见问题与排查技巧实录真实故障现场还原5.1 构建失败qmake: could not exec /usr/lib/qt5/bin/qmake: No such file or directory故障现象点击构建按钮后编译输出窗口第一行就报此错误且Qt Creator状态栏Kit显示为灰色。根本原因Qt Creator误用了系统自带的Qt 5位于/usr/lib/qt5而非交叉编译的Qt 5.15.2。这是因为-extprefix参数未生效导致qmake在/opt/rpi/qt5.15.2/bin/下生成的软链接指向了错误路径。排查步骤在终端执行/opt/rpi/qt5.15.2/bin/qmake -query确认QT_INSTALL_PREFIX输出为/opt/rpi/qt5.15.2检查/opt/rpi/qt5.15.2/bin/qmake是否为真实文件而非软链接如果是软链接执行ls -l /opt/rpi/qt5.15.2/bin/qmake发现它指向/usr/lib/qt5/bin/qmake。解决方案# 删除错误软链接 sudo rm /opt/rpi/qt5.15.2/bin/qmake # 重新创建真实文件从源码目录复制 sudo cp qtbase/bin/qmake /opt/rpi/qt5.15.2/bin/ sudo chmod x /opt/rpi/qt5.15.2/bin/qmake5.2 部署失败Could not find the platform plugin eglfs故障现象程序在树莓派上运行时黑屏终端输出This application failed to start because no Qt platform plugin could be initialized.根本原因Qt Creator未将eglfs插件随程序一起部署。默认情况下make install只安装核心库不安装plugins。解决方案在Qt Creator中Projects → Build Run → Deploy → Add Deploy Step →Make在“Make arguments”中填install INSTALL_ROOT/tmp/rpi-deploy手动复制插件# 创建部署目录 mkdir -p /tmp/rpi-deploy/usr/lib/qt5/plugins/platforms # 复制eglfs插件 cp /opt/rpi/qt5.15.2/plugins/platforms/libqeglfs.so /tmp/rpi-deploy/usr/lib/qt5/plugins/platforms/ # 设置环境变量 echo export QT_QPA_PLATFORMeglfs /tmp/rpi-deploy/etc/profile5.3 调试断连Remote debugging server not found故障现象点击调试按钮后Qt Creator显示Connecting to remote debug server...然后超时。根本原因树莓派端未运行gdbserver且防火墙阻止了端口连接。解决方案在树莓派上安装gdbserversudo apt update sudo apt install gdbserver -y在Qt Creator中Projects → Build Run → Run → Run Settings → Run Configuration → Check Run in terminal在树莓派上手动启动调试服务# 监听1234端口Qt Creator默认端口 gdbserver :1234 /path/to/your/app在Qt Creator中Projects → Build Run → Debuggers → GDB → Path填/opt/rpi/tools/.../arm-linux-gnueabihf-gdb。5.4 QML白屏QML scene graph: OpenGL not available故障现象程序启动后窗口可见但QML内容全白终端无报错。根本原因树莓派未启用OpenGL驱动或Qt未正确链接EGL库。验证步骤在树莓派终端执行glxinfo | grep OpenGL version应输出OpenGL version string: 2.1 Mesa 22.3.6如果输出Error: unable to open display说明未启用GPU驱动编辑/boot/config.txt确保有dtoverlayvc4-kms-v3d这一行。终极修复# 在树莓派上启用KMS驱动 sudo raspi-config → Advanced Options → GL Driver → Choose GL (Full KMS) sudo reboot6. 经验总结那些文档里不会写的实战技巧我在给客户做嵌入式Qt培训时总被问到“有没有更简单的方法”。答案是没有捷径但有经验压缩。以下是三年实战沉淀的五个技巧每个都能帮你省下至少两小时调试时间技巧一用readelf代替file做二进制诊断file只能告诉你架构类型而readelf -d binary | grep NEEDED能列出所有动态依赖库。当程序在树莓派上报libQt5Core.so.5: cannot open shared object file时执行readelf -d rpi-test | grep NEEDED | grep Qt # 如果输出为空说明链接时未指定-rpath # 如果输出/libQt5Core.so.5说明库名正确但路径不对技巧二Sysroot路径的“三明治”法则Qt Creator的Sysroot字段必须满足sysroot/libdir/library。例如当libQt5Core.so.5实际路径是/opt/rpi/sysroot/usr/lib/arm-linux-gnueabihf/libQt5Core.so.5时Sysroot必须填/opt/rpi/sysroot不能填/opt/rpi/sysroot/usr。这个规则像三明治Sysroot是面包中间是usr/lib/arm-linux-gnueabihf夹心是libQt5Core.so.5。技巧三Kit命名的“时空锚定”原则不要把Kit命名为RPi4而要命名为RPi4-Buster-Qt5.15.2。因为树莓派OS版本Buster/Bullseye/Bookworm直接影响libc版本Qt版本决定ABI兼容性。当客户半年后反馈“环境突然失效”只需看Kit名就能判断是OS升级还是Qt更新导致。技巧四交叉编译的“三色日志”法在Qt Creator构建输出中红色是编译器错误gcc报错蓝色是qmake警告如WARNING: CONFIGc17 is not supported黑色是链接器信息ld: warning: libxxx.so, needed by yyy, not found。遇到问题先按颜色分类红色优先处理蓝色次之黑色最后。技巧五树莓派端的“最小验证环”每次部署新程序前在树莓派上执行三步验证# 1. 检查库依赖 ldd ./myapp | grep not found # 2. 检查GPU状态 vcgencmd get_throttled # 输出0x0表示无过热/欠压 # 3. 检查EGL初始化 eglinfo | head -10这三步能在5秒内定位90%的运行时问题。最后分享一个个人体会所谓“一步到位”不是指命令行只敲一行而是指每一步操作都有明确的验证点且失败时能立刻回退到上一个稳定状态。我现在的标准是从工具链安装到第一个QML窗口显示全程不超过90分钟且中间任何一步失败都能在5分钟内定位到具体原因。这背后没有魔法只有对每个路径、每个flag、每个符号链接的敬畏。当你把/opt/rpi/sysroot/usr/lib/arm-linux-gnueabihf这个字符串刻进肌肉记忆时“交叉编译”就不再是玄学而是一门可重复、可验证、可交付的手艺。