From 79b9787513ea8a5da6febf450dcdde8b25cb82a0 Mon Sep 17 00:00:00 2001 From: Askeladd <1351914167@qq.com> Date: Thu, 25 Sep 2025 12:04:45 +0800 Subject: [PATCH 1/9] add shared doc of llgo --- .../content.md" | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 "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" 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/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" new file mode 100644 index 0000000..7c619b3 --- /dev/null +++ "b/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" @@ -0,0 +1,166 @@ +# LLGo 中 Python 编译与运行时集成:从依赖识别到一键交付 + +## 前言 + +LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR 构建与 C/C++/Python 生态融合在一起,从“能否编到一起”到“如何舒服地用起来”,中间隔着一整套构建、版本、分发与运行时的工程系统。本文以“LLGo 中与 Python 相关的编译流程”为主线,串联 C/C++ 与 Python 的关键差异与共同点,并结合 `bundle` 能力说明如何把 Python 一起打包,做到“拿来就跑”。 + +## LLGo 中与 Python 相关的编译流程解析 + +### 顶层入口:把 Python 能力“接进来” + +- 入口函数负责建立 SSA/IR 编译容器,并懒加载运行时与 Python 符号包: +```go + prog.SetRuntime(func() *types.Package { + noRt = 0 + return altPkgs[0].Types + }) + prog.SetPython(func() *types.Package { + return dedup.Check(llssa.PkgPython).Types + }) +``` +- 为什么不需要“C 的提供者”? + - C/C++ 的函数类型/符号在 cgo 与编译/链接期已给出,不需要像 Python 一样在 SSA 层动态提供类型信息。 + +### 构建包:识别依赖、归一化链接、标记是否需要 Python 初始化 + +- 统一遍历待构建的包,按“包类别”决定如何处理: +```418:424:llgo/internal/build/build.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 预构建”四步): +```go +{"prepare Python cache", func() error { return pyenv.EnsureWithFetch("") }}, +{"setup Python build env", pyenv.EnsureBuildEnv}, +{"verify Python", pyenv.Verify}, +{"fix install_name", func() error { return pyenv.FixLibpythonInstallName(pyHome) }}, +``` +- EnsureWithFetch:下载独立发行版到缓存(standalone,不侵入用户系统)。 +- EnsureBuildEnv:注入 PATH、PYTHONHOME、PKG_CONFIG_PATH 等,使 pkg-config 可正确解析头/库路径。 +- Verify:快速跑解释器以确认可用。 +- FixLibpythonInstallName(macOS):把 libpython 的 install_name 调整为 @rpath/...,便于后续按 rpath 定位。 + +### 链接阶段:注入“初始化解释器”,并确保运行时能找到库 + +- 汇总所有对象文件与链接参数的同时,聚合“是否需要 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()" + } +``` +- 生成“初始化桥接 .o”(C 源即时编译):它会从可执行文件相对位置推导 PYTHONHOME,并完成解释器初始化,与入口 IR 的调用对接。 +```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 打进发布物里,两种形态: + +- dir(目录式):把 libpython 和标准库复制到可执行文件旁固定层级,并在 macOS 下设置 install_name 为 @rpath: +```go +// /python/lib/libpython3.x.{dylib|so} +// /python/lib/python3.12/**(含 lib-dynload/ 与 site-packages/) +func BundleOnedir(app string) error { + exeDir := filepath.Dir(app) + pyHome := PythonHome() + exelibDir := filepath.Join(exeDir, "lib") + // ... +``` +```go + libSrc, err := findLibpython(filepath.Join(pyHome, "lib")) + // 复制到 /lib/python/lib + if runtime.GOOS == "darwin" { + _ = exec.Command("install_name_tool", "-id", "@rpath/"+filepath.Base(libDst), libDst).Run() + } +``` +- exe(单文件自解压):把“Python 目录 + 应用二进制”打成一个可执行壳,首次运行解压到缓存后设置库路径与 PYTHONHOME 再启动应用。 +```go +func BuildOnefileBinary(exe string, out string) error { + payload, err := BuildPyBundleZip() + // 写入 payload.zip + app.bin + 最小 Go main 启动器 + cmd := exec.Command("go", "build", "-o", out, "main.go") + // ... + return nil +} +``` + +通俗理解:onedir = “把 Python 摆在程序旁”,onefile = “把 Python 藏进单文件里”;两者都不依赖用户系统有没有 Python。 + +## C/C++ 与 Python:相同框架,不同要点 +- 相同: + - 统一走“构建包 → 导出 .o → 收集 LinkArgs → 链接”的编译框架; + - 外部库都走 `link: ...` 归一化为 `-L/-l/...`。 +- 不同: + - 运行期需求:C/C++ 无需“启动运行时”;Python 必须初始化解释器(设置 PYTHONHOME + 初始化调用)。 + - 环境准备:C/C++ 通常只要系统已有库即可;Python 需预置独立环境、修 install_name(macOS)、并在链接期注入 rpath。 + - 额外对象:Python 会生成“初始化桥接 .o”;C/C++ 无需此步。 + +## 一眼看懂的调用顺序 +- Do +- buildAllPkgs(初始包集) +- buildPkg(每包:NewPackageEx → buildCgo/LLGoFiles → exportObject) +- buildAllPkgs(替代包/补丁) +- createGlobals(如有) +- linkMainPkg + - 收集对象/链接参数 → 生成主入口 .o(含 Python 初始化声明/调用) → 如需再生成 Python 初始化桥接 .o → 追加 rpath → 最终链接 +- 运行/测试(按模式) + +## 总结 + +- 识别与分类:通过 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 应用交付链路。 \ No newline at end of file From 1919910ab51dfb0fb72a1646ffbfdf5d37f7a144 Mon Sep 17 00:00:00 2001 From: Askeladd <1351914167@qq.com> Date: Thu, 25 Sep 2025 19:02:04 +0800 Subject: [PATCH 2/9] fix problems from AI reviewer --- .../content.md" | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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/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" index 7c619b3..4d44912 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/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" @@ -24,7 +24,7 @@ LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR ### 构建包:识别依赖、归一化链接、标记是否需要 Python 初始化 - 统一遍历待构建的包,按“包类别”决定如何处理: -```418:424:llgo/internal/build/build.go +```go switch kind, param := cl.PkgKindOf(pkg.Types); kind { case cl.PkgDeclOnly: pkg.ExportFile = "" @@ -138,7 +138,7 @@ func BuildOnefileBinary(exe string, out string) error { - 外部库都走 `link: ...` 归一化为 `-L/-l/...`。 - 不同: - 运行期需求:C/C++ 无需“启动运行时”;Python 必须初始化解释器(设置 PYTHONHOME + 初始化调用)。 - - 环境准备:C/C++ 通常只要系统已有库即可;Python 需预置独立环境、修 install_name(macOS)、并在链接期注入 rpath。 + - 环境准备:C/C++ 通常只要系统已有库即可;Python 需预置独立环境、修改 install_name(macOS)、并在链接期注入 rpath。 - 额外对象:Python 会生成“初始化桥接 .o”;C/C++ 无需此步。 ## 一眼看懂的调用顺序 @@ -163,4 +163,7 @@ func BuildOnefileBinary(exe string, out string) error { - 本质差异:C/C++ 无需“启动运行时”;Python 需在启动早期设置 PYTHONHOME 并初始化解释器。 -- 结果与价值:实现“可编译、可链接、可运行、可分发、可复现”,以最小侵入把 Python 能力工程化纳入 Go 应用交付链路。 \ No newline at end of file +- 结果与价值:实现“可编译、可链接、可运行、可分发、可复现”,以最小侵入把 Python 能力工程化纳入 Go 应用交付链路。 + + + From ce3cfd88d9fdffed176ddaa17d0e7ef7d0c4ea3e Mon Sep 17 00:00:00 2001 From: Askeladd <1351914167@qq.com> Date: Fri, 26 Sep 2025 14:28:49 +0800 Subject: [PATCH 3/9] refine discription --- .../content.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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/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" index 4d44912..0e224d2 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/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" @@ -99,7 +99,7 @@ LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR return cmd.Link(buildArgs...) ``` -## 可选打包(Bundle):让用户“拿来就跑” +## 可选打包(Bundle):让用户“开箱即用” 想让用户机器“无需安装/配置 Python”,可以把 Python 打进发布物里,两种形态: - dir(目录式):把 libpython 和标准库复制到可执行文件旁固定层级,并在 macOS 下设置 install_name 为 @rpath: From d4b938f5fa8e9b4e3f8f5790a43b0372c688c121 Mon Sep 17 00:00:00 2001 From: Askeladd <1351914167@qq.com> Date: Fri, 26 Sep 2025 15:48:41 +0800 Subject: [PATCH 4/9] refine content in line 70 --- .../content.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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/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" index 0e224d2..5966c5c 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/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" @@ -67,7 +67,7 @@ LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR pyEnvInitDecl = "declare void @__llgo_py_init_from_exedir()" } ``` -- 生成“初始化桥接 .o”(C 源即时编译):它会从可执行文件相对位置推导 PYTHONHOME,并完成解释器初始化,与入口 IR 的调用对接。 +- 生成“Python 初始化.o”(C 源即时编译):它会从可执行文件相对位置推导 PYTHONHOME,并完成解释器初始化,入口 IR `__llgo_py_init_from_exedir` 会调用这个 `.o` 文件。 ```go out := tmp.Name() + ".o" args := []string{ From e2acd37fb12ef3407934932f24b35dc1605d8970 Mon Sep 17 00:00:00 2001 From: Askeladd <1351914167@qq.com> Date: Fri, 26 Sep 2025 16:02:10 +0800 Subject: [PATCH 5/9] refine content in line 21 & 91 --- .../content.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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/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" index 5966c5c..59e361e 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/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" @@ -18,7 +18,7 @@ LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR return dedup.Check(llssa.PkgPython).Types }) ``` -- 为什么不需要“C 的提供者”? +- 为什么不需要“C 的编译容器”? - C/C++ 的函数类型/符号在 cgo 与编译/链接期已给出,不需要像 Python 一样在 SSA 层动态提供类型信息。 ### 构建包:识别依赖、归一化链接、标记是否需要 Python 初始化 @@ -88,7 +88,7 @@ LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR addRpath(&linkArgs, "@executable_path/python/lib") addRpath(&linkArgs, "@executable_path/lib/python/lib") ``` -- 最终链接(统一交给 clang/或交叉链接器),把以上对象与参数合并为可执行文件: +- 最终链接(统一交给 clang/交叉链接器),把以上对象与参数合并为可执行文件: ```go buildArgs := []string{"-o", app} buildArgs = append(buildArgs, linkArgs...) From a012bbe9a6798f4f433b8d6423bad182ad73ef21 Mon Sep 17 00:00:00 2001 From: Askeladd <1351914167@qq.com> Date: Fri, 26 Sep 2025 16:08:12 +0800 Subject: [PATCH 6/9] refine content in line 37 & 47 --- .../content.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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/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" index 59e361e..fecfbbc 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/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" @@ -34,7 +34,7 @@ LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR // 常规包 ``` - 与 Python 直接相关的两类: - - 外链库(link: ...):当参数内出现 `$(pkg-config --libs python3-embed)`,先“准备一套可用的 Python 工具链”,再展开成 `-lpythonX -L...` 等链接参数。 + - 外链库(link: ...):当参数内出现 `$(pkg-config --libs python3-embed)`,先准备一套可用的 Python 工具链,再展开成 `-lpythonX -L...` 等链接参数。 - Python 模块(py.):缺失则在“独立 Python 环境”内用 pip 安装,不污染系统。 关键实现(展开 pkg-config 前的“Python 预构建”四步): @@ -44,7 +44,7 @@ LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR {"verify Python", pyenv.Verify}, {"fix install_name", func() error { return pyenv.FixLibpythonInstallName(pyHome) }}, ``` -- EnsureWithFetch:下载独立发行版到缓存(standalone,不侵入用户系统)。 +- EnsureWithFetch:下载`python-build-standalone`独立发行版到缓存, 不侵入用户系统。 - EnsureBuildEnv:注入 PATH、PYTHONHOME、PKG_CONFIG_PATH 等,使 pkg-config 可正确解析头/库路径。 - Verify:快速跑解释器以确认可用。 - FixLibpythonInstallName(macOS):把 libpython 的 install_name 调整为 @rpath/...,便于后续按 rpath 定位。 From ba8c01b805808b940ba94f27936cb563214371dd Mon Sep 17 00:00:00 2001 From: Askeladd <1351914167@qq.com> Date: Tue, 30 Sep 2025 16:43:09 +0800 Subject: [PATCH 7/9] add more detailed description --- .../content.md" | 115 ++++++++++++------ 1 file changed, 76 insertions(+), 39 deletions(-) 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/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" index fecfbbc..947522e 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/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" @@ -18,8 +18,6 @@ LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR return dedup.Check(llssa.PkgPython).Types }) ``` -- 为什么不需要“C 的编译容器”? - - C/C++ 的函数类型/符号在 cgo 与编译/链接期已给出,不需要像 Python 一样在 SSA 层动态提供类型信息。 ### 构建包:识别依赖、归一化链接、标记是否需要 Python 初始化 @@ -34,20 +32,66 @@ LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR // 常规包 ``` - 与 Python 直接相关的两类: - - 外链库(link: ...):当参数内出现 `$(pkg-config --libs python3-embed)`,先准备一套可用的 Python 工具链,再展开成 `-lpythonX -L...` 等链接参数。 - - Python 模块(py.):缺失则在“独立 Python 环境”内用 pip 安装,不污染系统。 + - 外链库(link: ...):当参数内出现 `$(pkg-config --libs python3-embed)`,**先准备一套可用的 Python 工具链**,再展开成 `-lpythonX -L...` 等链接参数。 + - Python 模块(py.):若缺失,则我们希望在“独立 Python 环境”内用 pip 安装,从而避免污染系统,实现对用户环境的最小入侵。 + +因此在进行 `pkg-config` 展开之前,我们需要进行 Python环境的构建。 关键实现(展开 pkg-config 前的“Python 预构建”四步): ```go -{"prepare Python cache", func() error { return pyenv.EnsureWithFetch("") }}, -{"setup Python build env", pyenv.EnsureBuildEnv}, -{"verify Python", pyenv.Verify}, -{"fix install_name", func() error { return pyenv.FixLibpythonInstallName(pyHome) }}, +//确保缓存目录存在;若目录为空则下载并解压指定(或默认)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) +} ``` -- EnsureWithFetch:下载`python-build-standalone`独立发行版到缓存, 不侵入用户系统。 -- EnsureBuildEnv:注入 PATH、PYTHONHOME、PKG_CONFIG_PATH 等,使 pkg-config 可正确解析头/库路径。 -- Verify:快速跑解释器以确认可用。 -- FixLibpythonInstallName(macOS):把 libpython 的 install_name 调整为 @rpath/...,便于后续按 rpath 定位。 + +- 为何 C/C++ 不需要(额外的)构建环境准备 + + - 这里的分支只做“链接参数解析/归一化”,不负责编译源码;C/C++ 源码的编译早在 cgo 与 LLGoFiles 流程中完成为 .o。 + + - 普通 C/C++ 外部库通常依赖系统/现成目录与已安装的 .a/.so/.dylib,链接器只需 -L/-l 即可,不需要像 Python 那样额外下载解释器、设置 PYTHONHOME、修正 install_name 等。 + ### 链接阶段:注入“初始化解释器”,并确保运行时能找到库 @@ -88,7 +132,7 @@ LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR addRpath(&linkArgs, "@executable_path/python/lib") addRpath(&linkArgs, "@executable_path/lib/python/lib") ``` -- 最终链接(统一交给 clang/交叉链接器),把以上对象与参数合并为可执行文件: +- 最终链接(统一交给 clang/交叉链接器),把根据链接参数,将以上对象合并为可执行文件: ```go buildArgs := []string{"-o", app} buildArgs = append(buildArgs, linkArgs...) @@ -100,37 +144,30 @@ LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR ``` ## 可选打包(Bundle):让用户“开箱即用” -想让用户机器“无需安装/配置 Python”,可以把 Python 打进发布物里,两种形态: +想让用户机器“无需安装/配置 Python”,可以把 Python 打进发布物里: + +- 命令:llgo bundle +- 关键参数: + - -mode dir|exe + - -out 输出路径(仅 exe 模式用) + - -archive zip|tar 与 -archiveOut(dir 产物可选打包归档) -- dir(目录式):把 libpython 和标准库复制到可执行文件旁固定层级,并在 macOS 下设置 install_name 为 @rpath: -```go -// /python/lib/libpython3.x.{dylib|so} -// /python/lib/python3.12/**(含 lib-dynload/ 与 site-packages/) -func BundleOnedir(app string) error { - exeDir := filepath.Dir(app) - pyHome := PythonHome() - exelibDir := filepath.Join(exeDir, "lib") - // ... -``` -```go - libSrc, err := findLibpython(filepath.Join(pyHome, "lib")) - // 复制到 /lib/python/lib - if runtime.GOOS == "darwin" { - _ = exec.Command("install_name_tool", "-id", "@rpath/"+filepath.Base(libDst), libDst).Run() - } -``` -- exe(单文件自解压):把“Python 目录 + 应用二进制”打成一个可执行壳,首次运行解压到缓存后设置库路径与 PYTHONHOME 再启动应用。 ```go -func BuildOnefileBinary(exe string, out string) error { - payload, err := BuildPyBundleZip() - // 写入 payload.zip + app.bin + 最小 Go main 启动器 - cmd := exec.Command("go", "build", "-o", out, "main.go") - // ... - return nil +// 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: .)") ``` -通俗理解:onedir = “把 Python 摆在程序旁”,onefile = “把 Python 藏进单文件里”;两者都不依赖用户系统有没有 Python。 +- 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:相同框架,不同要点 - 相同: From 9948ab184e28c6bdccec334d9ae53f9fdb0f82ac Mon Sep 17 00:00:00 2001 From: Askeladd <1351914167@qq.com> Date: Tue, 30 Sep 2025 17:42:32 +0800 Subject: [PATCH 8/9] add more to shown purpose --- .../content.md" | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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/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" index 947522e..ad140bb 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/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" @@ -2,7 +2,7 @@ ## 前言 -LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR 构建与 C/C++/Python 生态融合在一起,从“能否编到一起”到“如何舒服地用起来”,中间隔着一整套构建、版本、分发与运行时的工程系统。本文以“LLGo 中与 Python 相关的编译流程”为主线,串联 C/C++ 与 Python 的关键差异与共同点,并结合 `bundle` 能力说明如何把 Python 一起打包,做到“拿来就跑”。 +LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR 构建与 C/C++/Python 生态融合在一起,从“能否编到一起”到“如何舒服地用起来”,中间隔着一整套构建、版本、分发与运行时的工程系统。但目前 LLGo 在 Python 能力中仍存在不足,即对用户 Python 环境的强依赖。为解决这个问题,本文展示了一种用户不可见的 Python 环境构建方案,以“LLGo 中与 Python 相关的编译流程”为主线,串联 C/C++ 与 Python 的关键差异与共同点,并结合 `bundle` 能力说明如何把 Python 一起打包,做到“拿来就跑”。 ## LLGo 中与 Python 相关的编译流程解析 @@ -10,14 +10,11 @@ LLGo 是一款基于 LLVM 的 Go 编译器,它把 Go 的类型系统和 SSA/IR - 入口函数负责建立 SSA/IR 编译容器,并懒加载运行时与 Python 符号包: ```go - prog.SetRuntime(func() *types.Package { - noRt = 0 - return altPkgs[0].Types - }) prog.SetPython(func() *types.Package { return dedup.Check(llssa.PkgPython).Types }) ``` +这是 LLGo 中已实现的语言编译容器,此处不做赘述。 ### 构建包:识别依赖、归一化链接、标记是否需要 Python 初始化 @@ -132,7 +129,7 @@ func PipInstall(spec string) error { addRpath(&linkArgs, "@executable_path/python/lib") addRpath(&linkArgs, "@executable_path/lib/python/lib") ``` -- 最终链接(统一交给 clang/交叉链接器),把根据链接参数,将以上对象合并为可执行文件: +- 最终链接(统一交给 clang/交叉链接器),把根据链接参数,将以上对象构建为可执行文件: ```go buildArgs := []string{"-o", app} buildArgs = append(buildArgs, linkArgs...) From 649a9123830b514a60272c9b989153ff3d779fc7 Mon Sep 17 00:00:00 2001 From: Askeladd <1351914167@qq.com> Date: Thu, 9 Oct 2025 11:42:53 +0800 Subject: [PATCH 9/9] change on line 104, delete unclear discription. --- .../content.md" | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) 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/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" index ad140bb..b307be6 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/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" @@ -101,7 +101,7 @@ func PipInstall(spec string) error { if !needPyInit { needPyInit = need2 } } ``` -- 生成主入口 IR:按需声明并调用 Python 初始化符号(入口早期执行),然后导出为一个 .o 参与最终链接: +- 生成主入口初始化函数,并在 IR 中嵌入:按需声明并调用 Python 初始化符号(入口早期执行),然后导出为一个 .o 参与最终链接: ```go if needPyInit { pyEnvInit = "call void @__llgo_py_init_from_exedir()" @@ -175,16 +175,6 @@ Cmd.Flag.StringVar(&archiveOut, "archiveOut", "", "archive output path (default: - 环境准备:C/C++ 通常只要系统已有库即可;Python 需预置独立环境、修改 install_name(macOS)、并在链接期注入 rpath。 - 额外对象:Python 会生成“初始化桥接 .o”;C/C++ 无需此步。 -## 一眼看懂的调用顺序 -- Do -- buildAllPkgs(初始包集) -- buildPkg(每包:NewPackageEx → buildCgo/LLGoFiles → exportObject) -- buildAllPkgs(替代包/补丁) -- createGlobals(如有) -- linkMainPkg - - 收集对象/链接参数 → 生成主入口 .o(含 Python 初始化声明/调用) → 如需再生成 Python 初始化桥接 .o → 追加 rpath → 最终链接 -- 运行/测试(按模式) - ## 总结 - 识别与分类:通过 link: ... 与 py. 判定 Python 依赖,触发专属流程;C/C++ 仅归一化为 -L/-l 无需额外运行时。 @@ -199,5 +189,3 @@ Cmd.Flag.StringVar(&archiveOut, "archiveOut", "", "archive output path (default: - 结果与价值:实现“可编译、可链接、可运行、可分发、可复现”,以最小侵入把 Python 能力工程化纳入 Go 应用交付链路。 - -