Files
llgo/internal/build/run.go
2025-09-07 16:37:38 +08:00

153 lines
4.1 KiB
Go

/*
* 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 build
import (
"fmt"
"os"
"os/exec"
"strings"
"github.com/goplus/llgo/internal/mockable"
"github.com/goplus/llgo/internal/shellparse"
)
func runNative(ctx *context, app, pkgDir, pkgName string, conf *Config, mode Mode) error {
switch mode {
case ModeRun:
args := make([]string, 0, len(conf.RunArgs)+1)
if isWasmTarget(conf.Goos) {
wasmer := os.ExpandEnv(WasmRuntime())
wasmerArgs := strings.Split(wasmer, " ")
wasmerCmd := wasmerArgs[0]
wasmerArgs = wasmerArgs[1:]
switch wasmer {
case "wasmtime":
args = append(args, "--wasm", "multi-memory=true", app)
args = append(args, conf.RunArgs...)
case "iwasm":
args = append(args, "--stack-size=819200000", "--heap-size=800000000", app)
args = append(args, conf.RunArgs...)
default:
args = append(args, wasmerArgs...)
args = append(args, app)
args = append(args, conf.RunArgs...)
}
app = wasmerCmd
} else {
args = conf.RunArgs
}
cmd := exec.Command(app, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return err
}
if s := cmd.ProcessState; s != nil {
mockable.Exit(s.ExitCode())
}
case ModeTest:
cmd := exec.Command(app, conf.RunArgs...)
cmd.Dir = pkgDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
fmt.Fprintf(os.Stderr, "%s: exit code %d\n", app, exitErr.ExitCode())
if !ctx.testFail {
ctx.testFail = true
}
} else {
fmt.Fprintf(os.Stderr, "failed to run test %s: %v\n", app, err)
if !ctx.testFail {
ctx.testFail = true
}
}
}
case ModeCmpTest:
cmpTest(pkgDir, pkgName, app, conf.GenExpect, conf.RunArgs)
}
return nil
}
func runInEmulator(emulator string, envMap map[string]string, pkgDir, pkgName string, conf *Config, mode Mode, verbose bool) error {
if emulator == "" {
return fmt.Errorf("target %s does not have emulator configured", conf.Target)
}
if verbose {
fmt.Fprintf(os.Stderr, "Using emulator: %s\n", emulator)
}
switch mode {
case ModeRun:
return runEmuCmd(envMap, emulator, conf.RunArgs, verbose)
case ModeTest:
return runEmuCmd(envMap, emulator, conf.RunArgs, verbose)
case ModeCmpTest:
cmpTest(pkgDir, pkgName, envMap["out"], conf.GenExpect, conf.RunArgs)
return nil
}
return nil
}
// runEmuCmd runs the application in emulator by formatting the emulator command template
func runEmuCmd(envMap map[string]string, emulatorTemplate string, runArgs []string, verbose bool) error {
// Expand the emulator command template
emulatorCmd := emulatorTemplate
for placeholder, path := range envMap {
var target string
if placeholder == "" {
target = "{}"
} else {
target = "{" + placeholder + "}"
}
emulatorCmd = strings.ReplaceAll(emulatorCmd, target, path)
}
if verbose {
fmt.Fprintf(os.Stderr, "Running in emulator: %s\n", emulatorCmd)
}
// Parse command and arguments safely handling quoted strings
cmdParts, err := shellparse.Parse(emulatorCmd)
if err != nil {
return fmt.Errorf("failed to parse emulator command: %w", err)
}
if len(cmdParts) == 0 {
return fmt.Errorf("empty emulator command")
}
// Add run arguments to the end
cmdParts = append(cmdParts, runArgs...)
// Execute the emulator command
cmd := exec.Command(cmdParts[0], cmdParts[1:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return err
}
if s := cmd.ProcessState; s != nil {
mockable.Exit(s.ExitCode())
}
return nil
}