Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions internal/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
"github.com/goplus/llgo/internal/env"
"github.com/goplus/llgo/internal/mockable"
"github.com/goplus/llgo/internal/packages"
"github.com/goplus/llgo/internal/pyenv"
"github.com/goplus/llgo/internal/typepatch"
"github.com/goplus/llgo/ssa/abi"
"github.com/goplus/llgo/xtool/clang"
Expand Down Expand Up @@ -160,7 +161,6 @@ func Do(args []string, conf *Config) ([]Package, error) {
if err != nil {
return nil, fmt.Errorf("failed to setup crosscompile: %w", err)
}

// Update GOOS/GOARCH from export if target was used
if conf.Target != "" && export.GOOS != "" {
conf.Goos = export.GOOS
Expand Down Expand Up @@ -286,6 +286,7 @@ func Do(args []string, conf *Config) ([]Package, error) {
isCheckLinkArgsEnabled: IsCheckLinkArgsEnabled(),
cTransformer: cabi.NewTransformer(prog, conf.AbiMode),
}

pkgs, err := buildAllPkgs(ctx, initial, verbose)
check(err)
if mode == ModeGen {
Expand All @@ -308,7 +309,6 @@ func Do(args []string, conf *Config) ([]Package, error) {

global, err := createGlobals(ctx, ctx.prog, pkgs)
check(err)

for _, pkg := range initial {
if needLink(pkg, mode) {
linkMainPkg(ctx, pkg, allPkgs, global, conf, mode, verbose)
Expand Down Expand Up @@ -449,6 +449,18 @@ func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs
expdArgs := make([]string, 0, len(altParts))
for _, param := range altParts {
param = strings.TrimSpace(param)
if param == "$LLGO_LIB_PYTHON" {
err := pyenv.EnsureWithFetch("")
if err != nil {
panic(fmt.Sprintf("failed to prepare Python cache: %v\n\tLLGO_CACHE_DIR=%s\n\thint: set LLPYG_PYHOME or check network/permissions", err, env.LLGoCacheDir()))
}
if err = pyenv.EnsureBuildEnv(); err != nil {
panic(fmt.Sprintf("failed to set up Python build env: %v\n\tPYTHONHOME=%s", err, pyenv.PythonHome()))
}
if err = pyenv.Verify(); err != nil {
panic(fmt.Sprintf("failed to verify Python: %v\n\tpython=%s", err, filepath.Join(pyenv.PythonHome(), "bin", "python3")))
}
}
if strings.ContainsRune(param, '$') {
expdArgs = append(expdArgs, xenv.ExpandEnvToArgs(param)...)
ctx.nLibdir++
Expand Down Expand Up @@ -476,13 +488,21 @@ func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs
ctx.nLibdir++
}
}

if ctx.isCheckLinkArgsEnabled {
if err := ctx.compiler().CheckLinkArgs(pkgLinkArgs, isWasmTarget(ctx.buildConf.Goos)); err != nil {
panic(fmt.Sprintf("test link args '%s' failed\n\texpanded to: %v\n\tresolved to: %v\n\terror: %v", param, expdArgs, pkgLinkArgs, err))
}
}
aPkg.LinkArgs = append(aPkg.LinkArgs, pkgLinkArgs...)
}
if kind == cl.PkgPyModule {
if param != "" && param != "builtins" {
if err := pyenv.PipInstall(param); err != nil {
panic(fmt.Sprintf("pip install failed for '%s': %v\n\tPYTHONHOME=%s\n\thint: ensure pip is available and network reachable, or pin a version in LLGoPackage (e.g. py.numpy==1.26.4)", param, pyenv.PythonHome(), err))
}
}
}
default:
err := buildPkg(ctx, aPkg, verbose)
if err != nil {
Expand Down Expand Up @@ -595,6 +615,17 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
}
}
})

// Heuristic: if link args reference python libs, force python init
if !needPyInit {
for _, arg := range linkArgs {
if strings.Contains(arg, "python") {
needPyInit = true
break
}
}
}

entryObjFile, err := genMainModuleFile(ctx, llssa.PkgRuntime, pkg, needRuntime, needPyInit)
check(err)
// defer os.Remove(entryLLFile)
Expand All @@ -609,6 +640,14 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
err = linkObjFiles(ctx, app, objFiles, linkArgs, verbose)
check(err)

// After linking, ensure the executable references libpython via @rpath
if needPyInit && ctx.buildConf.Goos == "darwin" {
if dep := findDylibDep(app, "python3"); dep != "" && !strings.HasPrefix(dep, "@rpath") {
base := filepath.Base(dep)
_ = exec.Command("install_name_tool", "-change", dep, "@rpath/"+base, app).Run()
}
}

switch mode {
case ModeTest:
cmd := exec.Command(app, conf.RunArgs...)
Expand Down
127 changes: 127 additions & 0 deletions internal/pyenv/fetch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package pyenv

import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
)

func downloadAndExtract(url, dir string) (err error) {
if _, err = os.Stat(dir); err == nil {
os.RemoveAll(dir)
}
tempDir := dir + ".temp"
os.RemoveAll(tempDir)
if err = os.MkdirAll(tempDir, 0755); err != nil {
return fmt.Errorf("failed to create temporary directory: %w", err)
}

urlPath := strings.Split(url, "/")
filename := urlPath[len(urlPath)-1]
localFile := filepath.Join(tempDir, filename)
if err = downloadFile(url, localFile); err != nil {
return fmt.Errorf("failed to download file: %w", err)
}
defer os.Remove(localFile)

if strings.HasSuffix(filename, ".tar.gz") || strings.HasSuffix(filename, ".tgz") {
err = extractTarGz(localFile, tempDir)
} else {
return fmt.Errorf("unsupported archive format: %s", filename)
}
if err != nil {
return fmt.Errorf("failed to extract archive: %w", err)
}
if err = os.Rename(tempDir, dir); err != nil {
return fmt.Errorf("failed to rename directory: %w", err)
}
return nil
}

func downloadFile(url, filepath string) error {
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s", resp.Status)
}
_, err = io.Copy(out, resp.Body)
return err
}

func extractTarGz(tarGzFile, dest string) error {
file, err := os.Open(tarGzFile)
if err != nil {
return err
}
defer file.Close()
gzr, err := gzip.NewReader(file)
if err != nil {
return err
}
defer gzr.Close()
tr := tar.NewReader(gzr)
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
target := filepath.Join(dest, header.Name)
if !strings.HasPrefix(target, filepath.Clean(dest)+string(os.PathSeparator)) {
return fmt.Errorf("%s: illegal file path", target)
}
switch header.Typeflag {
case tar.TypeDir:
if err := os.MkdirAll(target, 0755); err != nil {
return err
}
case tar.TypeReg:
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
return err
}
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
return err
}
if _, err := io.Copy(f, tr); err != nil {
f.Close()
return err
}
f.Close()
case tar.TypeSymlink:
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
return err
}
_ = os.Remove(target)
if err := os.Symlink(header.Linkname, target); err != nil {
return err
}
case tar.TypeLink:
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
return err
}
_ = os.Remove(target)
linkTarget := filepath.Join(dest, header.Linkname)
if err := os.Link(linkTarget, target); err != nil {
return err
}

}
}
return nil
}
Loading
Loading