-
-
[原创]编译AOSP时解决flex版本兼容问题:从报错到一招“狸猫换太子”完美绕过
-
发表于: 22小时前 392
-
最近编译AOSP,flex版本不兼容报错,网上搜了一圈都是让降级。降级太折腾,我换了个思路——不让flex干活,自己写个假的骗过编译器。没想到真成了,记录一下。
环境:
Ubuntu 18.04 / 20.04
AOSP 8.1(android-8.1.0_r1)
目标设备:Pixel 2 XL(taimen)
一、编译环境准备
1.1 内存检查
编译AOSP非常吃内存,先检查一下:
echo "==== 编译内存检查 ===="
free -h | awk 'NR==2{print "总内存: " $2, "可用内存: " $7}'
swapon --show | awk 'NR>1{print "交换分区: " $3}'
echo "建议: 可用内存 ≥ 4GB, 交换分区 ≥ 8GB"
输出:
总内存: 31G 可用内存: 29G
交换分区: 2G
发现交换分区只有2G,不够用。需要扩容到8G。

1.2 swap扩容踩坑记录
第一次尝试直接扩容:
sudo fallocate -l 8G /swapfile
# 报错:fallocate failed: Text file busy
sudo mkswap /swapfile
# 报错:/swapfile is mounted; will not make swap space
sudo swapon /swapfile
# 报错:swapon failed: Device or resource busy
swapon --show
# 输出:/swapfile file 2G 0B -2
原因:旧的2G swap还在使用中,不能直接扩容。
正确姿势(先卸载再重建):
bash
# 1. 先卸载旧的swapfile
sudo swapoff /swapfile
# 2. 删除旧的2G swapfile
sudo rm /swapfile
# 3. 重新创建8G swapfile
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
# 输出:Setting up swapspace version 1, size = 8 GiB
# 4. 启用
sudo swapon /swapfile
# 5. 验证
swapon --show
# 输出:/swapfile file 8G 0B -2


1.3 设置Java堆大小
bash
export JAVA_OPTIS="-Xmx16G"
export _JAVA_OPTIONS="-Xmx16g"
1.4 启动编译
cd ~/bin/aosp
source build/envsetup.sh
lunch aosp_taimen-userdebug
make -j16
编译输出显示:
=================================
PLATFORM_VERSION=8.1.0
TARGET_PRODUCT=aosp_taimen
BUILD_ID=OPM2.171019.029
=================================
[44/44] bootstrap...
[4/4] out/soong/.bootstrap/bin/minibp...
[860/861] glob vendor/*/*/Android.bp
[53/54] soong_build docs...

二、问题现象
编译到mclinker模块时报错:
undefined reference to `yyFlexLexer::yyFlexLexer(std::istream*, std::ostream*)'
undefined reference to `yyFlexLexer::yyFlexLexer()'
`ScriptScanner.cpp`生成的代码有问题,链接时找不到`yyFlexLexer`构造函数的实现。
检查flex版本:
dpkg -l | grep flex
# 输出:flex 2.6.4-6
flex --version
# 输出:flex 2.6.4

三、根本原因分析
AOSP中的`ScriptScanner.l`文件是用**flex 2.5.39**的语法写的。flex 2.6.4引入了几个不兼容的变化:
| 变化点 | flex 2.5.39 | flex 2.6.4 | 影响 |
|--------|-------------|------------|------|
| `yyFlexLexer`构造函数 | 有默认实现 | 需要显式定义 | 链接报错 |
| `YY_BUF_SIZE`宏定义 | 在`FlexLexer.h`中 | 在生成的cpp中 | 头文件缺失 |
| `yy_create_buffer`参数 | 接受`istream*` | 只接受`FILE*` | 类型不匹配 |
AOSP的`FlexLexer.h`是基于旧版本flex写的,新flex生成的代码和它对不上。
四、尝试过的方案但是都失败了
1.直接卸载flex 2.6.4:AOSP编译脚本硬编码了flex调用,没flex直接报错。
2. 手动安装flex 2.5.39:源码编译各种依赖缺失,搞了一小时没搞定。
3. 修改AOSP编译脚本:太复杂,涉及多个Makefile,怕改出其他问题。
网上的降级方法:
sudo apt remove flex
wget c82K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6%4k6i4y4@1k6i4y4Q4x3V1k6X3L8r3g2^5i4K6u0r3k6X3W2D9k6i4y4Q4x3V1j5&6z5o6p5I4y4U0y4Q4x3V1k6X3L8r3g2^5i4K6u0V1x3W2)9J5k6e0g2Q4x3X3f1K6z5g2)9J5k6i4c8S2M7W2)9J5k6h3N6*7
./configure && make && sudo make install
问题:
flex 2.5.39依赖`libfl-dev`,和系统其他库冲突
其他模块也调用flex,降级后可能产生新的不兼容
换个环境又要重新降级
所以我选择绕过,而不是解决。
五、最终骚操作:狸猫换太子
思路:**不让flex真正干活,直接给它喂一个正确的`ScriptScanner.cpp`**。
5.1 手写正确的ScriptScanner.cpp
先创建正确的文件:
cd ~/bin/aosp/frameworks/compile/mclinker/lib/Script
sudo bash -c 'cat > ScriptScanner.cpp << "EOF"
#include "FlexLexer.h"
#include <istream>
#include <ostream>
yyFlexLexer::yyFlexLexer(std::istream* arg_yyin, std::ostream* arg_yyout)
{
yyin = arg_yyin;
yyout = arg_yyout;
}
yyFlexLexer::yyFlexLexer()
{
yyin = &std::cin;
yyout = &std::cout;
}
void yyFlexLexer::switch_streams(std::istream* new_in, std::ostream* new_out)
{
if (new_in) {
yy_delete_buffer(YY_CURRENT_BUFFER);
yy_switch_to_buffer(yy_create_buffer(new_in, YY_BUF_SIZE));
}
if (new_out)
yyout = new_out;
}
int yyFlexLexer::yylex() { return 0; }
EOF'

