Merge pull request #968 from cpunion/find-llgo-root

Find llgo root
This commit is contained in:
xushiwei
2025-02-01 19:01:34 +08:00
committed by GitHub
19 changed files with 484 additions and 288 deletions

View File

@@ -1,8 +0,0 @@
coverage:
ignore:
- "compiler/chore"
- "chore"
- "py"
- "x"
- "cpp"
- "runtime"

3
.github/codecov.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
coverage:
ignore:
- "compiler/chore"

View File

@@ -54,5 +54,3 @@ jobs:
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v5
with: with:
token: ${{secrets.CODECOV_TOKEN}} token: ${{secrets.CODECOV_TOKEN}}
slug: goplus/llgo
codecov_yml_path: .github/ci-config/codecov.yml

View File

@@ -0,0 +1,62 @@
package main
import (
"log"
"os"
"path/filepath"
"strings"
"testing"
)
func TestMain(t *testing.T) {
// Create test package in current module
testPkg := filepath.Join(".testdata_dont_commit", "hello")
err := os.MkdirAll(testPkg, 0755)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(filepath.Join(".testdata_dont_commit"))
helloFile := filepath.Join(testPkg, "hello.go")
err = os.WriteFile(helloFile, []byte(`package hello
func Hello() string {
return "Hello, World!"
}
`), 0644)
if err != nil {
t.Fatal(err)
}
// Save original args and restore them after test
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
// Get absolute path to test package
absTestPkg, err := filepath.Abs(testPkg)
if err != nil {
t.Fatal(err)
}
// Set test arguments
os.Args = []string{"llgen", absTestPkg}
// Run main
main()
// Check if the output file exists
outputFile := filepath.Join(testPkg, "llgo_autogen.ll")
log.Printf("Generated file: %s", filepath.Join(absTestPkg, "llgo_autogen.ll"))
if _, err = os.Stat(outputFile); err != nil {
t.Fatalf("Generated file should exist: %v", err)
}
// Read and verify file content
content, err := os.ReadFile(outputFile)
if err != nil {
t.Fatalf("Should be able to read generated file: %v", err)
}
if !strings.Contains(string(content), "define") {
t.Error("Generated file should contain LLVM IR code")
}
}

View File

@@ -17,6 +17,9 @@
package cl_test package cl_test
import ( import (
"io"
"log"
"os"
"testing" "testing"
"github.com/goplus/llgo/compiler/cl" "github.com/goplus/llgo/compiler/cl"
@@ -24,6 +27,13 @@ import (
"github.com/goplus/llgo/compiler/internal/build" "github.com/goplus/llgo/compiler/internal/build"
) )
func init() {
devNull, _ := os.OpenFile(os.DevNull, os.O_WRONLY, 0)
os.Stderr = devNull
os.Stdout = devNull
log.SetOutput(io.Discard)
}
func testCompile(t *testing.T, src, expected string) { func testCompile(t *testing.T, src, expected string) {
t.Helper() t.Helper()
cltest.TestCompileEx(t, src, "foo.go", expected, false) cltest.TestCompileEx(t, src, "foo.go", expected, false)

View File

@@ -23,6 +23,7 @@ import (
"github.com/goplus/llgo/compiler/cmd/internal/base" "github.com/goplus/llgo/compiler/cmd/internal/base"
"github.com/goplus/llgo/compiler/internal/build" "github.com/goplus/llgo/compiler/internal/build"
"github.com/goplus/llgo/compiler/internal/mockable"
) )
// llgo build // llgo build
@@ -47,6 +48,6 @@ func runCmd(cmd *base.Command, args []string) {
_, err := build.Do(args, conf) _, err := build.Do(args, conf)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(1) mockable.Exit(1)
} }
} }

View File

