
lunch 不显示构建列表这个问题凡是碰过源码编译环境的开发者多少都遇到过几次。明明按教程执行了lunch终端却只蹦出两行提示然后“构建目标列表”那一栏干干净净一个选项都没有回车也不知道该选什么。整个编译流程直接卡在这一步后面根本没法继续。作为接手过不少源码构建环境的开发者我可以负责任地说这个问题的根源并不复杂但现象特别能迷惑人。它不像编译报错那样会直接甩一段堆栈给你而是“静悄悄地失败”——脚本跑完了却什么都没给你列出来。这篇就是要把lunch背后的原理、不显示列表的几类常见原因、排查步骤以及我自己踩过的坑一次性说清楚。不管是刚接触系统源码编译的新人还是被这个问题反复折磨的熟手都能对照着定位到自己机器上的问题。1. 先说清楚 lunch 到底是干什么的1.1 lunch 的工作机制远不止“选一个 target”很多教程把lunch描述成“选择编译目标的命令”这没错但如果你只把它当普通命令遇到问题就很被动。lunch本质上不是一个独立的二进制程序而是一个定义在环境初始化脚本里的shell 函数。它依赖你先把整个构建环境的初始化脚本用source方式加载进当前终端函数才能真正“活”起来。当你执行lunch不带参数时它做的事情大致是先去读取产品配置目录里的构建目标定义文件把每个目标对应的编译产品名、设备名、版本变量都解析出来然后按照固定的格式渲染成一个列表最后等着你输入编号或名称。这个机制有点像餐厅里服务员递上来的菜单——厨房其实早就有能做哪些菜的清单菜单只是一个“呈现”环节。lunch不显示列表问题可能出在“菜单没印出来”也可能出在“厨房压根不知道有哪些菜谱”。理解了这个机制你排障时就不会只盯着一行命令看了而是会去检查三个环节函数是否被正确定义、配置扫描路径是否正确、解析过程有没有被错误打断。这三个环节任何一个出问题最终表现都是“列表不显示”。1.2 “不显示构建列表”到底有哪几种表现同样是“不显示列表”实际现象是有差异的而且这些差异直接指向不同原因。我把它分成三类你排查时先对照自己的情况。第一类是完全没有输出执行lunch后终端几乎没有任何反馈直接就回到命令提示符了。这种情况绝大多数是lunch压根不是那个 shell 函数——比如环境脚本没 source 成功系统里却恰好有个同名命令或别名结果执行了别的东西。第二类是打印了开头但列表区为空终端能看到Youre building on Linux之类的提示甚至能看到“Lunch menu... pick a combo”的引导文字但真正列可选项的部分却没有内容。这种情况说明脚本函数执行了配置扫描也跑了只是筛选出来的目标数为零或者中途因为某个解析错误被提前终止了。第三类是列了列表但明显不全列表只有一两项而你确定仓库里应该有十几个目标设备。这种通常是配置目录里的产品定义文件没同步完整或者文件格式/命名不符合脚本的匹配规则被过滤掉了。这三类现象的处理方向差别很大。最忌讳的是不管现象如何直接去删缓存或重装环境那样大概率把问题搞得更乱。先分清现象再往下走。2. 问题定位先分清是“卡死”还是“列表为空”2.1 执行 lunch 之后先看这几样东西拿到一台“lunch 不显示构建列表”的机器我做的第一件事不是改代码而是先盯着终端看三样东西输出内容、退出状态、光标位置。输出内容不要只看最后几行要往上翻。脚本如果中途报错错误信息往往混在环境初始化的输出里很容易被滚动窗口淹没。建议直接执行并带上退出码查看lunch; echo exit$?如果exit0说明脚本逻辑走完了列表为空是“正常地没找到目标”如果exit非 0说明哪一步直接报错中断了错误信息通常就在前面。另外注意光标位置如果执行后终端久久不返回甚至像卡住一样那就不是“不显示列表”的范畴而是脚本在某个循环或输入读取上挂住了。前者是配置/扫描逻辑的问题后者往往是终端类型或管道交互的问题处理方式完全不同。还有一个容易忽略的点lunch交互模式下它会读你的标准输入。如果你是通过脚本、管道、CI 任务去调用它而不是在一个真正的终端里跑它会读不到输入表现也像是“没有输出列表”。这种要和“真不显示”分开判断。2.2 快速确认环境初始化是否成功lunch是 shell 函数验证它是否存在直接用一个命令就能看出来type lunch如果输出是lunch is a shell function说明环境脚本加载成功如果输出是lunch is /usr/bin/lunch之类的文件路径那就要小心了——你执行的可能根本不是构建脚本里那个函数。或者更糟的情况type lunch直接提示not found那说明初始化脚本压根没有运行。很多源码仓库的构建环境里初始化脚本是一个形如envsetup.sh的文件位于仓库根目录。开发者需要用source或点号命令加载它而不是直接执行source build/envsetup.sh这里有个非常关键的区别如果直接bash envsetup.sh脚本会在一个子 shell 里执行里面定义的环境变量和函数在子 shell 退出后全部消失当前终端还是什么都没有。新手最容易在这一步踩坑。我见过不少“lunch 按下去没反应”的案例最后发现就是把source写成了bash或者干脆忘了执行这一步。确认环境函数到位之后再检查几个关键环境变量是否存在比如TARGET_PRODUCT、TARGET_BUILD_VARIANT之类的构建变量。虽然未选 target 时它们可能是空的但至少能看出初始化逻辑有没有运行到后半段。3. 几类典型原因与修复方案3.1 环境初始化脚本没有正确执行导致 lunch 函数不存在这是最常见、也最容易排查的原因但它造成的现象很有欺骗性。有时候你明明执行过初始化脚本但那是上一个终端会话里执行的新开一个终端后函数并不会残留。shell 的环境变量和函数定义只在当前进程里有效终端一关就没了。所以我每次接手别人的电脑或服务器都会先跑一下type lunch确认。修复方法很简单重新source正确的环境脚本。但要注意有些仓库的初始化脚本还有前置依赖比如必须先进入仓库根目录或者必须设置某个JAVA_HOME、ANDROID_*环境变量脚本才会完整执行。如果你发现source过程打印了报错就要先解决那些报错否则脚本可能没有执行到最后一步lunch函数依然没定义。另外有些开发者喜欢把初始化脚本写进~/.bashrc每次打开终端自动加载。这个思路本身没问题但如果你用的 shell 不是 Bash或者远程登录时走了非交互模式自动加载不一定生效。判断方式还是type lunch不要凭记忆觉得“我配置过”。3.2 产品配置目录缺失或不在预期路径扫描结果为空lunch之所以能列出构建目标是因为它在固定的目录结构里扫描一类特定命名规则的产品配置文件。这类文件通常以类似AndroidProducts.mk的文件名分布在设备相关的仓库目录里。假设你同步的源码不完整或者仓库里缺了某个 device 目录那扫描目标就会少一大截。一个很容易被忽视的场景是你换了分支分支之间目录结构有差异。比如之前的分支把设备配置放在device/某厂商/某型号/下新分支重构了目录把它挪到了vendor/某厂商/下面。初始化脚本里的扫描路径如果还是旧逻辑自然就找不到新位置的目标列表自然为空。这种情况编译环境本身是“健康”的纯粹是分支演进带来的路径漂移。检查方式就是手动看一眼配置目录是否存在、是否有正常的配置文件而不是空壳或.git目录。如果仓库同步是用某个 repo 管理工具拉的还需要确认那几个配置相关的仓库有没有被正确检出来。经常有人同步到一半中断结果目录在文件却少了好几个。修复就是补全同步或者根据实际目录路径去调整扫描配置。但我不建议你直接修改扫描脚本去适配错误的分支——先确认是不是分支本身就不完整才是正路。3.3 Python 版本与脚本不兼容解析过程异常中断这个原因我单独拿出来说是因为它在近几年的构建环境里越来越常见。构建选择脚本本身可能混合了不同版本的 Python 语法有些旧环境默认python指向 Python 2新环境默认指向 Python 3脚本在解析配置时遇到不兼容的语法就直接抛异常了。但诡异之处在于某些脚本调子脚本的时候没有把 stderr 透传到终端或者异常被 try/except 吞掉了最终你看到的不是一串红字报错而是“列表没打出来”。如果lunch执行后一切静悄悄十有八九是这种静默失败。排查方式很直接python --version python3 --version看看默认python指向哪个版本再根据构建系统要求的版本来调整。有的仓库确实要求 Python 3但你在PATH里先找到了某个旧版本导致脚本跑起来就报错。我自己的习惯是在环境初始化脚本前单独把需要的 Python 路径加到PATH前部或者用虚拟环境隔离而不是去改系统默认的python链接。乱改系统 Python 链接极容易牵连其他工具。3.4 环境变量残留与历史配置干扰列表被“过滤干净”这个原因特别隐蔽也很容易发生在“来回切换目标产品”的开发机上。lunch的列表生成逻辑里往往会读取一些现成的环境变量去过滤目标比如你设置的TARGET_PRODUCT如果非空脚本可能就不再显示交互列表而是直接尝试加载那个指定的产品配置。如果你之前编译过特定目标这些变量被写入到~/.bashrc、~/.profile或其他 shell 配置文件里每次新开终端都会自动加载。那么你再执行不带参数的lunch脚本会走“跳过列表选择、直接进入指定配置”的逻辑等同于你看到的现象就是“没有列表”。处理方法也简单检查这些 shell 配置文件里有没有写入构建变量有的话清理掉。另外可以用env命令查看当前终端的完整环境变量找到TARGET_*、LUNCH_*之类前缀的可疑变量逐个unset。但要注意这种清理只对当前终端有效如果根因是写进了配置文件一定还是要改配置文件才能根治。3.5 文件权限、软链与挂载方式引发的怪问题最后一类原因属于“环境性怪胎”排查起来最费时间但知道方向后会变得很快。比如源码目录是通过共享挂载方式访问的目录权限或属主不对脚本扫描时没有读取权限静默跳过又比如配置目录是一个软链软链指向的路径失效脚本遍历到它时直接跳过再比如某些文件是从 Windows 拷贝过来的带着CRLF换行符脚本按 Unix 行去读配置时解析失败。这类问题在 CI 机器、共享开发机、跨平台拷贝的场景里特别多。我印象很深的一次是某台服务器上源码挂载自一个共享存储所有 user 都没有写权限但读权限正常。按理说不影响lunch扫描可偏偏构建脚本启动时会在配置目录里尝试创建临时文件创建失败又不报警后续扫描直接返回空结果。最终结果是列表为空但一切“看似正常”。遇到这类情况先确认你当前用户对源码目录是不是只有读权限再看mount挂载参数和文件系统类型最后拿一个代表性配置文件试试当前用户能否正常打开读取。三个检查做完基本上问题就清楚了。4. 排障实操一步一步把问题找出来4.1 从执行过程里收集最完整的线索排障最怕信息不全所以我建议你先别急着改任何东西而是把问题现场完整复现一遍并保留输出。推荐手动执行source build/envsetup.sh lunch 21 | tee /tmp/lunch_debug.log用tee把输出同时写到文件里哪怕终端显示内容早就滚走了你也能回来慢慢看。如果lunch执行很快无输出调试时还可以打开 shell 的追踪模式bash -x build/envsetup.sh同样用21 | tee收集日志。这种方式能看到脚本每一步执行了什么命令、哪个变量被赋了什么值、哪一步之后列表循环没有再继续。日志里往往直接写着原因有可能是“目录不存在”有可能是“匹配了 0 个文件”也有可能是“Python 报错”。这一招能救回大量盲目操作的时间。我个人比较反感“一出问题就先删除目录重新同步”的做法。因为删除重拉要花很长时间而且如果根因是环境变量污染或脚本兼容问题重新同步后问题依旧。保留日志、分析日志才是性价比最高的起点。4.2 用“干净终端对照法”做定位很多lunch不显示列表的问题来源是你的交互登录环境被太多自定义配置污染了。定位时可以用一个干净环境做对照实验。先开一个不带任何配置的干净 Bashenv -i bash --noprofile --norc在这个 shell 里手动设置必要的基础变量比如HOME、PATH然后手动source环境初始化脚本再执行lunch。如果这里能正常显示列表说明问题出在你的登录环境加载了某些干扰配置如果这里也一样空那问题就在源码目录或脚本本身。这个对照法特别适合排查“我这台机器刚换了个人用之前好好的突然不行了”的场景。很多情况是某次装软件时安装程序往~/.bashrc里塞了自定义变量而这些变量正好和构建环境相冲突。用干净 shell 一跑立刻水落石出。拿到干净环境能正常工作之后再逐步把你原来的自定义配置一项一项加回来每加一项就执行一次lunch直到定位出是哪一个变量/哪一段配置导致了问题。这个过程看着笨但非常可靠。4.3 给构建环境做一次“温和的重置”如果确认当前环境已经被各种脏配置弄乱了又不想重新登录或重启机器可以按下面的顺序做一次温和重置。这个方法比直接重装环境温和得多也快得多。第一步清理可疑环境变量unset TARGET_PRODUCT TARGET_BUILD_VARIANT TARGET_BUILD_TYPE unset lunch 2/dev/null注意unset lunch只会删掉当前会话里函数定义不会伤及任何文件可以放心执行。第二步回到源码根目录重新加载一次环境cd /path/to/source/root source build/envsetup.sh第三步确认函数和目录状态type lunch ls device/ vendor/第四步重新执行lunch。这时候一般就能看到完整列表了。不到万不得已我不建议动辄删除整个 out 目录或其他构建输出目录。因为如果只是环境状态问题删除输出目录并不能解决lunch不显示列表的问题反而会影响后续增量编译的速度。真想清缓存优先考虑删除构建系统内部的状态缓存文件而不是一股脑全删。5. 常