在编译 mongoose-android-x86 时,NDK 提示 only position independent executables (PIE) are supported。这个报错卡了我很久,最后是靠着 TaoToken 把 Codex 的 Base URL 指到 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 创建的统一 API 通道上,再让 Codex 按 x86_64-4.9 工具链补全 examples.mk 的 CFLAGS 和 LDFLAGS 才解决。整个过程你也能复现:先到官网拿 Key,配好 Codex,把报错贴进去,按补丁改完重新 make 就行。写这篇不是让你去翻 Android NDK 的古老文档,而是记录一条能落地的排障路径:遇到 NDK 参数报错,与其自己逐行猜 Makefile,不如让 Codex 直接看报错和文件,同时把模型通道稳定下来。
1. 先把现场摆出来:examples.mk 里的 PIE 报错
1.1 NDK 报了什么错
在编译 mongoose-android-x86 的 example 程序时,NDK 的 x86_64-4.9 工具链直接抛出一句 only position independent executables (PIE) are supported,make 当场停住。这个错误在 Android 6.0 之后的 NDK 环境里几乎是必现的,它不是一个普通警告,而是平台对可执行文件格式的硬性要求:从 Android 5.0 开始,系统不再加载非 PIE 的可执行文件,NDK 的链接器一旦发现目标文件不满足 PIE 条件,就会直接拒绝生成产物。所以你会看到 make 在执行到编译或链接那一步时戛然而止,后面的一堆规则全都不会继续跑。
PIE 报错通常有两种形态。编译阶段如果 CFLAGS 里没有-fPIE,会提示(-fPIE);链接阶段如果 LDFLAGS 里没有-pie,会提示(-pie)。你看到的可能是其中一句,也可能两句先后出现,这取决于 Makefile 是先编译还是先链接。原文的 examples.mk 里恰好把这两句注释都留在了对应位置,像是原作者给后来人留的线索。顺着那两行注释往下看,就能定位到 CFLAGS 和 LDFLAGS 两个变量。
1.2 examples.mk 里缺了哪两个参数
mongoose-android-x86 的 example 程序共用一份 examples.mk。默认情况下,CFLAGS 只有-g -W -Wall这类常规选项,附带-I../..和-Wno-unused-function,里面没有任何与 PIE 相关的参数。LDFLAGS 也只有一行-Wl,-rpath-link指向 sysroot 的库目录,同样没有 PIE 相关参数。两个变量正好各缺一个开关:CFLAGS 里少-fPIE,LDFLAGS 里少-fPIE -pie。这里容易犯迷糊的地方是,很多人只给编译参数加了-fPIE,以为链接参数不用管,结果 make 在最后链接时报第二句错误,白白浪费一轮等待。
这个问题的麻烦之处在于,工程本身是可以编出 x86_64 版本的,模拟器上也跑过,但 NDK 版本一换,链接策略跟着收紧,老参数就不够用了。所以修复思路不是升级 NDK,也不是换编译器,而是在 examples.mk 的编译和链接两条参数链上各补一个开关。如果你只是想临时绕过,可以在 make 命令后面手动传CFLAGS="-fPIE" LDFLAGS="-pie",但每换一个 example 目录就得重来一次,而且不同 example 的 Makefile 还不一定都接受外部传参。最干净的做法是把参数写进 examples.mk,让make一次通过。
1.3 为什么把这件事交给 Codex
Codex 在处理「已知报错 + 已知 Makefile」的场景时,最大的优势是能同时看到报错、工具链版本和文件内容,然后对照平台文档生成最小补丁。你不需要花十分钟教它什么是 PIE,它自己能从前面的报错文本里推断出缺的是-fPIE和-pie。但要让 Codex 跑起来,得先解决模型 API 的连通问题。我的做法是用 TaoToken 把 Codex 的 Base URL 指到统一 API 通道上,这样 Codex 就能稳定调用模型。下面这一节就是配置步骤。
2. 用 TaoToken 把 Codex 接到你的环境
2.1 官网拿 Key,写进 ~/.codex/config.toml
Codex 读取的是用户目录下的~/.codex/config.toml,它不认 Claude Code 那套ANTHROPIC_BASE_URL环境变量。所以第一步是打开 TaoToken,注册后在控制台创建一个 API Key,拿到YOUR_API_KEY。注意这个 Key 不是用来填官网的,而是填进 Codex 的 provider 配置里,让 Codex 的请求走 TaoToken 的 API 通道。我的~/.codex/config.toml最终长这样:
model = "你的模型ID" # 以 TaoToken 模型广场为准 model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "YOUR_API_KEY" wire_api = "chat"这里base_url是接口地址,必须写成https://taotoken.net/api,末尾不要加/v1。配置文件里的env_key指的是环境变量名,你需要把真正的 Key 值导出到环境变量里,在终端执行:
export YOUR_API_KEY=粘贴你刚创建的Key官网页面 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 只负责注册、创建 Key、看模型广场和用量,和工具里填的 Base URL 是两回事,不要把带 UTM 的落地页地址填进任何工具的 base_url 字段。另外,env_key这个名字容易让人误以为要在 config.toml 里直接写 Key 原文,实际上 Codex 是启动时从这个环境变量读取,所以只要 shell 会话里导出了这个变量,Codex 就能正常工作。
2.2 模型 ID 去模型广场选,别自己造
我一开始想当然填了一个自己听过的模型 ID,Codex 启动后直接报 model not found。后来去 TaoToken 的模型广场(就在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 上)看了一眼,才发现模型 ID 和我预想的不一样。所以model字段一定要以模型广场为准,不要凭印象写。模型广场同时会标注上下文长度、是否支持工具调用,这两个信息会影响 Codex 能否看懂完整的 examples.mk。上下文太短的模型可能只看到片段就给出不完整的补丁,选一个上下文足够长的模型更稳妥。配置好之后,Codex 就能通过 TaoToken 的兼容通道调用模型了。接下来把编译报错和 examples.mk 贴给它,让它干活。
3. 让 Codex 改 examples.mk:补 -fPIE 和 -pie
3.1 给 Codex 的提示词怎么写
新开一个 Codex 会话,把下面这段提示词贴进去,剩下的交给 Codex:
我在编译 mongoose-android-x86 的 example 程序,NDK 是 x86_64-4.9 工具链, SYSROOT 指向 android-23/arch-x86_64。 编译报错:only position independent executables (PIE) are supported. (-fPIE / -pie) 这是 examples/examples.mk 里的相关片段: CFLAGS 里只有 -g -W -Wall 以及 -I../..、-Wno-unused-function LDFLAGS 里只有 -Wl,-rpath-link=$(SYSROOT)/usr/lib 请只修改这两个变量,补全 PIE 相关参数,保持其他编译选项不变,并输出 diff。这样写的好处是给 Codex 限定了边界:只补-fPIE和-pie,不要顺手动-O2或者加别的库。Codex 给出的结果基本就是原文注释里那两行参数的还原,但它会把参数放进对应变量,而不是让你自己猜位置。如果你不限定边界,它有时会顺手做点“优化”,比如把-Wall换成-Wextra,虽然也能编过,但会给后续对比带来噪音。
3.2 CFLAGS 的改法
Codex 给出的 CFLAGS 差异很小,在原有选项里插入一个-fPIE:
CFLAGS += -fPIE实际操作时把它和原有 CFLAGS 合并成一行也可以,比如把原来的-g -W -Wall改成-g -W -fPIE -Wall。注意这里用的是-fPIE,不是-fPIC。两者都生成位置无关代码,但-fPIC一般用于共享库,编译可执行文件时用-fPIE更符合平台预期。如果误用-fPIC,某些 NDK 版本虽然也能编过,但链接时可能出现告警,所以按 Codex 的改法最稳。补丁应用到 examples.mk 后,可以先用grep -n fPIE examples.mk确认参数已经写进去了,再执行 make。
3.3 LDFLAGS 的改法
LDFLAGS 需要同时补-ldl -fPIE -pie:
LDFLAGS += -ldl -fPIE -pie或者和原来的 rpath-link 写在一起:
LDFLAGS += -Wl,-rpath-link=$(SYSROOT)/usr/lib -ldl -fPIE -pie-ldl在某些精简例子里不是必须的,但加上不会出错,而且能让 mongoose 的dlopen相关功能正常链接。-fPIE在链接阶段同样要出现,它不是编译器的专利;-pie则明确告诉链接器输出一个 PIE 可执行文件。这两个参数少一个,之前的报错就会换着花样回来。Codex 给完 diff 后,我不会让它直接去改本机的 examples.mk,也不会让它执行 make。它只负责生成补丁,真正保存文件、重新编译都在我的终端里操作。AI 工具可以给方案,但环境的控制权要留在自己手里。
4. 顺手改掉端口和 WebSocket 地址
4.1 simplest_web_server.c 的监听端口
PIE 参数修好之后,原工程还有两个小坑要处理。examples/simplest_web_server/simplest_web_server.c里默认监听端口是 8000。如果希望 Android 浏览器直接访问http://localhost而不带端口,可以把这行改成 80:
static const char *s_http_port = "80";这里改不改不影响编译,但如果你要部署到真机上,通常都会顺手改成 80,省得每次敲端口。要注意的是,80 端口在 Android 上不一定能直接绑定,部分系统的应用进程没有权限监听 1024 以下的端口,需要 root 或者换回 8000。所以这个改动要结合你的实际运行环境决定,不要因为看到原文改成 80 就盲目照搬。Codex 同样可以直接定位到这个文件里的对应行,你只要把文件路径发给它,它会告诉你这一行的上下文,方便你决定是否改动。
4.2 index.html 的 ws 地址
examples/websocket_chat/index.html里的 WebSocket 地址默认连的是当前域名下的/ws。如果聊天服务跑在 8000 端口,而页面不是从 8000 端口打开的,握手会失败,所以要把端口显式写出来:
var ws = new WebSocket('ws://' + location.host + ':8000');如果你把 HTTP 服务也改成 80,那么这里可以保持'/ws'不变;只要端口不一致,就必须显式写清楚。客户端握手失败时通常不会在终端打印日志,只会看到页面上的聊天消息发不出去,排查起来比 PIE 报错更费劲。这两处改动和 PIE 报错没有直接关系,但既然要重新编译一次,最好一起改完,省得下次又为这一行重新走一遍 make 和 adb push。
5. 重新 make 并通过 adb 部署
5.1 编译和产物复制
改完 examples.mk 和两个源文件后,回到终端重新编译。先编 websocket_chat,再编 simplest_web_server:
cd examples/websocket_chat make clean && make cd ../simplest_web_server make clean && make这里务必执行make clean。如果之前的失败编译留下了目标文件,新的-fPIE参数可能不会完整参与链接,make 会误以为没有变更而跳过链接步骤。只有 clean 之后重新 make,才能确认补丁真的生效了。编译过程中如果看到-fPIE出现在命令行里,基本就可以放心了;如果依然报错,回到第 6 节排查。编译通过后,把产物放到共享目录:
mkdir -p /opt/share-vm/fedora23server-share/webserver cp examples/simplest_web_server/simplest_web_server /opt/share-vm/fedora23server-share/webserver/ cp examples/websocket_chat/websocket_chat /opt/share-vm/fedora23server-share/webserver/ cp examples/websocket_chat/index.html /opt/share-vm/fedora23server-share/webserver/5.2 推到 Android 设备上运行
用 adb 把整个 webserver 目录推到设备的/system/xbin/quagga/:
adb push /opt/share-vm/fedora23server-share/webserver /system/xbin/quagga/ adb shell进入设备后启动两个服务:
cd /system/xbin/quagga ./simplest_web_server & ./websocket_chat &浏览器访问http://localhost,能看到页面就说明编译和部署都通了。这一步是整个排障的验证环节:如果 PIE 参数没补对,这里根本走不进去;如果端口和 ws 地址没改对,页面能打开但聊天功能是坏的。所以建议把这一条当作最终验收用例,跑通了再关终端。
6. 排障:万一还报 PIE 或者别的
6.1 make 仍报 PIE 时先看 V=1
如果你把 Codex 给的补丁粘进 examples.mk,make还是报 only position independent executables (PIE) are supported,先跑一次make V=1,看实际执行的编译命令里有没有-fPIE和-pie。很多时候不是没加,而是 Makefile 里CFLAGS被赋值了两次,后面的覆盖了前面的。把完整 examples.mk 重新发给 Codex,让它检查是否有变量互相覆盖,比你自己逐行找快得多。另一种情况是编译过了、链接时只报-pie,说明 CFLAGS 生效了,但 LDFLAGS 里的-pie没进入最终链接命令。检查 LDFLAGS 是否被 Makefile 后续规则重写,或者确认目标规则里的编译命令确实引用了$(LDFLAGS)。Codex 给出的补丁一般会连这条规则一起检查,所以如果你是从命令行直接拼参数而不是改文件,很容易漏掉这一步。
6.2 回官网看调用量和模型广场
排障结束、服务也跑起来之后,我通常回到 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 的控制台看一眼这次排障消耗了多少量,以及到底用的是哪个模型。这里有两个实际意义:一是确认 Key 没有异常消耗,二是顺便看看模型广场里有没有上下文更长、更适合看 Makefile 的模型。如果下次再遇到类似的 NDK 报错,我会优先选上下文更长的模型,让 Codex 一次把整个 examples.mk 读完,避免它只看到片段就给出不完整的补丁。如果你还没创建 Key,现在去 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 注册并创建 API Key,再按上面的步骤配好 Codex,就能把同样的排障流程跑通。