@@ -29,6 +29,7 @@ import (
"unicode/utf8" "unicode/utf8"
"github.com/goplus/llgo/compiler/cmd/internal/base" "github.com/goplus/llgo/compiler/cmd/internal/base"
"github.com/goplus/llgo/compiler/internal/mockable"
) )
// Help implements the 'help' command. // Help implements the 'help' command.
@@ -49,7 +50,7 @@ Args:
helpSuccess += " " + strings.Join(args[:i], " ") helpSuccess += " " + strings.Join(args[:i], " ")
} }
fmt.Fprintf(os.Stderr, "llgo help %s: unknown help topic. Run '%s'.\n", strings.Join(args, " "), helpSuccess) fmt.Fprintf(os.Stderr, "llgo help %s: unknown help topic. Run '%s'.\n", strings.Join(args, " "), helpSuccess)
os.Exit(2) mockable.Exit(2)
} }
if len(cmd.Commands) > 0 { if len(cmd.Commands) > 0 {
@@ -98,7 +99,7 @@ func tmpl(w io.Writer, text string, data interface{}) {
if ew.err != nil { if ew.err != nil {
// I/O error writing. Ignore write on closed pipe. // I/O error writing. Ignore write on closed pipe.
if strings.Contains(ew.err.Error(), "pipe") { if strings.Contains(ew.err.Error(), "pipe") {
os.Exit(1) mockable.Exit(1)
} }
log.Fatalf("writing output: %v", ew.err) log.Fatalf("writing output: %v", ew.err)
} }

View File

@@ -23,6 +23,7 @@ import (
"github.com/goplus/llgo/compiler/cmd/internal/base" "github.com/goplus/llgo/compiler/cmd/internal/base"
"github.com/goplus/llgo/compiler/internal/build" "github.com/goplus/llgo/compiler/internal/build"
"github.com/goplus/llgo/compiler/internal/mockable"
) )
// llgo install // llgo install
@@ -40,6 +41,6 @@ func runCmd(cmd *base.Command, args []string) {
_, err := build.Do(args, conf) _, err := build.Do(args, conf)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(1) mockable.Exit(1)
} }
} }

View File

@@ -25,6 +25,7 @@ import (
"github.com/goplus/llgo/compiler/cmd/internal/base" "github.com/goplus/llgo/compiler/cmd/internal/base"
"github.com/goplus/llgo/compiler/internal/build" "github.com/goplus/llgo/compiler/internal/build"
"github.com/goplus/llgo/compiler/internal/mockable"
) )
var ( var (
@@ -68,7 +69,7 @@ func runCmdEx(_ *base.Command, args []string, mode build.Mode) {
_, err = build.Do(args, conf) _, err = build.Do(args, conf)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(1) mockable.Exit(1)
} }
} }

View File

@@ -21,7 +21,7 @@ import (
"runtime" "runtime"
"github.com/goplus/llgo/compiler/cmd/internal/base" "github.com/goplus/llgo/compiler/cmd/internal/base"
"github.com/goplus/llgo/x/env" "github.com/goplus/llgo/compiler/internal/env"
) )
// llgo version // llgo version

View File

@@ -32,6 +32,7 @@ import (
"github.com/goplus/llgo/compiler/cmd/internal/install" "github.com/goplus/llgo/compiler/cmd/internal/install"
"github.com/goplus/llgo/compiler/cmd/internal/run" "github.com/goplus/llgo/compiler/cmd/internal/run"
"github.com/goplus/llgo/compiler/cmd/internal/version" "github.com/goplus/llgo/compiler/cmd/internal/version"
"github.com/goplus/llgo/compiler/internal/mockable"
) )
func mainUsage() { func mainUsage() {
@@ -77,7 +78,7 @@ BigCmdLoop:
bigCmd = cmd bigCmd = cmd
if len(args) == 0 { if len(args) == 0 {
help.PrintUsage(os.Stderr, bigCmd) help.PrintUsage(os.Stderr, bigCmd)
os.Exit(2) mockable.Exit(2)
} }
if args[0] == "help" { if args[0] == "help" {
help.Help(os.Stderr, append(strings.Split(base.CmdName, " "), args[1:]...)) help.Help(os.Stderr, append(strings.Split(base.CmdName, " "), args[1:]...))
@@ -97,6 +98,6 @@ BigCmdLoop:
helpArg = " " + base.CmdName[:i] helpArg = " " + base.CmdName[:i]
} }
fmt.Fprintf(os.Stderr, "llgo %s: unknown command\nRun 'llgo help%s' for usage.\n", base.CmdName, helpArg) fmt.Fprintf(os.Stderr, "llgo %s: unknown command\nRun 'llgo help%s' for usage.\n", base.CmdName, helpArg)
os.Exit(2) mockable.Exit(2)
} }
} }

View File

