EN

运行时执行模型

了解 Func 如何编译应用与执行顺序。。

更新于

func 会先把应用声明编译成可执行结构,再根据每次传入的 argv 完成一次调用。装饰器只描述模块、命令、字段和处理器之间的关系;真正的实例创建与方法调用都发生在运行时。

createApp() 面向进程应用:编译完成后,bootstrap() 执行一次并设置进程退出码。createInvoker() 同样只编译一次,但可以反复调用 invoke(),每次返回自己的结果与退出码。

Func 运行时管线
  1. 编译应用

    展开模块并校验声明,生成命令图和每个节点的 option 规则。

  2. 解析调用

    解析 argv,匹配命令路径,并确定唯一的 handler 或 option action。

  3. 准备作用域

    创建本次调用的 Context、依赖容器,并激活需要的模块与 provider。

  4. 执行管线

    完成字段绑定与校验,然后调用所选入口。

  5. 处理与释放

    处理异常,确定结果与退出码,并释放本次调用创建的实例。

编译应用

调用 createApp()createInvoker() 时,func 会从根模块开始展开 imports,检查 Provider 的声明与可见性,并收集 Commands 与 options。随后,Command path、Handler path 与 alias 会被编译成一张命令图。

重复 token、无效 export、循环 import、冲突路径等应用结构问题会在这里直接报错。编译阶段只生成和校验结构,不会提前创建 Module、Command 或 Provider 实例。

模块与依赖的完整规则见模块

解析一次调用

每次调用都会收到 shell 已经处理好的 argv 字符串数组。func 根据不同命令节点可见的 options 分别解析 argv,再用剩余的位置参数匹配命令图。因此,只要 option 在目标节点可见,它可以写在 Command path 前面或后面;-- 之后的内容则全部作为位置输入保留。

入口大致按照以下顺序确定:

  1. 优先选择匹配最深且唯一的具名 Command 或 Handler path。
  2. 没有具名入口时,尝试根级 option action。
  3. 再尝试 @CommandMajor@CommandMissing 或根命令的默认行为。

一次调用最终只会选中一个入口。多个 action 同时出现、同样具体的多个候选或目标节点不可见的 option 都会产生解析错误,而不会依赖注册顺序猜测。

普通字段 option 只提供数据;由 @Handler({ flag })@OptionCommand() 声明的 action 会改为执行对应入口。action 被选中时,不会校验与它无关的业务字段,因此 deploy --help 不会因为缺少 deploy 所需参数而失败。

创建本次调用的作用域

func 为每次调用创建新的 Context 和运行时依赖容器。根模块始终属于当前调用;命令匹配成功后,运行时还会激活直接声明该命令的 owner Module。仅用于连接 imports 的中间模块不会自动成为执行作用域。

class 与 factory Provider 在第一次被请求时创建,并在当前调用内复用;下一次调用会得到新的实例。value Provider 则直接返回注册时提供的值。未被当前入口使用的 Provider 不会创建。

模块会在执行入口前完成初始化,其他依赖的 onInit() 在实例首次创建时运行。option Provider 会先完成字段绑定,再进入 onInit(),因此不要在构造函数中假设 argv 已经写入字段。

执行所选入口

成功解析后,拦截器从外到内包裹本次调用:应用级 → owner Module → Command。内层执行完成后,结果再按相反方向返回给外层拦截器。

普通 Command 的核心执行顺序是:

  1. 创建 Command 及其构造函数依赖。
  2. 写入字段值与默认值,并执行 required、validator 和跨字段 constraint。
  3. 生成冻结的 Args 快照,运行 Command 的 onInit()
  4. 解析 Handler 参数并调用唯一的 Handler。

Handler 可以同步返回,也可以返回 Promise;两者会进入相同的结果处理流程。Args 包含原始 argv、实际与标准命令路径、剩余位置输入和归一化 options;Context 则提供当前命令、IO、cwd、signal、结果输出方式与退出码。

参数注入见处理器参数,资源的初始化与释放规则见错误与生命周期

异常处理与释放

运行时异常会交给当前已经激活的 onError。已经选中命令时,按 Command → direct-owner Module → root Module 的方向处理;onError 正常返回表示异常已处理,继续抛出错误则上交给外层。解析失败时尚无 Command 与 owner Module,此时错误只会进入根模块。

没有被 onError 处理的常规运行时错误会写入 stderr,并让调用以退出码 1 结束。onError 也可以通过 Context 调整退出码并控制输出流。详细传播规则见错误处理

业务执行后,func 会依次查找本次调用中已经激活的 Command、Module 和 Provider。Command 与 Module 按作用域激活顺序逆序释放,Provider 按创建顺序逆序释放。