diff --git "a/2025/17. \345\256\236\350\256\255\350\220\245\345\277\203\345\276\227\345\210\206\344\272\253/share.md" "b/2025/17. SPX-Algorithm\357\274\232\346\236\204\345\273\272\345\244\232\346\250\241\346\200\201\346\220\234\347\264\242\346\234\215\345\212\241\347\232\204\344\270\200\344\272\233\345\277\203\345\276\227/share.md" similarity index 100% rename from "2025/17. \345\256\236\350\256\255\350\220\245\345\277\203\345\276\227\345\210\206\344\272\253/share.md" rename to "2025/17. SPX-Algorithm\357\274\232\346\236\204\345\273\272\345\244\232\346\250\241\346\200\201\346\220\234\347\264\242\346\234\215\345\212\241\347\232\204\344\270\200\344\272\233\345\277\203\345\276\227/share.md" diff --git "a/2025/16. \344\273\243\347\240\201\344\270\215\346\230\257\346\240\270\345\277\203\357\274\232\344\273\216 XLink \351\241\271\347\233\256\347\234\213\344\272\247\345\223\201\345\274\200\345\217\221\347\232\204\345\206\263\347\255\226\345\261\202\346\254\241/content.md" "b/2025/18. \344\273\243\347\240\201\344\270\215\346\230\257\346\240\270\345\277\203\357\274\232\344\273\216 XLink \351\241\271\347\233\256\347\234\213\344\272\247\345\223\201\345\274\200\345\217\221\347\232\204\345\206\263\347\255\226\345\261\202\346\254\241/content.md" similarity index 100% rename from "2025/16. \344\273\243\347\240\201\344\270\215\346\230\257\346\240\270\345\277\203\357\274\232\344\273\216 XLink \351\241\271\347\233\256\347\234\213\344\272\247\345\223\201\345\274\200\345\217\221\347\232\204\345\206\263\347\255\226\345\261\202\346\254\241/content.md" rename to "2025/18. \344\273\243\347\240\201\344\270\215\346\230\257\346\240\270\345\277\203\357\274\232\344\273\216 XLink \351\241\271\347\233\256\347\234\213\344\272\247\345\223\201\345\274\200\345\217\221\347\232\204\345\206\263\347\255\226\345\261\202\346\254\241/content.md" diff --git "a/2025/18.X\347\273\230\345\233\276-\346\210\221\344\273\254\346\230\257\345\246\202\344\275\225\350\256\251AI\346\233\264\345\245\275\347\232\204\350\236\215\345\205\245\346\210\221\344\273\254\347\232\204\344\272\247\345\223\201\347\232\204/image1.png" "b/2025/18.X\347\273\230\345\233\276-\346\210\221\344\273\254\346\230\257\345\246\202\344\275\225\350\256\251AI\346\233\264\345\245\275\347\232\204\350\236\215\345\205\245\346\210\221\344\273\254\347\232\204\344\272\247\345\223\201\347\232\204/image1.png" deleted file mode 100644 index 7f30f3e..0000000 Binary files "a/2025/18.X\347\273\230\345\233\276-\346\210\221\344\273\254\346\230\257\345\246\202\344\275\225\350\256\251AI\346\233\264\345\245\275\347\232\204\350\236\215\345\205\245\346\210\221\344\273\254\347\232\204\344\272\247\345\223\201\347\232\204/image1.png" and /dev/null differ diff --git "a/2025/18.X\347\273\230\345\233\276-\346\210\221\344\273\254\346\230\257\345\246\202\344\275\225\350\256\251AI\346\233\264\345\245\275\347\232\204\350\236\215\345\205\245\346\210\221\344\273\254\347\232\204\344\272\247\345\223\201\347\232\204/image2.png" "b/2025/18.X\347\273\230\345\233\276-\346\210\221\344\273\254\346\230\257\345\246\202\344\275\225\350\256\251AI\346\233\264\345\245\275\347\232\204\350\236\215\345\205\245\346\210\221\344\273\254\347\232\204\344\272\247\345\223\201\347\232\204/image2.png" deleted file mode 100644 index 792d15a..0000000 Binary files "a/2025/18.X\347\273\230\345\233\276-\346\210\221\344\273\254\346\230\257\345\246\202\344\275\225\350\256\251AI\346\233\264\345\245\275\347\232\204\350\236\215\345\205\245\346\210\221\344\273\254\347\232\204\344\272\247\345\223\201\347\232\204/image2.png" and /dev/null differ diff --git "a/2025/17. llpyg: LLGo \345\277\253\351\200\237\351\233\206\346\210\220 Python \347\224\237\346\200\201\347\232\204\346\241\245\346\242\201/content.md" "b/2025/19. llpyg: LLGo \345\277\253\351\200\237\351\233\206\346\210\220 Python \347\224\237\346\200\201\347\232\204\346\241\245\346\242\201/content.md" similarity index 100% rename from "2025/17. llpyg: LLGo \345\277\253\351\200\237\351\233\206\346\210\220 Python \347\224\237\346\200\201\347\232\204\346\241\245\346\242\201/content.md" rename to "2025/19. llpyg: LLGo \345\277\253\351\200\237\351\233\206\346\210\220 Python \347\224\237\346\200\201\347\232\204\346\241\245\346\242\201/content.md" diff --git "a/2025/20.X\347\273\230\345\233\276-\346\210\221\344\273\254\346\230\257\345\246\202\344\275\225\350\256\251AI\346\233\264\345\245\275\347\232\204\350\236\215\345\205\245\346\210\221\344\273\254\347\232\204\344\272\247\345\223\201\347\232\204/image1.png" "b/2025/20.X\347\273\230\345\233\276-\346\210\221\344\273\254\346\230\257\345\246\202\344\275\225\350\256\251AI\346\233\264\345\245\275\347\232\204\350\236\215\345\205\245\346\210\221\344\273\254\347\232\204\344\272\247\345\223\201\347\232\204/image1.png" new file mode 100644 index 0000000..ba5213f Binary files /dev/null and "b/2025/20.X\347\273\230\345\233\276-\346\210\221\344\273\254\346\230\257\345\246\202\344\275\225\350\256\251AI\346\233\264\345\245\275\347\232\204\350\236\215\345\205\245\346\210\221\344\273\254\347\232\204\344\272\247\345\223\201\347\232\204/image1.png" differ diff --git "a/2025/20.X\347\273\230\345\233\276-\346\210\221\344\273\254\346\230\257\345\246\202\344\275\225\350\256\251AI\346\233\264\345\245\275\347\232\204\350\236\215\345\205\245\346\210\221\344\273\254\347\232\204\344\272\247\345\223\201\347\232\204/image2.png" "b/2025/20.X\347\273\230\345\233\276-\346\210\221\344\273\254\346\230\257\345\246\202\344\275\225\350\256\251AI\346\233\264\345\245\275\347\232\204\350\236\215\345\205\245\346\210\221\344\273\254\347\232\204\344\272\247\345\223\201\347\232\204/image2.png" new file mode 100644 index 0000000..ba5213f Binary files /dev/null and "b/2025/20.X\347\273\230\345\233\276-\346\210\221\344\273\254\346\230\257\345\246\202\344\275\225\350\256\251AI\346\233\264\345\245\275\347\232\204\350\236\215\345\205\245\346\210\221\344\273\254\347\232\204\344\272\247\345\223\201\347\232\204/image2.png" differ diff --git "a/2025/18.X\347\273\230\345\233\276-\346\210\221\344\273\254\346\230\257\345\246\202\344\275\225\350\256\251AI\346\233\264\345\245\275\347\232\204\350\236\215\345\205\245\346\210\221\344\273\254\347\232\204\344\272\247\345\223\201\347\232\204/\345\256\236\350\256\255\346\204\237\346\202\237.md" "b/2025/20.X\347\273\230\345\233\276-\346\210\221\344\273\254\346\230\257\345\246\202\344\275\225\350\256\251AI\346\233\264\345\245\275\347\232\204\350\236\215\345\205\245\346\210\221\344\273\254\347\232\204\344\272\247\345\223\201\347\232\204/\345\256\236\350\256\255\346\204\237\346\202\237.md" similarity index 100% rename from "2025/18.X\347\273\230\345\233\276-\346\210\221\344\273\254\346\230\257\345\246\202\344\275\225\350\256\251AI\346\233\264\345\245\275\347\232\204\350\236\215\345\205\245\346\210\221\344\273\254\347\232\204\344\272\247\345\223\201\347\232\204/\345\256\236\350\256\255\346\204\237\346\202\237.md" rename to "2025/20.X\347\273\230\345\233\276-\346\210\221\344\273\254\346\230\257\345\246\202\344\275\225\350\256\251AI\346\233\264\345\245\275\347\232\204\350\236\215\345\205\245\346\210\221\344\273\254\347\232\204\344\272\247\345\223\201\347\232\204/\345\256\236\350\256\255\346\204\237\346\202\237.md" diff --git "a/2025/99.\345\256\236\350\256\255\350\220\245\345\210\206\344\272\253\357\274\232LLGo \344\270\255 Python \347\274\226\350\257\221\344\270\216\350\277\220\350\241\214\346\227\266\351\233\206\346\210\220/content.md" "b/2025/21. \345\256\236\350\256\255\350\220\245\345\210\206\344\272\253\357\274\232LLGo \344\270\255 Python \347\274\226\350\257\221\344\270\216\350\277\220\350\241\214\346\227\266\351\233\206\346\210\220/content.md" similarity index 98% rename from "2025/99.\345\256\236\350\256\255\350\220\245\345\210\206\344\272\253\357\274\232LLGo \344\270\255 Python \347\274\226\350\257\221\344\270\216\350\277\220\350\241\214\346\227\266\351\233\206\346\210\220/content.md" rename to "2025/21. \345\256\236\350\256\255\350\220\245\345\210\206\344\272\253\357\274\232LLGo \344\270\255 Python \347\274\226\350\257\221\344\270\216\350\277\220\350\241\214\346\227\266\351\233\206\346\210\220/content.md" index b307be6..1ca47c5 100644 --- "a/2025/99.\345\256\236\350\256\255\350\220\245\345\210\206\344\272\253\357\274\232LLGo \344\270\255 Python \347\274\226\350\257\221\344\270\216\350\277\220\350\241\214\346\227\266\351\233\206\346\210\220/content.md" +++ "b/2025/21. \345\256\236\350\256\255\350\220\245\345\210\206\344\272\253\357\274\232LLGo \344\270\255 Python \347\274\226\350\257\221\344\270\216\350\277\220\350\241\214\346\227\266\351\233\206\346\210\220/content.md" @@ -1,191 +1,191 @@ -# LLGo 中 Python 编译与运行时集成:从依赖识别到一键交付 - -## 前言 - -LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR 构建与 C/C++/Python 生态融合在一起,从“能否编到一起”到“如何舒服地用起来”,中间隔着一整套构建、版本、分发与运行时的工程系统。但目前 LLGo 在 Python 能力中仍存在不足,即对用户 Python 环境的强依赖。为解决这个问题,本文展示了一种用户不可见的 Python 环境构建方案,以“LLGo 中与 Python 相关的编译流程”为主线,串联 C/C++ 与 Python 的关键差异与共同点,并结合 `bundle` 能力说明如何把 Python 一起打包,做到“拿来就跑”。 - -## LLGo 中与 Python 相关的编译流程解析 - -### 顶层入口:把 Python 能力“接进来” - -- 入口函数负责建立 SSA/IR 编译容器,并懒加载运行时与 Python 符号包: -```go - prog.SetPython(func() *types.Package { - return dedup.Check(llssa.PkgPython).Types - }) -``` -这是 LLGo 中已实现的语言编译容器,此处不做赘述。 - -### 构建包:识别依赖、归一化链接、标记是否需要 Python 初始化 - -- 统一遍历待构建的包,按“包类别”决定如何处理: -```go - switch kind, param := cl.PkgKindOf(pkg.Types); kind { - case cl.PkgDeclOnly: - pkg.ExportFile = "" - case cl.PkgLinkIR, cl.PkgLinkExtern, cl.PkgPyModule: - // ... 见下文 - default: - // 常规包 -``` -- 与 Python 直接相关的两类: - - 外链库(link: ...):当参数内出现 `$(pkg-config --libs python3-embed)`,**先准备一套可用的 Python 工具链**,再展开成 `-lpythonX -L...` 等链接参数。 - - Python 模块(py.):若缺失,则我们希望在“独立 Python 环境”内用 pip 安装,从而避免污染系统,实现对用户环境的最小入侵。 - -因此在进行 `pkg-config` 展开之前,我们需要进行 Python环境的构建。 - -关键实现(展开 pkg-config 前的“Python 预构建”四步): -```go -//确保缓存目录存在;若目录为空则下载并解压指定(或默认)Python发行包到缓存目录。 -func EnsureWithFetch(url string) error { - if url == "" { - url = defaultPythonURL() - } -} - -//设置构建所需环境(PATH、PYTHONHOME、PKG_CONFIG_PATH 等),为后续 pkg-config/链接做准备。会在该编译程序的运行时指定python环境 -func EnsureBuildEnv() error { - pyHome := PythonHome() - return applyEnv(pyHome) -} - -//快速校验当前可用的 Python 解释器是否可运行。 -func Verify() error { - cmd := exec.Command(exe, "-c", "import sys; print('OK')") - return cmd.Run() -} - -//(macOS) 把 libpython 的 install_name 改为 @rpath/…,确保链接与运行时能按 rpath 正确定位库。 -func FixLibpythonInstallName(pyHome string) error { - if runtime.GOOS != "darwin" { - return nil - } -} -``` -- **为何需要下载到缓存?** - - 为了不对用户的环境做任何侵入性的改变,我们希望编译时所需的运行时应不与用户环境有关,且对用户不可见,故使用 stand alone 的形式将环境构建在用户的 cache中。 - -- **为何需要设置rpath?** - - 为保证可用的 Python 构建链,我们选用了 `Python-build-standalone` 作为独立环境供 LLGo 使用,从而不对用户环境做任何修改。 - - 在编译时,`EnsureBuildEnv()`保证了程序可以找到我们加载的该 Python 位置,从而展开该 Python 的 `$(pkg-config --libs python3-embed)`。 但 `Python-build-standalone` 在其 `python3-embed`中嵌入的路径为 `/install/...` 前缀,这与 `Python-build-standalone` 的构建有关,与 LLGo 无关。那么编译出的二进制根据该路径去寻找 libpython 时,会找不到库而报错。故我们需要将该内容修改为 `@rpath/...` 以让程序可以找到正确的 libpython 位置。 - - 但在此处,并未设置 Rpath 的实际内容,仍为系统默认值,实际设置发生在链接期 - -- 接上文,若检测 `PkgPyModule // Python 模块(LLGoPackage="py.")` , 则使用 pip 下载对应第三方库 - -```go -func PipInstall(spec string) error { - ... - return InstallPackages(spec) -} -``` - -- 为何 C/C++ 不需要(额外的)构建环境准备 - - - 这里的分支只做“链接参数解析/归一化”,不负责编译源码;C/C++ 源码的编译早在 cgo 与 LLGoFiles 流程中完成为 .o。 - - - 普通 C/C++ 外部库通常依赖系统/现成目录与已安装的 .a/.so/.dylib,链接器只需 -L/-l 即可,不需要像 Python 那样额外下载解释器、设置 PYTHONHOME、修正 install_name 等。 - - -### 链接阶段:注入“初始化解释器”,并确保运行时能找到库 - -- 汇总所有对象文件与链接参数的同时,聚合“是否需要 Python 初始化”标记(使用到 Python C-API 的包会置 true): -```go - if p.ExportFile != "" && aPkg != nil { - // ... - need1, need2 := isNeedRuntimeOrPyInit(ctx, p) - if !needRuntime { needRuntime = need1 } - if !needPyInit { needPyInit = need2 } - } -``` -- 生成主入口初始化函数,并在 IR 中嵌入:按需声明并调用 Python 初始化符号(入口早期执行),然后导出为一个 .o 参与最终链接: -```go - if needPyInit { - pyEnvInit = "call void @__llgo_py_init_from_exedir()" - pyEnvInitDecl = "declare void @__llgo_py_init_from_exedir()" - } -``` -- 生成“Python 初始化.o”(C 源即时编译):它会从可执行文件相对位置推导 PYTHONHOME,并完成解释器初始化,入口 IR `__llgo_py_init_from_exedir` 会调用这个 `.o` 文件。 -```go - out := tmp.Name() + ".o" - args := []string{ - "-x", "c", - "-o", out, "-c", tmp.Name(), - } - // 注入 Python 头文件 - inc := filepath.Join(pyenv.PythonHome(), "include", "python3.12") - args = append(args, "-I"+inc) - cmd := ctx.compiler() - if err := cmd.Compile(args...); err != nil { return "", err } -``` -- 注入 rpath:在得到完整 linkArgs 之后再去重追加,既考虑独立 Python 的 lib 路径,也考虑常用的 `@executable_path` 前缀(macOS): -```go - for _, dir := range pyenv.FindPythonRpaths(pyenv.PythonHome()) { - addRpath(&linkArgs, dir) - } - addRpath(&linkArgs, "@executable_path/python/lib") - addRpath(&linkArgs, "@executable_path/lib/python/lib") -``` -- 最终链接(统一交给 clang/交叉链接器),把根据链接参数,将以上对象构建为可执行文件: -```go - buildArgs := []string{"-o", app} - buildArgs = append(buildArgs, linkArgs...) - // 可选:调试符号/交叉编译 LDFLAGS/EXTRAFLAGS - buildArgs = append(buildArgs, ctx.crossCompile.LDFLAGS...) - buildArgs = append(buildArgs, ctx.crossCompile.EXTRAFLAGS...) - buildArgs = append(buildArgs, objFiles...) - return cmd.Link(buildArgs...) -``` - -## 可选打包(Bundle):让用户“开箱即用” -想让用户机器“无需安装/配置 Python”,可以把 Python 打进发布物里: - -- 命令:llgo bundle -- 关键参数: - - -mode dir|exe - - -out 输出路径(仅 exe 模式用) - - -archive zip|tar 与 -archiveOut(dir 产物可选打包归档) - -```go -// llgo bundle -var Cmd = &base.Command{ - UsageLine: "llgo bundle [-mode dir|exe] [-out output] [-archive zip|tar] [-archiveOut file] [packages]", - Short: "Package executable with embedded Python runtime", -} -... -Cmd.Flag.StringVar(&mode, "mode", "dir", "bundle mode: dir|exe") -Cmd.Flag.StringVar(&out, "out", "", "output file for onefile (default: )") -Cmd.Flag.StringVar(&archive, "archive", "", "archive dist for onedir: zip|rar|tar (default: none)") -Cmd.Flag.StringVar(&archiveOut, "archiveOut", "", "archive output path (default: .)") -``` - -- llgo bundle 的对外用法:llgo bundle [-mode dir|exe] [-out] [-archive ...] [-archiveOut] -- dir:在 dist 目录内生成 lib/python/lib 与 lib/python/lib/python3.12 等完整运行时布局;macOS 添加 rpath;可选再归档。 -- exe:生成单一可执行;运行时解压内嵌的 Python 布局到缓存,设置 PYTHONHOME,转而执行内嵌的 app。 - -## C/C++ 与 Python:相同框架,不同要点 -- 相同: - - 统一走“构建包 → 导出 .o → 收集 LinkArgs → 链接”的编译框架; - - 外部库都走 `link: ...` 归一化为 `-L/-l/...`。 -- 不同: - - 运行期需求:C/C++ 无需“启动运行时”;Python 必须初始化解释器(设置 PYTHONHOME + 初始化调用)。 - - 环境准备:C/C++ 通常只要系统已有库即可;Python 需预置独立环境、修改 install_name(macOS)、并在链接期注入 rpath。 - - 额外对象:Python 会生成“初始化桥接 .o”;C/C++ 无需此步。 - -## 总结 - -- 识别与分类:通过 link: ... 与 py. 判定 Python 依赖,触发专属流程;C/C++ 仅归一化为 -L/-l 无需额外运行时。 - -- 预构建环境:在展开 $(pkg-config --libs python3-embed) 前完成 EnsureWithFetch、EnsureBuildEnv、Verify、FixLibpythonInstallName,保证可解析、可链接、可运行且不侵入系统。 - -- 链接注入:主入口注入 __llgo_py_init_from_exedir 调用并生成桥接 .o,统一追加 rpath(含独立 Python 与 @executable_path/...),再交由链接器合成可执行文件。 - -- 可选打包:BundleOnedir(目录式)与 BuildOnefileBinary(单文件)让应用“拿来就跑”,无需用户安装/配置 Python。 - -- 本质差异:C/C++ 无需“启动运行时”;Python 需在启动早期设置 PYTHONHOME 并初始化解释器。 - -- 结果与价值:实现“可编译、可链接、可运行、可分发、可复现”,以最小侵入把 Python 能力工程化纳入 Go 应用交付链路。 - +# LLGo 中 Python 编译与运行时集成:从依赖识别到一键交付 + +## 前言 + +LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR 构建与 C/C++/Python 生态融合在一起,从“能否编到一起”到“如何舒服地用起来”,中间隔着一整套构建、版本、分发与运行时的工程系统。但目前 LLGo 在 Python 能力中仍存在不足,即对用户 Python 环境的强依赖。为解决这个问题,本文展示了一种用户不可见的 Python 环境构建方案,以“LLGo 中与 Python 相关的编译流程”为主线,串联 C/C++ 与 Python 的关键差异与共同点,并结合 `bundle` 能力说明如何把 Python 一起打包,做到“拿来就跑”。 + +## LLGo 中与 Python 相关的编译流程解析 + +### 顶层入口:把 Python 能力“接进来” + +- 入口函数负责建立 SSA/IR 编译容器,并懒加载运行时与 Python 符号包: +```go + prog.SetPython(func() *types.Package { + return dedup.Check(llssa.PkgPython).Types + }) +``` +这是 LLGo 中已实现的语言编译容器,此处不做赘述。 + +### 构建包:识别依赖、归一化链接、标记是否需要 Python 初始化 + +- 统一遍历待构建的包,按“包类别”决定如何处理: +```go + switch kind, param := cl.PkgKindOf(pkg.Types); kind { + case cl.PkgDeclOnly: + pkg.ExportFile = "" + case cl.PkgLinkIR, cl.PkgLinkExtern, cl.PkgPyModule: + // ... 见下文 + default: + // 常规包 +``` +- 与 Python 直接相关的两类: + - 外链库(link: ...):当参数内出现 `$(pkg-config --libs python3-embed)`,**先准备一套可用的 Python 工具链**,再展开成 `-lpythonX -L...` 等链接参数。 + - Python 模块(py.):若缺失,则我们希望在“独立 Python 环境”内用 pip 安装,从而避免污染系统,实现对用户环境的最小入侵。 + +因此在进行 `pkg-config` 展开之前,我们需要进行 Python环境的构建。 + +关键实现(展开 pkg-config 前的“Python 预构建”四步): +```go +//确保缓存目录存在;若目录为空则下载并解压指定(或默认)Python发行包到缓存目录。 +func EnsureWithFetch(url string) error { + if url == "" { + url = defaultPythonURL() + } +} + +//设置构建所需环境(PATH、PYTHONHOME、PKG_CONFIG_PATH 等),为后续 pkg-config/链接做准备。会在该编译程序的运行时指定python环境 +func EnsureBuildEnv() error { + pyHome := PythonHome() + return applyEnv(pyHome) +} + +//快速校验当前可用的 Python 解释器是否可运行。 +func Verify() error { + cmd := exec.Command(exe, "-c", "import sys; print('OK')") + return cmd.Run() +} + +//(macOS) 把 libpython 的 install_name 改为 @rpath/…,确保链接与运行时能按 rpath 正确定位库。 +func FixLibpythonInstallName(pyHome string) error { + if runtime.GOOS != "darwin" { + return nil + } +} +``` +- **为何需要下载到缓存?** + + 为了不对用户的环境做任何侵入性的改变,我们希望编译时所需的运行时应不与用户环境有关,且对用户不可见,故使用 stand alone 的形式将环境构建在用户的 cache中。 + +- **为何需要设置rpath?** + + 为保证可用的 Python 构建链,我们选用了 `Python-build-standalone` 作为独立环境供 LLGo 使用,从而不对用户环境做任何修改。 + + 在编译时,`EnsureBuildEnv()`保证了程序可以找到我们加载的该 Python 位置,从而展开该 Python 的 `$(pkg-config --libs python3-embed)`。 但 `Python-build-standalone` 在其 `python3-embed`中嵌入的路径为 `/install/...` 前缀,这与 `Python-build-standalone` 的构建有关,与 LLGo 无关。那么编译出的二进制根据该路径去寻找 libpython 时,会找不到库而报错。故我们需要将该内容修改为 `@rpath/...` 以让程序可以找到正确的 libpython 位置。 + + 但在此处,并未设置 Rpath 的实际内容,仍为系统默认值,实际设置发生在链接期 + +- 接上文,若检测 `PkgPyModule // Python 模块(LLGoPackage="py.")` , 则使用 pip 下载对应第三方库 + +```go +func PipInstall(spec string) error { + ... + return InstallPackages(spec) +} +``` + +- 为何 C/C++ 不需要(额外的)构建环境准备 + + - 这里的分支只做“链接参数解析/归一化”,不负责编译源码;C/C++ 源码的编译早在 cgo 与 LLGoFiles 流程中完成为 .o。 + + - 普通 C/C++ 外部库通常依赖系统/现成目录与已安装的 .a/.so/.dylib,链接器只需 -L/-l 即可,不需要像 Python 那样额外下载解释器、设置 PYTHONHOME、修正 install_name 等。 + + +### 链接阶段:注入“初始化解释器”,并确保运行时能找到库 + +- 汇总所有对象文件与链接参数的同时,聚合“是否需要 Python 初始化”标记(使用到 Python C-API 的包会置 true): +```go + if p.ExportFile != "" && aPkg != nil { + // ... + need1, need2 := isNeedRuntimeOrPyInit(ctx, p) + if !needRuntime { needRuntime = need1 } + if !needPyInit { needPyInit = need2 } + } +``` +- 生成主入口初始化函数,并在 IR 中嵌入:按需声明并调用 Python 初始化符号(入口早期执行),然后导出为一个 .o 参与最终链接: +```go + if needPyInit { + pyEnvInit = "call void @__llgo_py_init_from_exedir()" + pyEnvInitDecl = "declare void @__llgo_py_init_from_exedir()" + } +``` +- 生成“Python 初始化.o”(C 源即时编译):它会从可执行文件相对位置推导 PYTHONHOME,并完成解释器初始化,入口 IR `__llgo_py_init_from_exedir` 会调用这个 `.o` 文件。 +```go + out := tmp.Name() + ".o" + args := []string{ + "-x", "c", + "-o", out, "-c", tmp.Name(), + } + // 注入 Python 头文件 + inc := filepath.Join(pyenv.PythonHome(), "include", "python3.12") + args = append(args, "-I"+inc) + cmd := ctx.compiler() + if err := cmd.Compile(args...); err != nil { return "", err } +``` +- 注入 rpath:在得到完整 linkArgs 之后再去重追加,既考虑独立 Python 的 lib 路径,也考虑常用的 `@executable_path` 前缀(macOS): +```go + for _, dir := range pyenv.FindPythonRpaths(pyenv.PythonHome()) { + addRpath(&linkArgs, dir) + } + addRpath(&linkArgs, "@executable_path/python/lib") + addRpath(&linkArgs, "@executable_path/lib/python/lib") +``` +- 最终链接(统一交给 clang/交叉链接器),把根据链接参数,将以上对象构建为可执行文件: +```go + buildArgs := []string{"-o", app} + buildArgs = append(buildArgs, linkArgs...) + // 可选:调试符号/交叉编译 LDFLAGS/EXTRAFLAGS + buildArgs = append(buildArgs, ctx.crossCompile.LDFLAGS...) + buildArgs = append(buildArgs, ctx.crossCompile.EXTRAFLAGS...) + buildArgs = append(buildArgs, objFiles...) + return cmd.Link(buildArgs...) +``` + +## 可选打包(Bundle):让用户“开箱即用” +想让用户机器“无需安装/配置 Python”,可以把 Python 打进发布物里: + +- 命令:llgo bundle +- 关键参数: + - -mode dir|exe + - -out 输出路径(仅 exe 模式用) + - -archive zip|tar 与 -archiveOut(dir 产物可选打包归档) + +```go +// llgo bundle +var Cmd = &base.Command{ + UsageLine: "llgo bundle [-mode dir|exe] [-out output] [-archive zip|tar] [-archiveOut file] [packages]", + Short: "Package executable with embedded Python runtime", +} +... +Cmd.Flag.StringVar(&mode, "mode", "dir", "bundle mode: dir|exe") +Cmd.Flag.StringVar(&out, "out", "", "output file for onefile (default: )") +Cmd.Flag.StringVar(&archive, "archive", "", "archive dist for onedir: zip|rar|tar (default: none)") +Cmd.Flag.StringVar(&archiveOut, "archiveOut", "", "archive output path (default: .)") +``` + +- llgo bundle 的对外用法:llgo bundle [-mode dir|exe] [-out] [-archive ...] [-archiveOut] +- dir:在 dist 目录内生成 lib/python/lib 与 lib/python/lib/python3.12 等完整运行时布局;macOS 添加 rpath;可选再归档。 +- exe:生成单一可执行;运行时解压内嵌的 Python 布局到缓存,设置 PYTHONHOME,转而执行内嵌的 app。 + +## C/C++ 与 Python:相同框架,不同要点 +- 相同: + - 统一走“构建包 → 导出 .o → 收集 LinkArgs → 链接”的编译框架; + - 外部库都走 `link: ...` 归一化为 `-L/-l/...`。 +- 不同: + - 运行期需求:C/C++ 无需“启动运行时”;Python 必须初始化解释器(设置 PYTHONHOME + 初始化调用)。 + - 环境准备:C/C++ 通常只要系统已有库即可;Python 需预置独立环境、修改 install_name(macOS)、并在链接期注入 rpath。 + - 额外对象:Python 会生成“初始化桥接 .o”;C/C++ 无需此步。 + +## 总结 + +- 识别与分类:通过 link: ... 与 py. 判定 Python 依赖,触发专属流程;C/C++ 仅归一化为 -L/-l 无需额外运行时。 + +- 预构建环境:在展开 $(pkg-config --libs python3-embed) 前完成 EnsureWithFetch、EnsureBuildEnv、Verify、FixLibpythonInstallName,保证可解析、可链接、可运行且不侵入系统。 + +- 链接注入:主入口注入 __llgo_py_init_from_exedir 调用并生成桥接 .o,统一追加 rpath(含独立 Python 与 @executable_path/...),再交由链接器合成可执行文件。 + +- 可选打包:BundleOnedir(目录式)与 BuildOnefileBinary(单文件)让应用“拿来就跑”,无需用户安装/配置 Python。 + +- 本质差异:C/C++ 无需“启动运行时”;Python 需在启动早期设置 PYTHONHOME 并初始化解释器。 + +- 结果与价值:实现“可编译、可链接、可运行、可分发、可复现”,以最小侵入把 Python 能力工程化纳入 Go 应用交付链路。 + diff --git "a/2025/22.\350\256\270\345\274\217\344\274\237\357\274\232AI\346\227\266\344\273\243\351\207\215\345\241\221\350\275\257\344\273\266\345\267\245\347\250\213/reinventing_software_engineering_in_the_AI_Era.md" "b/2025/22.\350\256\270\345\274\217\344\274\237\357\274\232AI\346\227\266\344\273\243\351\207\215\345\241\221\350\275\257\344\273\266\345\267\245\347\250\213/reinventing_software_engineering_in_the_AI_Era.md" new file mode 100644 index 0000000..db6a95b --- /dev/null +++ "b/2025/22.\350\256\270\345\274\217\344\274\237\357\274\232AI\346\227\266\344\273\243\351\207\215\345\241\221\350\275\257\344\273\266\345\267\245\347\250\213/reinventing_software_engineering_in_the_AI_Era.md" @@ -0,0 +1,96 @@ +# 许式伟:AI 时代重塑软件工程 + +在第四期1024实训营结营仪式上,实训营发起人许式伟先生以《AI 时代重塑软件工程》为题,为全体学员和在线观众带来了一场深度分享。他从软件工程的基础理论出发,深入剖析了AI技术对软件工程领域的深刻影响与重塑。以下是演讲全文: + +# 正文: + +本期实训营的分享,与前三期有着显著的不同。在前三期中,实习生几乎无人使用 AI 编程;而这一期,每个团队都已大规模采用 AI coding。这一变化并非偶然,而是时代演进的必然结果。 + +1024实训营,本质上是在探索软件工程的最佳实践。然而,当参与实践的学员——这些尚未成型的工程师——其开发方式已在短短一两年间发生根本性转变时,我们就无法再回避一个关键问题:**在 AI 深度介入编程的今天,软件工程本身是否正在被重新定义?** + +过去,我们谈“如何成为优秀的工程师”;今天,我们不得不谈“如何在 AI 时代做好软件工程”。我反复思考这个问题后得出一个结论:AI 正在彻底重塑软件工程的底层逻辑。许多我们曾视为理所当然的工程原则,在 AI 时代可能不再成立。 + +正因如此,本期结营我选择了一个截然不同的主题。这不是一次技术工具的更新讨论,而是一场对工程范式的重新审视——我们必须以更严肃的态度,直面这场正在发生的范式革命。 + +## 软件工程铁三角:工程、设计与商业 + +在深入探讨AI的影响之前,我们有必要回归软件工程的基本理论框架。我认为软件工程的范畴始终围绕着三个核心层面:工程、设计与商业。 + +**工程**是一门关于“如何把事做成”的学问。它的核心方法论包含三个要素:模块(拆解复杂问题)、版本(迭代逼近目标)和分工(团队协同)。这是大多数技术人员最为熟悉的部分。 + +**设计**远不止于产品包装或UI界面,它本质上是一门关于“决策”的学问——要做什么事,以及把事做成什么样子。从战略设计、组织设计到产品设计、架构设计,每一个环节都需要精准的决策。 + +**商业**则是所有事情的源头。商业不一定是赚多少钱,但它一定绕不开社会价值。商业好不好,最终的衡量标准是 ROI(投资回报率),这里可以用公式表达:`ROI = 社会价值/实现代价`。即用更低的成本实现更大的社会价值,这就是商业的本质。 + +在这个框架下,我们才能理解为什么顶级产品经理做的是“减法”,而顶级架构师做的是“乘法”。 + +好产品的定义是:**用最少的功能,实现最大化的需求覆盖**。“少就是指数级的多”不仅是Google创始人的理念,更是产品设计的黄金准则。就像计算机的 CPU,五十多年来其内部实现不断迭代,但规格接口几乎保持不变——这种“封闭性”恰恰是好产品的特征。 + +架构设计之所以比产品设计更难,是因为它极度“逆直觉”。当我看到团队 Leader 简单地按照页面分配任务(页面A交给张三,页面B交给李四......)时,就知道这样的架构设计通常是错误的。**真正好的架构设计是在做“乘法”**,它要求每个模块都应被当作一个“子业务”来设计——**有独立需求边界、高复用性、低耦合。就像搭乐高,积木种类越少、拼装步骤越简、外部依赖越成熟,系统就越健壮**。 + + +## AI时代:“人月神话” 的终结 + +在《许式伟的架构课》第74节中,我曾提出一个思想实验:假设一家公司只保留产品经理和架构师,将所有编码任务外包。如果一个产品原本需要10人年完成,那么理论上,20人可做半年,40人可做一季度,120人甚至可在一个月内交付——前提是任务已被充分拆解。 + +这个实验揭示了一个被长期忽视的真相:软件工程的真正瓶颈,从来不是开发本身,而是产品与架构的设计过程。因为设计难以并行,而开发、测试、集成等后续环节,在理想状态下完全可以高度并行。 + +我将这一洞察总结为“软件工程的极限理论(许式定律)”—— + +>制约软件工程进度的瓶颈是产品设计和架构设计,这个过程无法并行;而与之对应的,开发过程可以并行,极限状态是10 分钟就把想要的东西做出来 + +过去,这只是一个理论模型,思想实验。现实中,我们无法瞬间招募成百上千名合格开发者,沟通成本、质量控制、知识传递等现实约束,让“并行开发”止步于想象。 + +但AI Coding的出现,彻底打破了这一限制。在AI时代,每个公司都可以“瞬间雇佣一万个开发人员”——新增一个 AI 开发者边际成本趋近于零。 + +这意味着,“许式定律”中的极限理论不再只是思想实验,而成为每个团队必须面对的现实: + +>如果你的产品需求清晰、架构拆解合理,AI能在几小时内交付过去需要数周的代码;
但如果你的需求模糊、模块边界混乱,AI只会以百倍速度放大你的错误。 + +人月神话的终结,不是因为人变少了,而是因为“月”不再由“人”定义。 + +AI 时代,每个公司都可雇任意多开发人员,因此传统软件工程中人月神话的“人月”不再是有效的度量单位。**开发工时不再是稀缺资源,清晰的问题定义与优雅的系统拆解,才是新时代的稀缺能力**。 + +当然,既然理论上我能够任意增加研发工时,为什么一个商业软件还是没办法一天内就做出来?制约的瓶颈因素有哪些?这是一个值得深入探讨的问题,今天不作展开,欢迎读者交流讨论。 + +## AI 在软件工程各环节的实践与挑战 + +在实际落地的过程中,我们发现AI在不同环节的表现差异显著。 + +在**产品设计**环节,AI 目前还难以在核心的决策过程中提供太大帮助。但它正在改变产品设计的表达方式——未来的产品经理必须输出 Live Demo,而不仅仅是静态原型。点击按钮要有真实反应,原型要能实际运行,这将成为AI时代产品设计的基本要求。 + +在**架构设计**环节,AI 能够辅助生成设计方案,但无法自动选择最优解,也不能自动拆解任务。在我们开发 XGo 语言的实践中,我们要求 AI 将每个功能拆解为三个标准动作:实现语法(AST/Parser/Format)、实现语义(Compiler)、写文档(Quick Start)。这种明确的指令框架大大提升了AI的工作质量。 + +在**开发与测试**环节,虽然这是 AI 最擅长的部分,但远非完美。在一个具体的案例中,AI 为实现可选参数功能提供的初始方案存在严重缺陷,甚至编写的测试用例也只是“自欺欺人”——刚好让有问题的代码通过测试,而没有真正覆盖所有分支。 + +经过多次迭代,我们发现:好的测试用例能够大幅提升 AI 的代码质量。测试覆盖率在AI时代变得前所未有的重要。如果团队原有的测试覆盖率不足,盲目引入 AI Coding 可能会是一场灾难。 + +## 驾驭AI:从“手动挡”到“自动挡”的思维转变 + +如何正确理解AI的编程能力?我的比喻是:**AI Coding不是自动驾驶,而是自动挡**。 + +自动驾驶(L5级别)意味着开发者完全不需要理解代码,只需给出需求即可。这离我们还很遥远。而自动挡意味着你仍然需要驾驶,需要知道方向,但操作门槛显著降低。 + +这个比喻引申出几个重要启示: + +首先,我**不建议新手直接使用AI Coding**。就像开自动挡车仍然需要驾驶技术一样,使用AI Coding需要对软件工程有足够的理解和驾驭能力。 + +其次,AI不会让所有开发者失业,而是导致两极分化。**优秀的开发者会因AI而更加高效、更加值钱**;而能力不足的开发者确实面临淘汰风险。 + +最后,AI的优势不在于推理能力(大致在人类平均线附近),**而在于几乎没有知识盲区**。它是全人类知识的总和,这使它能够解答许多看似困难的问题。 + +## 拥抱变革:效率倍增是底线要求 + +如果一个公司的软件开发过程没有因为AI而实现效率倍增,那么它必将成为落后者。效率翻倍只是底线,实际上有潜力实现更高的提升。 + +在 XGo 语言的开发中,我们已经全面使用 AI Coding,并且以完全公开透明的方式展示整个过程——包括如何给 AI 提任务、如何指挥 AI 工作。欢迎大家到 XGo 仓库(github.com/goplus/xgo)观摩交流。 + +AI时代不是终点,而是新的起点。我期待与更多朋友探讨两个方向的深度合作: + +对于教育从业者,让我们一起探讨AI时代的工程教育变革。1024实训营将继续以公开透明的方式探索软件工程的最佳实践。 + +对于企业决策者(CEO/CTO/技术总监),七牛云提供最全的 AI Coding 模型 API 服务和新一代软件工程的落地咨询(免费),帮助你的团队顺利过渡到AI新时代。 + +软件工程正在被重塑,而我们有幸成为这场变革的见证者和参与者。与其恐惧,不如拥抱;与其被动,不如主动学习驾驭这个强大的新工具。 + +未来的软件工程,属于那些能够将人类智慧与机器智能完美结合的组织和个人。 \ No newline at end of file diff --git a/README.md b/README.md index 5bb037f..5b6fce8 100644 --- a/README.md +++ b/README.md @@ -49,16 +49,16 @@ ### 🎓 第 4 期训练营 #### 实训课题 -| 模块 | 主题 | 内容 | -| ------- | --------------------------------------- | --------------------------------------------- | +| 模块 | 主题 | 内容 | +| ---- | -------------------------------------------- | ------------------------------------------------------------------------------------------- | | 第 1 组 | XBuilder 项目分享与传播 | [📖 阅读](4th/1st_xbuilder_share/topic.md) | | 第 2 组 | XBuilder 基于大模型的代码生成与素材生成 | [📖 阅读](4th/2nd_copilot_classfile/topic.md) | | 第 3 组 | LLGo 对 Python 库开箱即用 | [📖 阅读](4th/3rd_llgo_python/topic.md) | | 第 4 组 | AI Powered Ops | [📖 阅读](4th/4th_ai_powered_ops/topic.md) | #### 实训公开课 -| 期数 | 主题 | 内容 | -| ------- | --------------------------------------- | ---------------------------------- | +| 期数 | 主题 | 内容 | +| ---- | -------------------------------------------- | ------------------------------------------------------------------------------------------- | | 第 1 期 | 《人人都是产品经理》作者苏杰:AI 教我的产品哲学 | [📖 观看](https://mp.weixin.qq.com/s/qGQBKs3bjDZzk-SKdNumBw) | | 第 2 期 | 汪源 × 许式伟:AI时代如何打造真正解决用户痛点的产品? | [📖 观看](https://mp.weixin.qq.com/s/eppy6z7XNN-mVZIjk44yvg) | | 第 3 期 | 《构建之法》作者邹欣:实战中的软件工程——从微软到 AI 时代的构建之道 | [📖 观看](https://mp.weixin.qq.com/s/CkKXGLzycyE7inMFp6jKlw) |