@@ -1,52 +1,76 @@
package main package main
import ( import (
"bytes" "fmt"
"flag"
"io"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
"github.com/goplus/llgo/compiler/cmd/internal/base" "github.com/goplus/llgo/compiler/internal/mockable"
"github.com/goplus/llgo/compiler/cmd/internal/build"
"github.com/goplus/llgo/compiler/cmd/internal/help"
"github.com/goplus/llgo/compiler/cmd/internal/install"
"github.com/goplus/llgo/compiler/cmd/internal/run"
"github.com/goplus/llgo/compiler/cmd/internal/version"
) )
func setupTestProject(t *testing.T) string { func init() {
// Create a temporary directory for the test project origWd, err := os.Getwd()
tmpDir, err := os.MkdirTemp("", "llgo-test-*")
if err != nil { if err != nil {
t.Fatalf("Failed to create temp dir: %v", err) panic(err)
} }
// Create a simple Go program // Set LLGO_ROOT to project root
mainFile := filepath.Join(tmpDir, "main.go") llgoRoot := filepath.Join(origWd, "../../..")
err = os.WriteFile(mainFile, []byte(`package main if err := os.Setenv("LLGO_ROOT", llgoRoot); err != nil {
panic(fmt.Sprintf("Failed to set LLGO_ROOT: %v", err))
}
}
func setupTestProject(t *testing.T) string {
tmpDir := t.TempDir()
// Create main.go
mainGo := filepath.Join(tmpDir, "main.go")
err := os.WriteFile(mainGo, []byte(`package main
import "fmt" import "fmt"
import "os"
func main() { func main() {
fmt.Println("Hello, LLGO!") var arg string = "LLGO"
if len(os.Args) > 1 {
arg = os.Args[1]
}
switch arg {
case "stderr":
fmt.Fprintln(os.Stderr, "Hello, World!")
case "exit":
os.Exit(1)
default:
fmt.Println("Hello, " + arg + "!")
}
} }
`), 0644) `), 0644)
if err != nil { if err != nil {
os.RemoveAll(tmpDir) t.Fatalf("Failed to create main.go: %v", err)
t.Fatalf("Failed to write main.go: %v", err) }
// Create llgo.expect for cmptest
expectFile := filepath.Join(tmpDir, "llgo.expect")
err = os.WriteFile(expectFile, []byte(`#stdout
Hello, LLGO!
#stderr
#exit 0
`), 0644)
if err != nil {
t.Fatalf("Failed to create llgo.expect: %v", err)
} }
// Create a go.mod file // Create a go.mod file
goMod := filepath.Join(tmpDir, "go.mod") err = os.WriteFile(filepath.Join(tmpDir, "go.mod"), []byte(`module testproject
err = os.WriteFile(goMod, []byte(`module testproject
go 1.20 go 1.20
`), 0644) `), 0644)
if err != nil { if err != nil {
os.RemoveAll(tmpDir)
t.Fatalf("Failed to write go.mod: %v", err) t.Fatalf("Failed to write go.mod: %v", err)
} }
@@ -54,168 +78,195 @@ go 1.20
} }
func TestProjectCommands(t *testing.T) { func TestProjectCommands(t *testing.T) {
// Setup test project
tmpDir := setupTestProject(t)
defer os.RemoveAll(tmpDir)
// Save original working directory and environment
origWd, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get current directory: %v", err)
}
defer os.Chdir(origWd)
// Change to test project directory
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Failed to change to test directory: %v", err)
}
tests := []struct { tests := []struct {
name string name string
cmd *base.Command
args []string args []string
wantErr bool wantErr bool
setup func(dir string) error
}{ }{
{ {
name: "build command", name: "build command",
cmd: build.Cmd, args: []string{"llgo", "build", "."},
args: []string{"."},
wantErr: false, wantErr: false,
}, },
{ {
name: "install command", name: "install command",
cmd: install.Cmd, args: []string{"llgo", "install", "."},
args: []string{"."},
wantErr: false, wantErr: false,
}, },
{ {
name: "run command", name: "run command",
cmd: run.Cmd, args: []string{"llgo", "run", "."},
args: []string{"main.go"},
wantErr: false, wantErr: false,
}, },
{
name: "run command with file",
args: []string{"llgo", "run", "main.go"},
wantErr: false,
},
{
name: "run command verbose",
args: []string{"llgo", "run", "-v", "."},
wantErr: false,
},
{
name: "clean command",
args: []string{"llgo", "clean"},
wantErr: false,
},
{
name: "cmptest command",
args: []string{"llgo", "cmptest", "."},
wantErr: false,
},
{
name: "cmptest command with gen",
args: []string{"llgo", "cmptest", "-gen", "."},
wantErr: false,
setup: func(dir string) error {
return os.Remove(filepath.Join(dir, "llgo.expect"))
},
},
{
name: "cmptest command with args",
args: []string{"llgo", "cmptest", ".", "World"},
wantErr: true,
setup: func(dir string) error {
return os.WriteFile(filepath.Join(dir, "llgo.expect"), []byte(`#stdout
Hello, World!
#stderr
#exit 0
`), 0644)
},
},
{
name: "cmptest command with different stderr",
args: []string{"llgo", "cmptest", ".", "stderr"},
wantErr: true,
},
{
name: "cmptest command with different exit code",
args: []string{"llgo", "cmptest", ".", "exit"},
wantErr: true,
setup: func(dir string) error {
// Create llgo.expect with different exit code
return os.WriteFile(filepath.Join(dir, "llgo.expect"), []byte(`#stdout
Hello, LLGO!
#stderr
#exit 1
`), 0644)
},
},
{
name: "cmptest command without llgo.expect to compare with go run",
args: []string{"llgo", "cmptest", "."},
wantErr: false,
setup: func(dir string) error {
return os.Remove(filepath.Join(dir, "llgo.expect"))
},
},
{
name: "cmptest command with different go run output",
args: []string{"llgo", "cmptest", "."},
wantErr: true,
setup: func(dir string) error {
// Remove llgo.expect file
if err := os.Remove(filepath.Join(dir, "llgo.expect")); err != nil && !os.IsNotExist(err) {
return err
}
// Create main_llgo.go for llgo
if err := os.WriteFile(filepath.Join(dir, "main_llgo.go"), []byte(`//go:build llgo
// +build llgo
package main
import "fmt"
func main() {
fmt.Println("Hello, LLGO!")
}
`), 0644); err != nil {
return err
}
// Create main_go.go for go
return os.WriteFile(filepath.Join(dir, "main.go"), []byte(`//go:build !llgo
// +build !llgo
package main
import "fmt"
func main() {
fmt.Println("Hello, Go!")
}
`), 0644)
},
},
} }
// Save original args and flags
oldArgs := os.Args
oldFlagCommandLine := flag.CommandLine
oldStdout := os.Stdout
oldStderr := os.Stderr
defer func() {
os.Args = oldArgs
flag.CommandLine = oldFlagCommandLine
os.Stdout = oldStdout
os.Stderr = oldStderr
}()
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
// Reset flag.CommandLine for each test // Create a new test directory for each test case
flag.CommandLine = flag.NewFlagSet("llgo", flag.ContinueOnError) tmpDir := setupTestProject(t)
defer os.RemoveAll(tmpDir)
// Setup command arguments // Change to test project directory
args := append([]string{"llgo", tt.cmd.Name()}, tt.args...) if err := os.Chdir(tmpDir); err != nil {
os.Args = args t.Fatalf("Failed to change directory: %v", err)
// Capture output
outR, outW, err := os.Pipe()
if err != nil {
t.Fatalf("Failed to create stdout pipe: %v", err)
}
errR, errW, err := os.Pipe()
if err != nil {
t.Fatalf("Failed to create stderr pipe: %v", err)
} }
os.Stdout = outW if tt.setup != nil {
os.Stderr = errW if err := tt.setup(tmpDir); err != nil {
t.Fatalf("Failed to setup test: %v", err)
// Run command
done := make(chan struct{})
var outBuf, errBuf bytes.Buffer
go func() {
_, _ = io.Copy(&outBuf, outR)
done <- struct{}{}
}()
go func() {
_, _ = io.Copy(&errBuf, errR)
done <- struct{}{}
}()
panicked := false
func() {
defer func() {
if r := recover(); r != nil {
panicked = true
t.Logf("%s: Command panicked: %v", tt.name, r)
}
outW.Close()
errW.Close()
}()
flag.Parse()
base.CmdName = tt.cmd.Name()
if !tt.cmd.Runnable() {
t.Fatalf("%s: Command is not runnable", tt.name)
} }
}
// Print current working directory and files for debugging mockable.EnableMock()
if cwd, err := os.Getwd(); err == nil { defer func() {
t.Logf("%s: Current working directory: %s", tt.name, cwd) if r := recover(); r != nil {
if files, err := os.ReadDir("."); err == nil { if r != "exit" {
t.Log("Files in current directory:") if !tt.wantErr {
for _, f := range files { t.Errorf("unexpected panic: %v", r)
t.Logf(" %s", f.Name()) }
} else {
exitCode := mockable.ExitCode()
if (exitCode != 0) != tt.wantErr {
t.Errorf("got exit code %d, wantErr %v", exitCode, tt.wantErr)
} }
} }
} }
// Run the command
tt.cmd.Run(tt.cmd, tt.args)
}() }()
<-done os.Args = tt.args
<-done main()
// Check output // For build/install commands, check if binary was created
outStr := outBuf.String() if strings.HasPrefix(tt.name, "build") || strings.HasPrefix(tt.name, "install") {
errStr := errBuf.String() binName := "testproject"
var binPath string
if outStr == "" && errStr == "" && !panicked { if strings.HasPrefix(tt.name, "install") {
t.Logf("%s: Command completed with no output", tt.name) // For install command, binary should be in GOBIN or GOPATH/bin
} else { gobin := os.Getenv("GOBIN")
if outStr != "" { if gobin == "" {
t.Logf("%s stdout:\n%s", tt.name, outStr) gopath := os.Getenv("GOPATH")
} if gopath == "" {
if errStr != "" { gopath = filepath.Join(os.Getenv("HOME"), "go")
t.Logf("%s stderr:\n%s", tt.name, errStr) }
} gobin = filepath.Join(gopath, "bin")
}
// Check if the command succeeded
if !tt.wantErr {
// For build/install commands, check if binary was created
if tt.cmd == build.Cmd || tt.cmd == install.Cmd {
binName := "testproject"
if _, err := os.Stat(binName); os.IsNotExist(err) {
t.Logf("%s: Binary %s was not created", tt.name, binName)
} }
binPath = filepath.Join(gobin, binName)
} else {
// For build command, binary should be in current directory
binPath = filepath.Join(tmpDir, binName)
} }
if _, err := os.Stat(binPath); os.IsNotExist(err) {
// For run command, check if output contains expected string t.Errorf("Binary %s was not created at %s", binName, binPath)
if tt.cmd == run.Cmd {
if !strings.Contains(outStr, "Hello, LLGO!") {
t.Logf("%s: Expected output to contain 'Hello, LLGO!', got:\n%s", tt.name, outStr)
}
}
// Check for common error indicators, but don't fail the test
if strings.Contains(errStr, "error:") || strings.Contains(errStr, "failed") {
// Ignore LLVM reexported library warning
if !strings.Contains(errStr, "ld: warning: reexported library") {
t.Logf("%s: Command produced error output:\n%s", tt.name, errStr)
}
} }
} }
}) })
@@ -223,119 +274,95 @@ func TestProjectCommands(t *testing.T) {
} }
func TestCommandHandling(t *testing.T) { func TestCommandHandling(t *testing.T) {
// Save original args and flags
oldArgs := os.Args
oldFlagCommandLine := flag.CommandLine
defer func() {
os.Args = oldArgs
flag.CommandLine = oldFlagCommandLine
}()
tests := []struct {
name string
args []string
wantErr bool
commands []*base.Command
}{
{
name: "version command",
args: []string{"llgo", "version"},
wantErr: false,
commands: []*base.Command{
version.Cmd,
},
},
{
name: "build command",
args: []string{"llgo", "build"},
wantErr: false,
commands: []*base.Command{
build.Cmd,
},
},
{
name: "unknown command",
args: []string{"llgo", "unknowncommand"},
wantErr: true,
},
{
name: "help command",
args: []string{"llgo", "help"},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Reset flag.CommandLine for each test
flag.CommandLine = flag.NewFlagSet(tt.args[0], flag.ExitOnError)
os.Args = tt.args
if tt.commands != nil {
base.Llgo.Commands = tt.commands
}
// Capture panic that would normally exit
defer func() {
if r := recover(); r != nil {
if !tt.wantErr {
t.Errorf("unexpected panic: %v", r)
}
}
}()
flag.Parse()
if len(tt.args) > 1 {
base.CmdName = tt.args[1]
}
})
}
}
func TestHelpCommand(t *testing.T) {
oldArgs := os.Args
oldFlagCommandLine := flag.CommandLine
defer func() {
os.Args = oldArgs
flag.CommandLine = oldFlagCommandLine
}()
tests := []struct { tests := []struct {
name string name string
args []string args []string
wantErr bool wantErr bool
}{ }{
{ {
name: "help without subcommand", name: "version command",
args: []string{"llgo", "version"},
wantErr: false,
},
{
name: "help command",
args: []string{"llgo", "help"}, args: []string{"llgo", "help"},
wantErr: false, wantErr: false,
}, },
{ {
name: "help with subcommand", name: "invalid command",
args: []string{"llgo", "help", "build"}, args: []string{"llgo", "invalid"},
wantErr: false, wantErr: true,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
flag.CommandLine = flag.NewFlagSet(tt.args[0], flag.ExitOnError)
os.Args = tt.args
var buf bytes.Buffer
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
if !tt.wantErr { if r != "exit" {
t.Errorf("unexpected panic: %v", r) t.Errorf("unexpected panic: %v", r)
} }
exitCode := mockable.ExitCode()
if (exitCode != 0) != tt.wantErr {
t.Errorf("got exit code %d, wantErr %v", exitCode, tt.wantErr)
}
} }
}() }()
flag.Parse() os.Args = tt.args
args := flag.Args() main()
if len(args) > 0 && args[0] == "help" { })
help.Help(&buf, args[1:]) }
} }
func TestHelpCommand(t *testing.T) {
tests := []struct {
name string
args []string
}{
{
name: "help build",
args: []string{"llgo", "help", "build"},
},
{
name: "help install",
args: []string{"llgo", "help", "install"},
},
{
name: "help run",
args: []string{"llgo", "help", "run"},
},
{
name: "help version",
args: []string{"llgo", "help", "version"},
},
{
name: "help clean",
args: []string{"llgo", "help", "clean"},
},
{
name: "help cmptest",
args: []string{"llgo", "help", "cmptest"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
if r != "exit" {
t.Errorf("unexpected panic: %v", r)
}
exitCode := mockable.ExitCode()
if exitCode != 0 {
t.Errorf("got exit code %d, want 0", exitCode)
}
}
}()
os.Args = tt.args
main()
}) })
} }
} }

