build: download and compile with wasi-sdk
This commit is contained in:
@@ -37,6 +37,7 @@ import (
|
|||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
|
|
||||||
"github.com/goplus/llgo/cl"
|
"github.com/goplus/llgo/cl"
|
||||||
|
"github.com/goplus/llgo/internal/crosscompile"
|
||||||
"github.com/goplus/llgo/internal/env"
|
"github.com/goplus/llgo/internal/env"
|
||||||
"github.com/goplus/llgo/internal/mockable"
|
"github.com/goplus/llgo/internal/mockable"
|
||||||
"github.com/goplus/llgo/internal/packages"
|
"github.com/goplus/llgo/internal/packages"
|
||||||
@@ -291,7 +292,9 @@ func Do(args []string, conf *Config) ([]Package, error) {
|
|||||||
os.Setenv("PATH", env.BinDir()+":"+os.Getenv("PATH")) // TODO(xsw): check windows
|
os.Setenv("PATH", env.BinDir()+":"+os.Getenv("PATH")) // TODO(xsw): check windows
|
||||||
|
|
||||||
output := conf.OutFile != ""
|
output := conf.OutFile != ""
|
||||||
ctx := &context{env, cfg, progSSA, prog, dedup, patches, make(map[string]none), initial, mode, 0, output, make(map[*packages.Package]bool), make(map[*packages.Package]bool), conf}
|
export, err := crosscompile.UseCrossCompileSDK(conf.Goos, conf.Goarch)
|
||||||
|
check(err)
|
||||||
|
ctx := &context{env, cfg, progSSA, prog, dedup, patches, make(map[string]none), initial, mode, 0, output, make(map[*packages.Package]bool), make(map[*packages.Package]bool), conf, export}
|
||||||
pkgs, err := buildAllPkgs(ctx, initial, verbose)
|
pkgs, err := buildAllPkgs(ctx, initial, verbose)
|
||||||
check(err)
|
check(err)
|
||||||
if mode == ModeGen {
|
if mode == ModeGen {
|
||||||
@@ -357,7 +360,8 @@ type context struct {
|
|||||||
needRt map[*packages.Package]bool
|
needRt map[*packages.Package]bool
|
||||||
needPyInit map[*packages.Package]bool
|
needPyInit map[*packages.Package]bool
|
||||||
|
|
||||||
buildConf *Config
|
buildConf *Config
|
||||||
|
crossCompile crosscompile.Export
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs []*aPackage, err error) {
|
func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs []*aPackage, err error) {
|
||||||
@@ -543,6 +547,9 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, linkArgs
|
|||||||
args = append(args, "-gdwarf-4")
|
args = append(args, "-gdwarf-4")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args = append(args, ctx.crossCompile.CCFLAGS...)
|
||||||
|
args = append(args, ctx.crossCompile.LDFLAGS...)
|
||||||
|
|
||||||
cmd := ctx.env.Clang()
|
cmd := ctx.env.Clang()
|
||||||
cmd.Verbose = verbose
|
cmd.Verbose = verbose
|
||||||
err = cmd.Link(args...)
|
err = cmd.Link(args...)
|
||||||
@@ -1022,6 +1029,8 @@ func clFile(ctx *context, args []string, cFile, expFile string, procFile func(li
|
|||||||
cflags := buildCflags(ctx.buildConf.Goos, ctx.buildConf.Goarch, targetTriple)
|
cflags := buildCflags(ctx.buildConf.Goos, ctx.buildConf.Goarch, targetTriple)
|
||||||
args = append(cflags, args...)
|
args = append(cflags, args...)
|
||||||
args = append(args, "-emit-llvm", "-S", "-o", llFile, "-c", cFile)
|
args = append(args, "-emit-llvm", "-S", "-o", llFile, "-c", cFile)
|
||||||
|
args = append(args, ctx.crossCompile.CCFLAGS...)
|
||||||
|
args = append(args, ctx.crossCompile.CFLAGS...)
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Fprintln(os.Stderr, "clang", args)
|
fmt.Fprintln(os.Stderr, "clang", args)
|
||||||
}
|
}
|
||||||
|
|||||||
62
internal/crosscompile/cosscompile.go
Normal file
62
internal/crosscompile/cosscompile.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package crosscompile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/goplus/llgo/internal/env"
|
||||||
|
"github.com/goplus/llgo/internal/xtool/llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Export struct {
|
||||||
|
CCFLAGS []string
|
||||||
|
CFLAGS []string
|
||||||
|
LDFLAGS []string
|
||||||
|
}
|
||||||
|
|
||||||
|
const wasiSdkUrl = "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-macos.tar.gz"
|
||||||
|
|
||||||
|
func cacheDir() string {
|
||||||
|
return filepath.Join(env.LLGoCacheDir(), "crosscompile")
|
||||||
|
}
|
||||||
|
|
||||||
|
func UseCrossCompileSDK(goos, goarch string) (export Export, err error) {
|
||||||
|
if runtime.GOOS == goos && runtime.GOARCH == goarch {
|
||||||
|
// not cross compile
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if goarch == "wasm" {
|
||||||
|
sdkDir := filepath.Join(cacheDir(), llvm.GetTargetTriple(goos, goarch))
|
||||||
|
if _, err = os.Stat(sdkDir); err != nil {
|
||||||
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = downloadAndExtract(wasiSdkUrl, sdkDir); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Set up flags for the SDK
|
||||||
|
wasiSdkRoot := filepath.Join(sdkDir, "wasi-sdk-25.0-x86_64-macos")
|
||||||
|
sysrootDir := filepath.Join(wasiSdkRoot, "share", "wasi-sysroot")
|
||||||
|
libclangDir := filepath.Join(wasiSdkRoot, "lib", "clang", "19")
|
||||||
|
includeDir := filepath.Join(sysrootDir, "include", "wasm32-wasip1")
|
||||||
|
libDir := filepath.Join(sysrootDir, "lib", "wasm32-wasip1")
|
||||||
|
|
||||||
|
export.CCFLAGS = []string{
|
||||||
|
"--sysroot=" + sysrootDir,
|
||||||
|
"-resource-dir=" + libclangDir,
|
||||||
|
}
|
||||||
|
export.CFLAGS = []string{
|
||||||
|
"-I" + includeDir,
|
||||||
|
}
|
||||||
|
export.LDFLAGS = []string{
|
||||||
|
"-L" + libDir,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO(lijie): supports other platforms
|
||||||
|
return
|
||||||
|
}
|
||||||
156
internal/crosscompile/crosscompile_test.go
Normal file
156
internal/crosscompile/crosscompile_test.go
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
//go:build !llgo
|
||||||
|
// +build !llgo
|
||||||
|
|
||||||
|
package crosscompile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sysrootPrefix = "--sysroot="
|
||||||
|
resourceDirPrefix = "-resource-dir="
|
||||||
|
includePrefix = "-I"
|
||||||
|
libPrefix = "-L"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUseCrossCompileSDK(t *testing.T) {
|
||||||
|
// Skip long-running tests unless explicitly enabled
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("Skipping test in short mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test cases
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
goos string
|
||||||
|
goarch string
|
||||||
|
expectSDK bool
|
||||||
|
expectCCFlags bool
|
||||||
|
expectCFlags bool
|
||||||
|
expectLDFlags bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Same Platform",
|
||||||
|
goos: runtime.GOOS,
|
||||||
|
goarch: runtime.GOARCH,
|
||||||
|
expectSDK: false,
|
||||||
|
expectCCFlags: false,
|
||||||
|
expectCFlags: false,
|
||||||
|
expectLDFlags: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "WASM Target",
|
||||||
|
goos: "wasip1",
|
||||||
|
goarch: "wasm",
|
||||||
|
expectSDK: true,
|
||||||
|
expectCCFlags: true,
|
||||||
|
expectCFlags: true,
|
||||||
|
expectLDFlags: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unsupported Target",
|
||||||
|
goos: "windows",
|
||||||
|
goarch: "amd64",
|
||||||
|
expectSDK: false,
|
||||||
|
expectCCFlags: false,
|
||||||
|
expectCFlags: false,
|
||||||
|
expectLDFlags: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a temporary directory for the cache
|
||||||
|
tempDir, err := os.MkdirTemp("", "crosscompile_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
|
// Set environment variable for cache directory
|
||||||
|
oldEnv := os.Getenv("LLGO_CACHE_DIR")
|
||||||
|
os.Setenv("LLGO_CACHE_DIR", tempDir)
|
||||||
|
defer os.Setenv("LLGO_CACHE_DIR", oldEnv)
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
export, err := UseCrossCompileSDK(tc.goos, tc.goarch)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("export: %+v", export)
|
||||||
|
|
||||||
|
if tc.expectSDK {
|
||||||
|
// Check if flags are set correctly
|
||||||
|
if tc.expectCCFlags && len(export.CCFLAGS) == 0 {
|
||||||
|
t.Error("Expected CCFLAGS to be set, but they are empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectCFlags && len(export.CFLAGS) == 0 {
|
||||||
|
t.Error("Expected CFLAGS to be set, but they are empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectLDFlags && len(export.LDFLAGS) == 0 {
|
||||||
|
t.Error("Expected LDFLAGS to be set, but they are empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for specific flags
|
||||||
|
if tc.expectCCFlags {
|
||||||
|
hasSysroot := false
|
||||||
|
hasResourceDir := false
|
||||||
|
|
||||||
|
for _, flag := range export.CCFLAGS {
|
||||||
|
if len(flag) >= len(sysrootPrefix) && flag[:len(sysrootPrefix)] == sysrootPrefix {
|
||||||
|
hasSysroot = true
|
||||||
|
}
|
||||||
|
if len(flag) >= len(resourceDirPrefix) && flag[:len(resourceDirPrefix)] == resourceDirPrefix {
|
||||||
|
hasResourceDir = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasSysroot {
|
||||||
|
t.Error("Missing --sysroot flag in CCFLAGS")
|
||||||
|
}
|
||||||
|
if !hasResourceDir {
|
||||||
|
t.Error("Missing -resource-dir flag in CCFLAGS")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectCFlags {
|
||||||
|
hasInclude := false
|
||||||
|
|
||||||
|
for _, flag := range export.CFLAGS {
|
||||||
|
if len(flag) >= len(includePrefix) && flag[:len(includePrefix)] == includePrefix {
|
||||||
|
hasInclude = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasInclude {
|
||||||
|
t.Error("Missing -I flag in CFLAGS")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectLDFlags {
|
||||||
|
hasLib := false
|
||||||
|
|
||||||
|
for _, flag := range export.LDFLAGS {
|
||||||
|
if len(flag) >= len(libPrefix) && flag[:len(libPrefix)] == libPrefix {
|
||||||
|
hasLib = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasLib {
|
||||||
|
t.Error("Missing -L flag in LDFLAGS")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(export.CCFLAGS) != 0 || len(export.CFLAGS) != 0 || len(export.LDFLAGS) != 0 {
|
||||||
|
t.Errorf("Expected empty export, got CCFLAGS=%v, CFLAGS=%v, LDFLAGS=%v",
|
||||||
|
export.CCFLAGS, export.CFLAGS, export.LDFLAGS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
109
internal/crosscompile/fetch.go
Normal file
109
internal/crosscompile/fetch.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package crosscompile
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
8
internal/env/env.go
vendored
8
internal/env/env.go
vendored
@@ -32,6 +32,14 @@ func GOROOT() string {
|
|||||||
panic("cannot get GOROOT: " + err.Error())
|
panic("cannot get GOROOT: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LLGoCacheDir() string {
|
||||||
|
userCacheDir, err := os.UserCacheDir()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return filepath.Join(userCacheDir, "llgo")
|
||||||
|
}
|
||||||
|
|
||||||
func LLGoRuntimeDir() string {
|
func LLGoRuntimeDir() string {
|
||||||
root := LLGoROOT()
|
root := LLGoROOT()
|
||||||
if root != "" {
|
if root != "" {
|
||||||
|
|||||||
Reference in New Issue
Block a user