supports binary-format, only esp* supported for now

This commit is contained in:
Li Jie
2025-08-22 20:42:43 +08:00
parent 1f193c8533
commit ecaf7c8ac6
10 changed files with 568 additions and 15 deletions

View File

@@ -42,6 +42,7 @@ import (
"github.com/goplus/llgo/internal/clang"
"github.com/goplus/llgo/internal/crosscompile"
"github.com/goplus/llgo/internal/env"
"github.com/goplus/llgo/internal/firmware"
"github.com/goplus/llgo/internal/mockable"
"github.com/goplus/llgo/internal/packages"
"github.com/goplus/llgo/internal/typepatch"
@@ -629,23 +630,76 @@ func compileExtraFiles(ctx *context, verbose bool) ([]string, error) {
return objFiles, nil
}
func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global llssa.Package, conf *Config, mode Mode, verbose bool) {
pkgPath := pkg.PkgPath
name := path.Base(pkgPath)
app := conf.OutFile
if app == "" {
if mode == ModeBuild && len(ctx.initial) > 1 {
// generateOutputFilenames generates the final output filename (app) and intermediate filename (orgApp)
// based on configuration and build context.
func generateOutputFilenames(outFile, binPath, appExt, binExt, pkgName string, mode Mode, isMultiplePkgs bool) (app, orgApp string, err error) {
if outFile == "" {
if mode == ModeBuild && isMultiplePkgs {
// For multiple packages in ModeBuild mode, use temporary file
tmpFile, err := os.CreateTemp("", name+"*"+conf.AppExt)
check(err)
name := pkgName
if binExt != "" {
name += "*" + binExt
} else {
name += "*" + appExt
}
tmpFile, err := os.CreateTemp("", name)
if err != nil {
return "", "", err
}
app = tmpFile.Name()
tmpFile.Close()
} else {
app = filepath.Join(conf.BinPath, name+conf.AppExt)
app = filepath.Join(binPath, pkgName+appExt)
}
orgApp = app
} else {
// outFile is not empty, use it as base part
base := outFile
if binExt != "" {
// If binExt has value, use temporary file as orgApp for firmware conversion
tmpFile, err := os.CreateTemp("", "llgo-*"+appExt)
if err != nil {
return "", "", err
}
orgApp = tmpFile.Name()
tmpFile.Close()
// Check if base already ends with binExt, if so, don't add it again
if strings.HasSuffix(base, binExt) {
app = base
} else {
app = base + binExt
}
} else {
// No binExt, use base + AppExt directly
if filepath.Ext(base) == "" {
app = base + appExt
} else {
app = base
}
orgApp = app
}
} else if filepath.Ext(app) == "" {
app += conf.AppExt
}
return app, orgApp, nil
}
func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global llssa.Package, conf *Config, mode Mode, verbose bool) {
pkgPath := pkg.PkgPath
name := path.Base(pkgPath)
binFmt := ctx.crossCompile.BinaryFormat
binExt := firmware.BinaryExt(binFmt)
// app: converted firmware output file or executable file
// orgApp: before converted output file
app, orgApp, err := generateOutputFilenames(
conf.OutFile,
conf.BinPath,
conf.AppExt,
binExt,
name,
mode,
len(ctx.initial) > 1,
)
check(err)
needRuntime := false
needPyInit := false
@@ -701,9 +755,15 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
linkArgs = append(linkArgs, exargs...)
}
err = linkObjFiles(ctx, app, objFiles, linkArgs, verbose)
err = linkObjFiles(ctx, orgApp, objFiles, linkArgs, verbose)
check(err)
if orgApp != app {
fmt.Printf("cross compile: %#v\n", ctx.crossCompile)
err = firmware.MakeFirmwareImage(orgApp, app, ctx.crossCompile.BinaryFormat)
check(err)
}
switch mode {
case ModeTest:
cmd := exec.Command(app, conf.RunArgs...)

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"os"
"strings"
"testing"
"github.com/goplus/llgo/internal/mockable"
@@ -93,3 +94,173 @@ func TestExtest(t *testing.T) {
func TestCmpTest(t *testing.T) {
mockRun([]string{"../../cl/_testgo/runtest"}, &Config{Mode: ModeCmpTest})
}
func TestGenerateOutputFilenames(t *testing.T) {
tests := []struct {
name string
outFile string
binPath string
appExt string
binExt string
pkgName string
mode Mode
isMultiplePkgs bool
wantAppSuffix string
wantOrgAppDiff bool // true if orgApp should be different from app
wantErr bool
}{
{
name: "empty outFile, single package",
outFile: "",
binPath: "/usr/local/bin",
appExt: "",
binExt: "",
pkgName: "hello",
mode: ModeBuild,
isMultiplePkgs: false,
wantAppSuffix: "/usr/local/bin/hello",
wantOrgAppDiff: false,
},
{
name: "empty outFile with appExt",
outFile: "",
binPath: "/usr/local/bin",
appExt: ".exe",
binExt: "",
pkgName: "hello",
mode: ModeBuild,
isMultiplePkgs: false,
wantAppSuffix: "/usr/local/bin/hello.exe",
wantOrgAppDiff: false,
},
{
name: "outFile without binExt",
outFile: "myapp",
binPath: "/usr/local/bin",
appExt: ".exe",
binExt: "",
pkgName: "hello",
mode: ModeBuild,
isMultiplePkgs: false,
wantAppSuffix: "myapp.exe",
wantOrgAppDiff: false,
},
{
name: "outFile with existing extension, no binExt",
outFile: "myapp.exe",
binPath: "/usr/local/bin",
appExt: ".exe",
binExt: "",
pkgName: "hello",
mode: ModeBuild,
isMultiplePkgs: false,
wantAppSuffix: "myapp.exe",
wantOrgAppDiff: false,
},
{
name: "outFile with binExt, different from existing extension",
outFile: "myapp",
binPath: "/usr/local/bin",
appExt: ".exe",
binExt: ".bin",
pkgName: "hello",
mode: ModeBuild,
isMultiplePkgs: false,
wantAppSuffix: "myapp.bin",
wantOrgAppDiff: true,
},
{
name: "outFile already ends with binExt",
outFile: "t.bin",
binPath: "/usr/local/bin",
appExt: ".exe",
binExt: ".bin",
pkgName: "hello",
mode: ModeBuild,
isMultiplePkgs: false,
wantAppSuffix: "t.bin",
wantOrgAppDiff: true,
},
{
name: "outFile with full path already ends with binExt",
outFile: "/path/to/t.bin",
binPath: "/usr/local/bin",
appExt: ".exe",
binExt: ".bin",
pkgName: "hello",
mode: ModeBuild,
isMultiplePkgs: false,
wantAppSuffix: "/path/to/t.bin",
wantOrgAppDiff: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
app, orgApp, err := generateOutputFilenames(
tt.outFile,
tt.binPath,
tt.appExt,
tt.binExt,
tt.pkgName,
tt.mode,
tt.isMultiplePkgs,
)
if (err != nil) != tt.wantErr {
t.Errorf("generateOutputFilenames() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantAppSuffix != "" {
if app != tt.wantAppSuffix {
t.Errorf("generateOutputFilenames() app = %v, want %v", app, tt.wantAppSuffix)
}
}
if tt.wantOrgAppDiff {
if app == orgApp {
t.Errorf("generateOutputFilenames() orgApp should be different from app, but both are %v", app)
}
// Clean up temp file
if orgApp != "" && strings.Contains(orgApp, "llgo-") {
os.Remove(orgApp)
}
} else {
if app != orgApp {
t.Errorf("generateOutputFilenames() orgApp = %v, want %v (same as app)", orgApp, app)
}
}
})
}
}
func TestGenerateOutputFilenames_EdgeCases(t *testing.T) {
// Test case where outFile has same extension as binExt
app, orgApp, err := generateOutputFilenames(
"firmware.bin",
"/usr/local/bin",
".exe",
".bin",
"esp32app",
ModeBuild,
false,
)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if app != "firmware.bin" {
t.Errorf("Expected app to be 'firmware.bin', got '%s'", app)
}
if app == orgApp {
t.Errorf("Expected orgApp to be different from app when binExt is present, but both are '%s'", app)
}
// Clean up temp file
if orgApp != "" && strings.Contains(orgApp, "llgo-") {
os.Remove(orgApp)
}
}