View File

@@ -38,6 +38,7 @@ import (
"github.com/goplus/llgo/compiler/cl" "github.com/goplus/llgo/compiler/cl"
"github.com/goplus/llgo/compiler/internal/env" "github.com/goplus/llgo/compiler/internal/env"
"github.com/goplus/llgo/compiler/internal/mockable"
"github.com/goplus/llgo/compiler/internal/packages" "github.com/goplus/llgo/compiler/internal/packages"
"github.com/goplus/llgo/compiler/internal/typepatch" "github.com/goplus/llgo/compiler/internal/typepatch"
"github.com/goplus/llgo/compiler/ssa/abi" "github.com/goplus/llgo/compiler/ssa/abi"
@@ -221,15 +222,11 @@ func Do(args []string, conf *Config) ([]Package, error) {
} }
if mode != ModeBuild { if mode != ModeBuild {
nErr := 0
for _, pkg := range initial { for _, pkg := range initial {
if pkg.Name == "main" { if pkg.Name == "main" {
nErr += linkMainPkg(ctx, pkg, pkgs, linkArgs, conf, mode, verbose) linkMainPkg(ctx, pkg, pkgs, linkArgs, conf, mode, verbose)
} }
} }
if nErr > 0 {
os.Exit(nErr)
}
} }
return dpkg, nil return dpkg, nil
} }
@@ -290,7 +287,7 @@ func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs
fmt.Fprintln(os.Stderr, "cannot build SSA for package", errPkg) fmt.Fprintln(os.Stderr, "cannot build SSA for package", errPkg)
} }
if len(errPkgs) > 0 { if len(errPkgs) > 0 {
os.Exit(1) mockable.Exit(1)
} }
built := ctx.built built := ctx.built
for _, aPkg := range pkgs { for _, aPkg := range pkgs {
@@ -372,7 +369,7 @@ func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs
return return
} }
func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, linkArgs []string, conf *Config, mode Mode, verbose bool) (nErr int) { func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, linkArgs []string, conf *Config, mode Mode, verbose bool) {
pkgPath := pkg.PkgPath pkgPath := pkg.PkgPath
name := path.Base(pkgPath) name := path.Base(pkgPath)
app := conf.OutFile app := conf.OutFile
@@ -458,11 +455,6 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, linkArgs
if verbose || mode != ModeRun { if verbose || mode != ModeRun {
fmt.Fprintln(os.Stderr, "#", pkgPath) fmt.Fprintln(os.Stderr, "#", pkgPath)
} }
defer func() {
if e := recover(); e != nil {
nErr = 1
}
}()
// add rpath and find libs // add rpath and find libs
exargs := make([]string, 0, ctx.nLibdir<<1) exargs := make([]string, 0, ctx.nLibdir<<1)
@@ -506,12 +498,11 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, linkArgs
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Run() cmd.Run()
if s := cmd.ProcessState; s != nil { if s := cmd.ProcessState; s != nil {
os.Exit(s.ExitCode()) mockable.Exit(s.ExitCode())
} }
case ModeCmpTest: case ModeCmpTest:
cmpTest(filepath.Dir(pkg.GoFiles[0]), pkgPath, app, conf.GenExpect, conf.RunArgs) cmpTest(filepath.Dir(pkg.GoFiles[0]), pkgPath, app, conf.GenExpect, conf.RunArgs)
} }
return
} }
func buildPkg(ctx *context, aPkg *aPackage, verbose bool) (cgoLdflags []string, err error) { func buildPkg(ctx *context, aPkg *aPackage, verbose bool) (cgoLdflags []string, err error) {

View File

@@ -2,9 +2,11 @@ package env
import ( import (
"bytes" "bytes"
"fmt"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
) )
@@ -12,6 +14,7 @@ const (
LLGoCompilerPkg = "github.com/goplus/llgo" LLGoCompilerPkg = "github.com/goplus/llgo"
LLGoRuntimePkgName = "runtime" LLGoRuntimePkgName = "runtime"
LLGoRuntimePkg = LLGoCompilerPkg + "/" + LLGoRuntimePkgName LLGoRuntimePkg = LLGoCompilerPkg + "/" + LLGoRuntimePkgName
envFileName = "/compiler/internal/env/env.go"
) )
func GOROOT() string { func GOROOT() string {
@@ -38,8 +41,12 @@ func LLGoRuntimeDir() string {
} }
func LLGoROOT() string { func LLGoROOT() string {
if root, ok := isLLGoRoot(os.Getenv("LLGO_ROOT")); ok { llgoRootEnv := os.Getenv("LLGO_ROOT")
return root if llgoRootEnv != "" {
if root, ok := isLLGoRoot(llgoRootEnv); ok {
return root
}
fmt.Fprintf(os.Stderr, "WARNING: LLGO_ROOT is not a valid LLGO root: %s\n", llgoRootEnv)
} }
// Get executable path // Get executable path
exe, err := os.Executable() exe, err := os.Executable()
@@ -53,13 +60,22 @@ func LLGoROOT() string {
} }
// Check if parent directory is bin // Check if parent directory is bin
dir := filepath.Dir(exe) dir := filepath.Dir(exe)
if filepath.Base(dir) != "bin" { if filepath.Base(dir) == "bin" {
return "" // Get parent directory of bin
root := filepath.Dir(dir)
if root, ok := isLLGoRoot(root); ok {
return root
}
} }
// Get parent directory of bin if Devel() {
root := filepath.Dir(dir) root, err := getRuntimePkgDirByCaller()
if root, ok := isLLGoRoot(root); ok { if err != nil {
return root return ""
}
if root, ok := isLLGoRoot(root); ok {
fmt.Fprintln(os.Stderr, "WARNING: Using LLGO root for devel: "+root)
return root
}
} }
return "" return ""
} }
@@ -83,3 +99,22 @@ func isLLGoRoot(root string) (string, bool) {
} }
return root, true return root, true
} }
func getRuntimePkgDirByCaller() (string, error) {
_, file, _, ok := runtime.Caller(0)
if !ok {
return "", fmt.Errorf("cannot get caller")
}
if !strings.HasSuffix(file, envFileName) {
return "", fmt.Errorf("wrong caller")
}
// check file exists
if _, err := os.Stat(file); os.IsNotExist(err) {
return "", fmt.Errorf("file %s not exists", file)
}
modPath := strings.TrimSuffix(file, envFileName)
if st, err := os.Stat(modPath); os.IsNotExist(err) || !st.IsDir() {
return "", fmt.Errorf("not llgo compiler root: %s", modPath)
}
return modPath, nil
}

