
1. 先搞明白目标属性是从哪一行写进产物的接手这个RK3588平板定制项目时需求本身很简单把系统指纹里的vendor标识改成项目自己的版本号。ro.vendor.build.fingerprint这个属性在Android 9之后独立存在于vendor分区用于标识vendor构建指纹CTS/VTS测试里也经常要读它。我最初以为这不过是往build.prop里改一行的事结果前后折腾了整整两天踩了三个大坑才最终解决。这几天最大的收获不是成功改了个属性值而是把Android构建系统里属性如何从make变量变成产物文件里的那一行这条链路彻底梳理清楚了。这篇文章就是完整的排查与解决记录适合正在做Android BSP、ROM定制、CTS认证适配的工程师参考。1.1 fingerprint在构建链路里的真实位置很多刚接触Android系统编译的人会有一个直觉以为build.prop是源码树里维护的一个文本文件改一改再编译就行。实际上完全不是这么回事。build.prop是构建系统在编译过程中动态生成的文件它的内容来自两条路径普通属性走PRODUCT_PROPERTY_OVERRIDES、PRODUCT_VENDOR_PROPERTIES这些注入通道而fingerprint这类构建属性则是直接由make变量在生成脚本里展开输出的。具体到ro.vendor.build.fingerprint它对应的make变量叫BUILD_VENDOR_FINGERPRINT。AOSP里这个变量定义在build/make/core/sysprop.mk格式和BUILD_FINGERPRINT保持一致BUILD_FINGERPRINT : $(PRODUCT_BRAND)/$(PRODUCT_NAME)/$(PRODUCT_DEVICE):$(PLATFORM_VERSION)/$(BUILD_ID)/$(TARGET_BUILD_VARIANT):$(TARGET_BUILD_TYPE)/$(BUILD_VERSION_TAGS)也就是品牌/产品名/设备名:系统版本/构建ID/构建变体:构建类型/标签的结构。而BUILD_VENDOR_FINGERPRINT在默认情况下会继承这个值也可以被单独覆盖。每次编译时构建系统会重新计算这些变量然后在生成vendor/build.prop的脚本里执行一行类似echo ro.vendor.build.fingerprint$(BUILD_VENDOR_FINGERPRINT)的命令。所以这里有一个关键认知**build.prop是构建过程的产物不是一个可以被手动编辑后保留的源文件。**你在out目录里改的任何内容本质上和改一个编译生成的二进制文件没有区别只要对应的生成规则被重新触发改动就会瞬间蒸发。1.2 为什么vendor指纹非要独立出来说到ro.vendor.build.fingerprint就绕不开Treble架构和分区独立更新。Android 9前后Google在大规模推dynamic partitions和system-as-root核心诉求是让system分区可以独立OTA升级而vendor分区由SoC厂商单独维护。如果system升级后把整个设备的指纹都刷新了vendor里的二进制服务和HAL在做VINTF兼容性校验时就会对着一个被改过的指纹无法判断固件版本是否匹配。于是AOSP专门拆出了两个指纹ro.build.fingerprint归systemro.vendor.build.fingerprint归vendor。OTA升级system不会动vendor的指纹vendor的指纹只跟vendor分区的构建产物相关。理解了这层背景你就能明白为什么修改vendor指纹不能拿对待普通build.prop属性的思路去处理——它不是system构建产物的一部分它属于vendor分区属性生成脚本管理。2. 第一次尝试翻车直接在产物文件里改一杯make回到解放前2.1 我最初的操作方式当时的第一反应非常朴素既然要改vendor/build.prop里的这个值那就直接改呗。我进入out/target/product/rk3588_tablet/vendor/build.prop看到那行ro.vendor.build.fingerprintrk3588_tablet/rk3588_tablet/rk3588_tablet:13/TQ2A.230505.002/userdebug/release-keys用vim改成目标值然后执行make otapackage准备出整包。结果解包出来的update包里vendor/build.prop的值还是老样子。我不死心又试了直接在已生成的vendor.img上解包、改文件、重新打包刷机后getprop确实看到了新值但下一次增量编译生成vendor镜像时又被打回原形。这时候我才意识到问题不在文件内容而在生成这个文件的make变量。2.2 增量构建的产物刷新机制是怎么回事AOSP的构建系统确实支持增量编译但它判断要不要重新生成一个文件看的是依赖关系只要某个输入文件的时间戳或内容发生变化或者某个依赖目标被重建这条规则就会重新执行。vendor/build.prop的生成规则是一个由若干echo命令拼接而成的脚本它不依赖我手工修改过的那个build.prop本身而是依赖一堆product配置文件和make变量。所以当我修改了device/xxx/xxx.mk或任何产品配置后ninja重新计算依赖发现build.prop需要重生成整段脚本就会原样再跑一遍。不仅手改的文件内容没了连我后来专门去解包vendor.img做的改动也会在下次生成vendor镜像时被覆盖。这里给所有做ROM定制的朋友一个忠告永远不要试图通过修改out目录下的产物来改变最终固件行为out目录对构建系统来说就是个缓存池任何时刻都可能被清掉重来。要改就改输入不要改输出。3. 改用PRODUCT_PROPERTY_OVERRIDES也失败伪属性优先级问题3.1 第一方案产品mk里的PRODUCT_PROPERTY_OVERRIDES既然直接改产物文件不行那就回到源码配置。这类定制属性的常规做法是在device/xxx/xxx.mk里加PRODUCT_PROPERTY_OVERRIDES我也照做了PRODUCT_PROPERTY_OVERRIDES ro.vendor.build.fingerprintMyBrand/MyDevice/MyDevice:13/MyBuild/userdebug/release-keys重新编译完后查看vendor/build.prop这一行根本没出现。反而是system/build.prop里多出了一个ro.vendor.build.fingerprintMyBrand/...的属性。原因很清晰PRODUCT_PROPERTY_OVERRIDES这个变量在构建系统里对应的写入目标是system分区的build.prop或者更准确地说是构建系统的主build.prop生成流程。它不会知道也不关心你写的属性名带不带ro.vendor.前缀。哪怕属性名长得像vendor专属它也只会把它原样丢进system构建产物。虽然ro.开头的属性在init加载后全局可见但关键问题是构建脚本后续生vendor/build.prop时会再写一行正确格式的ro.vendor.build.fingerprint$(BUILD_VENDOR_FINGERPRINT)到vendor分区最终设备上的getprop结果还是会被vendor那行覆盖。3.2 PRODUCT_VENDOR_PROPERTIES写入的位置与顺序第二次尝试我换成了PRODUCT_VENDOR_PROPERTIES这个变量是官方提供的vendor分区属性注入通道PRODUCT_VENDOR_PROPERTIES ro.vendor.build.fingerprintMyBrand/MyDevice/MyDevice:13/MyBuild/userdebug/release-keys这次编译后vendor/build.prop里确实多了一行但仔细看值发现还是默认的rk3588_tablet/...我写的新值被覆盖了。这就触及了最核心的机制vendor/build.prop的生成脚本不是只处理一次属性写入。它的执行顺序大致是先把PRODUCT_VENDOR_PROPERTIES里展开的所有普通属性写入文件然后再执行固定的一行echo ro.vendor.build.fingerprint$(BUILD_VENDOR_FINGERPRINT)。后者是构建系统专门为pseudo property伪属性预留的输出语句写在脚本后面天然覆盖前面写入的同名属性。在AOSP里像fingerprint这类由构建变量直接决定的属性不能当普通属性硬塞必须通过覆盖对应的make变量来修改。下面这个表可以直观看出不同写入方式的差异写入方式是否会进入vendor/build.prop实际效果手改out产物是但下次构建被覆盖不生效PRODUCT_PROPERTY_OVERRIDES否进入system/build.prop不生效PRODUCT_VENDOR_PROPERTIES是但会被后写的变量行覆盖不生效PRODUCT_BUILD_PROP_OVERRIDES覆盖BUILD_VENDOR_FINGERPRINT是且是唯一生成来源生效4. 完整排查链路从build.prop反追到Makefile变量的赋值时机4.1 从out目录入手先确认写进文件的值来自身份连续失败两次之后我决定静下心从头排查。第一步是确认属性最终落地的文件和当前设备值# 查看设备当前值 adb shell getprop ro.vendor.build.fingerprint # 在out产物里搜 grep -rn ro.vendor.build.fingerprint out/target/product/rk3588_tablet/vendor/ # 在源码构建系统里搜这个属性名的来源 grep -rn ro.vendor.build.fingerprint build/make/core/ --include*.mk结果很快就明确了这行属性只出现在构建规则里的echo语句中而且echo的内容是$(BUILD_VENDOR_FINGERPRINT)变量展开。没有任何一个源码目录维护build.prop源文件来提供这个值。所以问题的答案已经呼之欲出——要改这个属性必须改这个变量本身。4.2 追BUILD_VENDOR_FINGERPRINT变量定义接着追变量的定义位置。在build/make/core/sysprop.mk里能看到BUILD_FINGERPRINT的完整定义以及BUILD_VENDOR_FINGERPRINT的默认继承逻辑。如果产品配置里没有显式覆盖BUILD_VENDOR_FINGERPRINT就等于BUILD_FINGERPRINT如果显式设置过则使用独立值。再往下看product配置的加载顺序就能理解为什么不能简简单单在device.mk里写一行BUILD_VENDOR_FINGERPRINT : MyFingerprint就完事。因为build/core的mk文件在include我们产品mk之后还会对一堆全局变量做二次赋值或默认值填充如果你的赋值时机不对很可能被后面的逻辑覆盖掉。而官方留给产品定制方的覆盖入口就是PRODUCT_BUILD_PROP_OVERRIDESPRODUCT_BUILD_PROP_OVERRIDES \ BUILD_VENDOR_FINGERPRINTMyBrand/MyDevice/MyDevice:13/MyBuild/userdebug/release-keys它的原理是在构建系统完成所有默认定义之后构建build.prop的最后阶段才把覆盖值压进去。相当于一个事后覆盖通道专门用来处理这种由普通make变量生成的pseudo property。4.3 别忽略make变量展开和include顺序的细节这个过程中我踩了一个很小的坑值得单独说一下PRODUCT_BUILD_PROP_OVERRIDES的值是一个变量名值的字符串不是直接给BUILD_VENDOR_FINGERPRINT赋值。很多人在这里写错写成PRODUCT_BUILD_PROP_OVERRIDES BUILD_FINGERPRINTxxx又抱怨没生效先检查是不是把变量名拼错了。另外如果值里包含引号、空格或冒号需要注意转义。fingerprint本身是单行的但PRODUCT_BUILD_PROP_OVERRIDES的解析过程会把整个字符串当作一个词传给构建脚本空格会被吞掉或者导致解析异常。我建议值里不要带多余空格直接拼紧凑一些。5. 正确解决办法覆盖BUILD_VENDOR_FINGERPRINT变量并清理旧产物5.1 在产品mk中覆盖BUILD_VENDOR_FINGERPRINT最终的修复方案就是在产品的mk文件里加上一行覆盖然后重新生成vendor镜像。我实际使用的写法# device/xxx/rk3588_tablet/device.mk PRODUCT_BUILD_PROP_OVERRIDES \ BUILD_VENDOR_FINGERPRINTMyBrand/MyDevice/MyDevice:$(PLATFORM_VERSION)/$(BUILD_ID)/$(TARGET_BUILD_VARIANT):user/$(BUILD_VERSION_TAGS)这里用变量去拼部分字段而不是写死所有内容是为了确保fingerprint里的系统版本、构建ID这些字段不会因为后续升级而和真实构建信息脱节。PLATFORM_VERSION会展开成Android大版本号比如13BUILD_ID是当次构建的IDTARGET_BUILD_VARIANT是user/userdebug/engBUILD_VERSION_TAGS是release-keys或test-keys。如果你的项目希望brand/name/device这几个字段也是动态的可以直接沿用$(PRODUCT_BRAND)、$(PRODUCT_NAME)、$(PRODUCT_DEVICE)变量不需要写死厂商名。但如果你要伪装成某个特定品牌去过兼容性测试那这几个字段就需要显式写死。5.2 清理与验证别让增量编译掩盖真实值改完device.mk之后直接make发现vendor/build.prop里还是旧值。这是增量构建的又一个坑虽然product配置变了但ninja可能因为中间产物的依赖关系没被正确触发或者时间戳判断粒度问题没有重跑vendor build.prop的生成规则。保险起见我手动删掉相关中间产物和最终产物rm -f out/target/product/rk3588_tablet/vendor/build.prop rm -rf out/target/product/rk3588_tablet/obj/ETC/vendor_build_prop_intermediates然后重新编译vendor镜像source build/envsetup.sh lunch rk3588_tablet-userdebug make -j16 vendorimage注意这里用的是vendorimage而不是全量编译速度会快很多。等镜像编译完成检查产物文件里的值是否已经是目标值然后刷机验证adb shell getprop ro.vendor.build.fingerprint adb shell getprop ro.build.fingerprint第一个值应该是我在BUILD_VENDOR_FINGERPRINT里指定的内容第二个值保持原来的system指纹不变。两个指纹互相独立各归各的分区属性这说明修改彻底生效了。5.3 连带问题VINTF校验和vendor服务起不来本来以为到这里就收工了结果重启设备后发现一部分vendor HAL服务起不来logcat里反复出现和VINTF校验相关的错误类似VINTF parse error: Can not get vintf object android.hardware.foo1.0 process crashed原因是我的设备VINTF manifest或者compatibility matrix里记录了构建指纹相关的信息。Android的VINTFVendor Interface对象会结合当前构建的fingerprint做固件版本校验一旦指纹变了VINTF对象校验失败依赖它的vendor服务就会被拒绝启动。这个问题有两种处理办法。一种是把vendor/etc/vintf/manifest.xml里的指纹信息同步改成新值然后重新生成vendor镜像另一种是重新生成整个设备的VINTF object再用vintf相关工具验证确保manifest和实际构建指纹一致。我这里因为指纹格式本身没变只是值变了所以同步manifest后重新编译就恢复了正常。改一个属性引发一连串VINTF问题是改fingerprint时最常见的连带反应。如果你改完后发现HAL服务起不来第一优先级检查VINTF不要以为是sepolicy或者其他权限问题。6. 这次排查给我的构建系统认知升级6.1 属性写入不止一条路径伪属性必须走变量覆盖这次经历让我彻底搞清楚了Android构建系统里属性注入的几条路径。普通属性看分区选通道写system分区用PRODUCT_PROPERTY_OVERRIDES写vendor分区用PRODUCT_VENDOR_PROPERTIES还有对应system_ext、product分区的PRODUCT_SYSTEM_EXT_PROPERTIES和PRODUCT_PRODUCT_PROPERTIES。而fingerprint这类由构建变量生成的pseudo property则必须通过PRODUCT_BUILD_PROP_OVERRIDES覆盖make变量。这里再补充一个很容易被忽略的事如果自定义了一个全新的vendor属性光写进build.prop还不够还要在system/sepolicy/private/property_contexts里给它配置属性上下文。否则init在加载属性时找不到匹配的context属性会被静默丢弃表现为getprop始终拿不到值。这个坑我在更早的项目里踩过这次虽然没有遇到但如果你在做类似的事情一定要记得检查。6.2 排查build.prop相关问题的通用方法经历过这次折腾我总结出一套通用排查流程再遇到属性改不生效的问题基本能一次定位先用adb shell getprop确认设备当前实际值。在out产物目录里搜属性名确认它最终写入的是哪个build.prop文件。用grep -rn 属性名 build/make/core/ device/ vendor/判断这个属性是普通注入还是变量生成的pseudo property。如果是变量生成的找到对应的make变量定义确认赋值时机。用PRODUCT_BUILD_PROP_OVERRIDES覆盖变量或者根据分区选择对应的PRODUCT_*_PROPERTIES注入通道。清理中间产物重新编译对应分区镜像。刷机验证同时留意VINTF、sepolicy这类连带约束。这套流程的核心顺序是先判断属性类型再选择修改通道而不是拿到属性名就直接往product mk里塞。6.3 几个容易被忽略的细节最后分享几个实际工作中容易踩到的细节。第一不要改build/make/core下的全局文件。有些同事图省事直接改sysprop.mk里BUILD_FINGERPRINT的定义来定制指纹这样做虽然能生效但会影响所有使用这套构建系统的产品后面做多项目维护时一定会打架。第二不同Android版本的构建规则有差异。Android 9引入vendor指纹独立后Android 10引入了system_ext分区Android 11之后dynamic partitions和APEX的引入又让build.prop的生成规则有了不少变化。如果你用的是低版本Android别完全套用这篇的路径先看一眼对应版本源码里的sysprop.mk和Makefile再动手。第三增量构建有时候不会立刻重跑build.prop的生成规则改完变量发现没生效时不要慌删掉obj/ETC/*_build_prop_intermediates这类中间产物目录再重新编译对应分区镜像或者干脆单独跑一次make vendorimage强制刷新。这个操作几乎能解决百分之九十的配置改了不生效问题。结合这次经历我个人的体会是别把修改系统属性的思路停留在改文件层面构建系统是变量驱动的找到变量、选对覆盖通道才是长久有效的方案。以后再做任何ROM定制哪怕只是改一个不起眼的小属性我也建议先花两分钟追一下它的生成来源能省下后面好几轮的无效编译时间。