diff --git a/internal/build/build.go b/internal/build/build.go index c9d11fe2..010aab8f 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -38,13 +38,13 @@ import ( "golang.org/x/tools/go/ssa" "github.com/goplus/llgo/cl" + "github.com/goplus/llgo/internal/clang" "github.com/goplus/llgo/internal/crosscompile" "github.com/goplus/llgo/internal/env" "github.com/goplus/llgo/internal/mockable" "github.com/goplus/llgo/internal/packages" "github.com/goplus/llgo/internal/typepatch" "github.com/goplus/llgo/ssa/abi" - "github.com/goplus/llgo/xtool/clang" xenv "github.com/goplus/llgo/xtool/env" "github.com/goplus/llgo/xtool/env/llvm" @@ -376,19 +376,13 @@ type context struct { } func (c *context) compiler() *clang.Cmd { - cmd := c.env.Clang() - if c.crossCompile.CC != "" { - cmd = clang.New(c.crossCompile.CC) - } + cmd := clang.NewCompiler(c.crossCompile) cmd.Verbose = c.buildConf.Verbose return cmd } func (c *context) linker() *clang.Cmd { - cmd := c.compiler() - if c.crossCompile.Linker != "" { - cmd = clang.New(c.crossCompile.Linker) - } + cmd := clang.NewLinker(c.crossCompile) cmd.Verbose = c.buildConf.Verbose return cmd } @@ -658,8 +652,6 @@ func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose buildArgs = append(buildArgs, "-gdwarf-4") } - buildArgs = append(buildArgs, ctx.crossCompile.LDFLAGS...) - buildArgs = append(buildArgs, ctx.crossCompile.EXTRAFLAGS...) buildArgs = append(buildArgs, objFiles...) cmd := ctx.linker() @@ -854,8 +846,6 @@ func exportObject(ctx *context, pkgPath string, exportFile string, data []byte) } exportFile += ".o" args := []string{"-o", exportFile, "-c", f.Name(), "-Wno-override-module"} - args = append(args, ctx.crossCompile.CCFLAGS...) - args = append(args, ctx.crossCompile.CFLAGS...) if ctx.buildConf.Verbose { fmt.Fprintln(os.Stderr, "clang", args) } @@ -1087,8 +1077,6 @@ func clFile(ctx *context, args []string, cFile, expFile string, procFile func(li llFile += ".o" args = append(args, "-o", llFile, "-c", cFile) } - args = append(args, ctx.crossCompile.CCFLAGS...) - args = append(args, ctx.crossCompile.CFLAGS...) if verbose { fmt.Fprintln(os.Stderr, "clang", args) } diff --git a/internal/clang/clang.go b/internal/clang/clang.go new file mode 100644 index 00000000..916b49b2 --- /dev/null +++ b/internal/clang/clang.go @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package clang + +import ( + "fmt" + "io" + "os" + "os/exec" + "runtime" + "strings" + + "github.com/goplus/llgo/internal/crosscompile" + "github.com/goplus/llgo/xtool/safesplit" +) + +// Cmd represents a clang command with environment and crosscompile support. +type Cmd struct { + app string + Env []string + Verbose bool + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer + crossCompile crosscompile.Export +} + +// New creates a new clang command with crosscompile configuration. +func New(app string, crossCompile crosscompile.Export) *Cmd { + if app == "" { + app = "clang" + } + return &Cmd{ + app: app, + Env: nil, + Verbose: false, + Stdin: nil, + Stdout: os.Stdout, + Stderr: os.Stderr, + crossCompile: crossCompile, + } +} + +// NewCompiler creates a compiler command with proper flag merging. +func NewCompiler(crossCompile crosscompile.Export) *Cmd { + app := "clang" + if crossCompile.CC != "" { + app = crossCompile.CC + } + return New(app, crossCompile) +} + +// NewLinker creates a linker command with proper flag merging. +func NewLinker(crossCompile crosscompile.Export) *Cmd { + app := "clang" + if crossCompile.Linker != "" { + app = crossCompile.Linker + } else if crossCompile.CC != "" { + app = crossCompile.CC + } + return New(app, crossCompile) +} + +// Compile executes a compilation command with merged flags. +func (c *Cmd) Compile(args ...string) error { + flags := c.mergeCompilerFlags() + allArgs := make([]string, 0, len(flags)+len(args)) + allArgs = append(allArgs, flags...) + allArgs = append(allArgs, args...) + return c.exec(allArgs...) +} + +// Link executes a linking command with merged flags. +func (c *Cmd) Link(args ...string) error { + flags := c.mergeLinkerFlags() + allArgs := make([]string, 0, len(flags)+len(args)) + allArgs = append(allArgs, flags...) + allArgs = append(allArgs, args...) + return c.exec(allArgs...) +} + +// mergeCompilerFlags merges environment CCFLAGS/CFLAGS with crossCompile flags. +func (c *Cmd) mergeCompilerFlags() []string { + var flags []string + + // Add environment CCFLAGS + if envCCFlags := os.Getenv("CCFLAGS"); envCCFlags != "" { + flags = append(flags, safesplit.SplitPkgConfigFlags(envCCFlags)...) + } + + // Add environment CFLAGS + if envCFlags := os.Getenv("CFLAGS"); envCFlags != "" { + flags = append(flags, safesplit.SplitPkgConfigFlags(envCFlags)...) + } + + // Add crossCompile CCFLAGS + flags = append(flags, c.crossCompile.CCFLAGS...) + + // Add crossCompile CFLAGS + flags = append(flags, c.crossCompile.CFLAGS...) + + return flags +} + +// mergeLinkerFlags merges environment CCFLAGS/LDFLAGS with crossCompile flags. +func (c *Cmd) mergeLinkerFlags() []string { + var flags []string + + // Add environment CCFLAGS (for linker) + if envCCFlags := os.Getenv("CCFLAGS"); envCCFlags != "" { + flags = append(flags, safesplit.SplitPkgConfigFlags(envCCFlags)...) + } + + // Add environment LDFLAGS + if envLDFlags := os.Getenv("LDFLAGS"); envLDFlags != "" { + flags = append(flags, safesplit.SplitPkgConfigFlags(envLDFlags)...) + } + + // Add crossCompile CCFLAGS (for linker) + flags = append(flags, c.crossCompile.CCFLAGS...) + + // Add crossCompile LDFLAGS + flags = append(flags, c.crossCompile.LDFLAGS...) + + return flags +} + +// exec executes the clang command with given arguments. +func (c *Cmd) exec(args ...string) error { + cmd := exec.Command(c.app, args...) + if c.Verbose { + fmt.Fprintf(os.Stderr, "%v\n", cmd) + } + cmd.Stdin = c.Stdin + cmd.Stdout = c.Stdout + cmd.Stderr = c.Stderr + if c.Env != nil { + cmd.Env = c.Env + } + return cmd.Run() +} + +// CheckLinkArgs validates linking arguments by attempting a test compile. +func (c *Cmd) CheckLinkArgs(cmdArgs []string, wasm bool) error { + // Create a temporary file with appropriate extension + extension := "" + if wasm { + extension = ".wasm" + } else if runtime.GOOS == "windows" { + extension = ".exe" + } + + tmpFile, err := os.CreateTemp("", "llgo_check*"+extension) + if err != nil { + return fmt.Errorf("failed to create temporary file: %w", err) + } + tmpFile.Close() + tmpPath := tmpFile.Name() + + // Make sure to delete the temporary file when done + defer os.Remove(tmpPath) + + // Set up compilation arguments + args := append([]string{}, cmdArgs...) + args = append(args, []string{"-x", "c", "-o", tmpPath, "-"}...) + src := "int main() {return 0;}" + srcIn := strings.NewReader(src) + c.Stdin = srcIn + + // Execute the command with linker flags + return c.Link(args...) +} \ No newline at end of file diff --git a/internal/crosscompile/crosscompile.go b/internal/crosscompile/crosscompile.go index ac42392f..2812e092 100644 --- a/internal/crosscompile/crosscompile.go +++ b/internal/crosscompile/crosscompile.go @@ -16,11 +16,10 @@ import ( ) type Export struct { - CC string // Compiler to use - CCFLAGS []string - CFLAGS []string - LDFLAGS []string - EXTRAFLAGS []string + CC string // Compiler to use + CCFLAGS []string + CFLAGS []string + LDFLAGS []string // Additional fields from target configuration LLVMTarget string @@ -133,6 +132,7 @@ func use(goos, goarch string, wasiThreads bool) (export Export, err error) { // not cross compile // Set up basic flags for non-cross-compile export.LDFLAGS = []string{ + "-L" + filepath.Join(clangRoot, "lib"), "-target", targetTriple, "-Wno-override-module", "-Wl,--error-limit=0", @@ -146,7 +146,7 @@ func use(goos, goarch string, wasiThreads bool) (export Export, err error) { err = fmt.Errorf("failed to get macOS SDK path: %w", sysrootErr) return } - export.LDFLAGS = append([]string{"--sysroot=" + sysrootPath}, export.LDFLAGS...) + export.CCFLAGS = append(export.CCFLAGS, []string{"--sysroot=" + sysrootPath}...) } // Add OS-specific flags @@ -284,7 +284,7 @@ func use(goos, goarch string, wasiThreads bool) (export Export, err error) { // "-z", "stack-size=10485760", // 10MB // "-Wl,--export=malloc", "-Wl,--export=free", } - export.EXTRAFLAGS = []string{ + export.LDFLAGS = append(export.LDFLAGS, []string{ "-sENVIRONMENT=web,worker", "-DPLATFORM_WEB", "-sEXPORT_KEEPALIVE=1", @@ -296,7 +296,7 @@ func use(goos, goarch string, wasiThreads bool) (export Export, err error) { "-sEXPORT_ALL=1", "-sASYNCIFY=1", "-sSTACK_SIZE=5242880", // 50MB - } + }...) default: err = errors.New("unsupported GOOS for WebAssembly: " + goos) @@ -377,7 +377,6 @@ func useTarget(targetName string) (export Export, err error) { export.CFLAGS = config.CFlags export.CCFLAGS = ccflags export.LDFLAGS = append(ldflags, config.LDFlags...) - export.EXTRAFLAGS = []string{} return export, nil }