View File

@@ -58,8 +58,28 @@ func TestLLGoRuntimeDir(t *testing.T) {
defer os.Setenv("LLGO_ROOT", origLLGoRoot) defer os.Setenv("LLGO_ROOT", origLLGoRoot)
os.Setenv("LLGO_ROOT", "/nonexistent/path") os.Setenv("LLGO_ROOT", "/nonexistent/path")
if got := LLGoRuntimeDir(); got != "" { wd, err := os.Getwd()
t.Errorf("LLGoRuntimeDir() = %v, want empty string", got) if err != nil {
t.Fatal(err)
}
runtimeDir := filepath.Join(wd, "../../../runtime")
if got := LLGoRuntimeDir(); got != runtimeDir {
t.Errorf("LLGoRuntimeDir() = %v, want %v", got, runtimeDir)
}
})
t.Run("devel runtime dir", func(t *testing.T) {
origLLGoRoot := os.Getenv("LLGO_ROOT")
defer os.Setenv("LLGO_ROOT", origLLGoRoot)
os.Setenv("LLGO_ROOT", "")
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
runtimeDir := filepath.Join(wd, "../../../runtime")
if got := LLGoRuntimeDir(); got != runtimeDir {
t.Errorf("LLGoRuntimeDir() = %v, want %v", got, runtimeDir)
} }
}) })
} }
@@ -90,8 +110,13 @@ func TestLLGoROOT(t *testing.T) {
defer os.Setenv("LLGO_ROOT", origLLGoRoot) defer os.Setenv("LLGO_ROOT", origLLGoRoot)
os.Setenv("LLGO_ROOT", "/nonexistent/path") os.Setenv("LLGO_ROOT", "/nonexistent/path")
if got := LLGoROOT(); got != "" { wd, err := os.Getwd()
t.Errorf("LLGoROOT() = %v, want empty string", got) if err != nil {
t.Fatal(err)
}
rootDir := filepath.Join(wd, "../../..")
if got := LLGoROOT(); got != rootDir {
t.Errorf("LLGoROOT() = %v, want %v", got, rootDir)
} }
}) })

