第 15 章:构建、测试与发布——把 C 与 Java 变成一个发行包
scrcpy 的运行时跨桌面和 Android,构建时也跨 Meson/Ninja 与 Gradle/Android SDK。发布工程的目标,是保证 client、server、协议和版本始终锁步。
一、顶层 Meson 是总编排器
顶层 meson.build 声明版本 4.1,并按选项进入:
app/:编译桌面 executable;server/:调用 Gradle 构建或复制 prebuilt server。
compile_app / compile_server 可独立关闭,支持只开发一端。版本字符串被注入 C 的 config.h,同时 Gradle versionName 独立声明;发布脚本必须保证两处一致。
二、客户端依赖与条件能力
app/meson.build 的固定依赖:
- libavformat、libavcodec、libavutil、libswresample;
- SDL 3.2+。
条件依赖:
- Linux V4L2 → libavdevice;
- USB/AOA → libusb 1.0;
- Windows → mingw32、ws2_32;
- 平台文件分别来自
sys/unix或sys/win。
配置阶段还探测 strdup/asprintf/reallocarray/SOCK_CLOEXEC 等能力并生成宏,让源码在 libc/平台差异间选择兼容实现。
三、服务端为何可用 prebuilt
默认 server/meson.build 通过 wrapper 调 Gradle,产出安装名 scrcpy-server。若设置 prebuilt_server,Meson 只复制已有产物,见 server/meson.build。
这有两个用途:
- 桌面开发者没有 Android SDK 也能编译 client;
- 发布流水线可先构建并校验唯一 server artifact,再复用到多平台 client 包,避免每个平台重复产生潜在不同 server。
四、Android 构建的特殊形态
Gradle 配置使用 application plugin、compile/target SDK 36、min SDK 21,并启用 AIDL,见 server/build.gradle。
虽然使用 Android application 构建链,运行时不安装/启动 Activity;构建产物的 dex/classes 被 app_process 通过 CLASSPATH 加载。使用 application plugin 主要是获得 Android SDK、AIDL、资源处理和测试生态。
五、客户端测试保护什么
Debug Meson 构建注册 12 个 C 测试,重点覆盖:
- ADB devices 输出解析;
- 大端 binary 读写;
- audio ring buffer;
- CLI 选项与边界组合;
- Windows command quoting;
- 控制消息序列化;
- 设备消息反序列化;
- orientation 解析;
- string/string buffer;
- vector/vecdeque 容器。
清单见 app/meson.build。这些测试集中在“纯函数 + 协议边界 + 自制基础设施”,因为它们最适合无设备确定性验证。
六、服务端测试保护什么
JVM 测试覆盖:
ControlMessageReader的逐类型解析;DeviceMessageWriter的二进制输出;- codec options;
- binary / binary search;
- command/string parsing。
注意两端都有协议测试,但协议 schema 仍是手工同步的。最有价值的扩展是加入 golden vectors:同一字节样本由 C 序列化、Java 解析,反方向同理。
七、Sanitizer 是 C 测试的一部分
release/test_client.sh 用 -Db_sanitize=address,undefined 配置测试构建,再运行 Ninja test。ASan 抓越界/use-after-free,UBSan 抓整数/移位/对齐等未定义行为。
对大量手工所有权、容器宏和二进制解析的 C 项目,sanitizer 不是附加质量项,而是单元测试断言的第二层。脚本入口见 release/test_client.sh。
八、CI 的构建矩阵
release workflow 大致分为:
- server test;
- server Gradle build;
- 无 Gradle server build 验证;
- Linux client test;
- Linux x86_64 build;
- Windows 32/64 交叉编译;
- macOS arm64/x86_64 build;
- 最终打包、校验和与 release。
工作流源文件见 .github/workflows/release.yml。各平台 client 可不同,但应嵌入同一 server 版本。
九、release scripts 为什么分层
release/ 把操作拆为 build_server、build_linux、build_macos、build_windows、package_client、package_server、generate_checksums 等脚本,而不是一个巨大脚本。build_common 统一工作目录和版本环境。
这种分层让 CI 可以单独缓存/上传中间 artifact,也让本地维护者复现某一阶段。最终 release.sh 只编排,不承载每个平台细节。
十、协议改动的发布检查表
修改 wire protocol 时至少同步:
- C enum/struct;
- C serializer 或 deserializer;
- Java mirror enum/model;
- Java reader 或 writer;
- 双端消息处理分支;
- 两端单元测试/golden bytes;
- 最大长度与所有权规则;
- client/server version;
- 文档与兼容说明。
由于没有运行时协议协商,“半升级”不能靠对端优雅识别;发行原子性就是兼容机制。
十一、从源码学习可改进的测试层
现有测试对纯逻辑覆盖良好,但完整系统天然需要更多层:
- 协议契约测试:跨语言 golden corpus;
- 虚拟 socket 集成测试:部分读取、粘包、断开、错误长度;
- 媒体样本测试:config/session/rotation 包序列;
- 设备矩阵测试:Android API × 厂商 × 编码器;
- 退出压力测试:在每个初始化阶段注入失败,验证无挂起/泄漏;
- 长时音频测试:模拟 ppm 时钟漂移与突发 jitter。
这些测试不一定都适合每次 PR,但与源码里的主要风险一一对应。
十二、系列总复盘
15 章之后,scrcpy 可以压缩成五条可迁移原则:
- 围绕真实数据形态分层:packet 与 frame 是不同扩展边界;
- 低延迟依赖有界状态:最新帧覆盖、消息合并、音频闭环,而非无限排队;
- 跨进程协议必须双端对照:连接顺序、字节序、flags 和长度都是契约;
- 生命周期是依赖图问题:stop、interrupt、join、destroy 各自有职责;
- 把平台不稳定性关进边界层:ADB、sys、wrapper、workaround 不污染核心数据流。