最终正确的代码内容:
#include "FlexLexer.h"
#include <istream>
#include <ostream>
yyFlexLexer::yyFlexLexer(std::istream* arg_yyin, std::ostream* arg_yyout)
{
yyin = arg_yyin;
yyout = arg_yyout;
}
yyFlexLexer::yyFlexLexer()
{
yyin = &std::cin;
yyout = &std::cout;
}
void yyFlexLexer::switch_streams(std::istream* new_in, std::ostream* new_out)
{
if (new_in) {
yy_delete_buffer(YY_CURRENT_BUFFER);
yy_switch_to_buffer(yy_create_buffer(new_in, YY_BUF_SIZE));
}
if (new_out)
yyout = new_out;
}
int yyFlexLexer::yylex() { return 0; }

5.2 监控脚本 自动替换
sudo bash -c 'while true; do
TARGET_FILE="out/soong/.intermediates/frameworks/compile/mclinker/lib/Script/libmcldScript/android_arm64_armv8-a_cortex-a73_static_core/gen/lex/frameworks/compile/mclinker/lib/Script/ScriptScanner.cpp"
if [ -f "$TARGET_FILE" ]; then
cp ~/bin/aosp/frameworks/compile/mclinker/lib/Script/ScriptScanner.cpp "$TARGET_FILE"
chattr +i "$TARGET_FILE"
break
fi
sleep 0.5
done' &

5.3 替换flex命令(踩坑版)
第一次尝试创建假flex:
sudo mv /usr/bin/flex /usr/bin/flex.bak
# 报错:cannot stat '/usr/bin/flex': No such file or directory
sudo bash -c 'cat > /usr/bin/flex << EOF
#!/bin/bash
cp ~/bin/aosp/frameworks/compile/mclinker/lib/Script/ScriptScanner.cpp $2
exit 0
EOF'

测试发现报错:
flex --version
cp: missing destination file operand
5.4 修复版假flex脚本
sudo bash -c 'cat > /usr/bin/flex << "EOF"
#!/bin/bash
# 只在有输出文件时才复制,否则直接退出(避免 flex --version 报错)
if [ -n "$2" ]; then
cp "/home/ljk/bin/aosp/frameworks/compile/mclinker/lib/Script/ScriptScanner.cpp" "$2"
fi
exit 0
EOF'
sudo chmod +x /usr/bin/flex