View File

@@ -16,20 +16,32 @@
package env package env
import "runtime/debug" import (
"runtime/debug"
)
const (
devel = "(devel)"
)
// buildVersion is the LLGo tree's version string at build time. It should be // buildVersion is the LLGo tree's version string at build time. It should be
// set by the linker. // set by the linker.
var buildVersion string var buildVersion string
// Version returns the version of the running LLGo binary. // Version returns the version of the running LLGo binary.
//
//export LLGoVersion
func Version() string { func Version() string {
if buildVersion != "" { if buildVersion != "" {
return buildVersion return buildVersion
} }
info, ok := debug.ReadBuildInfo() info, ok := debug.ReadBuildInfo()
if ok { if ok && info.Main.Version != "" {
return info.Main.Version return info.Main.Version
} }
return "(devel)" return devel
}
func Devel() bool {
return Version() == devel
} }

View File

@@ -0,0 +1,29 @@
package mockable
import (
"os"
)
var (
exitFunc = os.Exit
exitCode int
)
// EnableMock enables mocking of os.Exit
func EnableMock() {
exitCode = 0
exitFunc = func(code int) {
exitCode = code
panic("exit")
}
}
// Exit calls the current exit function
func Exit(code int) {
exitFunc(code)
}
// ExitCode returns the last exit code from a mocked Exit call
func ExitCode() int {
return exitCode
}

View File

@@ -18,6 +18,9 @@ package ssa_test
import ( import (
"go/types" "go/types"
"io"
"log"
"os"
"testing" "testing"
"github.com/goplus/llgo/compiler/cl/cltest" "github.com/goplus/llgo/compiler/cl/cltest"
@@ -26,6 +29,10 @@ import (
) )
func init() { func init() {
devNull, _ := os.OpenFile(os.DevNull, os.O_WRONLY, 0)
os.Stderr = devNull
os.Stdout = devNull
log.SetOutput(io.Discard)
ssa.SetDebug(ssa.DbgFlagAll) ssa.SetDebug(ssa.DbgFlagAll)
} }