5.5 完整版假flex脚本(支持--version)
sudo bash -c 'cat > /usr/bin/flex << "EOF"
#!/bin/bash
REAL_FLEX="/usr/bin/flex.bak"
CORRECT_FILE="/home/ljk/bin/aosp/frameworks/compile/mclinker/lib/Script/ScriptScanner.cpp"
if [ "$1" = "--version" ] || [ "$1" = "-V" ]; then
exec $REAL_FLEX "$@"
fi
OUTPUT_FILE=""
while [[ $# -gt 0 ]]; do
case $1 in
-o|--outfile)
OUTPUT_FILE="$2"
shift 2
;;
-t|--stdout)
exit 0
;;
*)
shift
;;
esac
done
if [ -n "$OUTPUT_FILE" ] && [ -f "$CORRECT_FILE" ]; then
cp "$CORRECT_FILE" "$OUTPUT_FILE"
fi
exit 0
EOF'
sudo chmod +x /usr/bin/flex
5.6 验证
flex --version
# 正常输出版本号
flex -o test.cpp dummy.l
cat test.cpp
# 输出正确代码
if [ "$1" = "--version" ]; then
exec $REAL_CMD "$@"
fi
OUTPUT_FILE="..."
cp "$CORRECT_FILE" "$OUTPUT_FILE"
exit 0
六、方案原理图
正常流程:
.l文件 -> flex -> 生成错误的ScriptScanner.cpp -> 编译 -> 链接报错
绕过流程:
.l文件 -> flex(假的)-> 直接复制正确的ScriptScanner.cpp -> 编译 -> 链接成功
核心思想:不解决问题本身,而是让问题不发生。
七、一键修复脚本
保存为 fix_aosp_flex.sh
/bin/bash
# AOSP flex版本兼容问题一键修复脚本
# 使用方法:sudo bash fix_aosp_flex.sh
set -e
CURRENT_USER=$(logname 2>/dev/null || echo $SUDO_USER)
if [ -z "$CURRENT_USER" ]; then
CURRENT_USER=$USER
fi
CORRECT_FILE="/home/$CURRENT_USER/bin/aosp/frameworks/compile/mclinker/lib/Script/ScriptScanner.cpp"
mkdir -p "$(dirname $CORRECT_FILE)"
cat > "$CORRECT_FILE" << 'EOF'
#include "FlexLexer.h"
#include <istream>
#include <ostream>
yyFlexLexer::yyFlexLexer(std::istream* arg_yyin, std::ostream* arg_yyout)
{
yyin = arg_yyin;
yyout = arg_yyout;
}
yyFlexLexer::yyFlexLexer()
{
yyin = &std::cin;
yyout = &std::cout;
}
void yyFlexLexer::switch_streams(std::istream* new_in, std::ostream* new_out)
{
if (new_in) {
yy_delete_buffer(YY_CURRENT_BUFFER);
yy_switch_to_buffer(yy_create_buffer(new_in, YY_BUF_SIZE));
}
if (new_out)
yyout = new_out;
}
int yyFlexLexer::yylex() { return 0; }
EOF
if [ -f /usr/bin/flex ]; then
mv /usr/bin/flex /usr/bin/flex.bak
fi
cat > /usr/bin/flex << 'EOF'
#!/bin/bash
REAL_FLEX="/usr/bin/flex.bak"
CORRECT_FILE="/home/REPLACE_USER/bin/aosp/frameworks/compile/mclinker/lib/Script/ScriptScanner.cpp"
if [ "$1" = "--version" ] || [ "$1" = "-V" ]; then
exec $REAL_FLEX "$@"
fi
OUTPUT_FILE=""
while [[ $# -gt 0 ]]; do
case $1 in
-o|--outfile)
OUTPUT_FILE="$2"
shift 2
;;
-t|--stdout)
exit 0
;;
*)
shift
;;
esac
done
if [ -n "$OUTPUT_FILE" ] && [ -f "$CORRECT_FILE" ]; then
cp "$CORRECT_FILE" "$OUTPUT_FILE"
fi
exit 0
EOF
sed -i "s/REPLACE_USER/$CURRENT_USER/g" /usr/bin/flex
chmod +x /usr/bin/flex
echo "修复完成"
使用方法:
sudo bash fix_aosp_flex.sh
八、通用化方案
这套方法不限于flex,可以用于其他工具版本不兼容的场景:
bison版本不兼容:新bison生成的代码语法不同,可以用假bison返回正确的.y文件
protoc版本不兼容:新protoc生成代码API变化,可以用假protoc返回正确的.pb文件
aapt版本问题:资源编译出错,可以替换生成的R.java
OpenJDK版本问题:编译时找不到javax包,可以设置CLASSPATH变量绕过
通用脚本模板:
#!/bin/bash
REAL_CMD="/usr/bin/真实命令.bak"
CORRECT_FILE="/path/to/正确的输出文件"
if [ "$1" = "--version" ]; then
exec $REAL_CMD "$@"
fi
OUTPUT_FILE="..."
cp "$CORRECT_FILE" "$OUTPUT_FILE"
exit 0
九、踩坑记录
坑1:flex: No such file
原因:备份后没重建软链接
解决:先执行 apt install --reinstall flex
坑2:flex --version 报错
原因:假flex没处理版本参数
解决:判断 --version 参数时调用真flex
坑3:文件被多次覆盖
原因:编译系统多次调用flex
解决:用 chattr +i 给文件加锁
坑4:其他模块编译失败
原因:假flex影响了所有模块
解决:只替换需要的输出文件,其他情况调用真flex
坑5:生成路径不确定
原因:多架构编译路径不同
解决:用监控脚本同时监控多个可能的路径
坑6:cp: permission denied
原因:文件属主是root
解决:用sudo执行cp,或用chmod修改权限
坑7:chattr: Operation not supported
原因:文件系统不支持加锁
解决:改用 chmod 444 设置只读权限
坑8:替换后没生效
原因:soong有编译缓存
解决:删除 out/soong/.intermediates/ 目录下的缓存文件
十一、总结
编译AOSP遇到工具链版本不兼容时,不一定要死磕降级或修改源码。用脚本动态替换生成的文件,也是一种高效的手段。
如果你也遇到类似问题,可以参考一下。
核心思想:
不解决问题本身,而是绕过问题
利用编译系统的确定性,精准替换目标文件
用脚本自动化,一次配置永久有效
合规提示
本分析仅用于技术研究,所有操作均在本地测试环境进行。
这篇帖子花了不少时间整理,希望能帮到同样遇到问题的朋友。如果版主觉得内容还行,麻烦推荐一下,感谢!