diff --git a/cmd/internal/build/build.go b/cmd/internal/build/build.go index 627dcf86..e2afe5c2 100644 --- a/cmd/internal/build/build.go +++ b/cmd/internal/build/build.go @@ -14,7 +14,7 @@ * limitations under the License. */ -// Package build implements the “llgo build” command. +// Package build implements the "llgo build" command. package build import ( @@ -34,6 +34,7 @@ func init() { func runCmd(cmd *base.Command, args []string) { build.Do(args, &build.Config{ - Mode: build.ModeBuild, + Mode: build.ModeBuild, + AppExt: build.DefaultAppExt(), }) } diff --git a/cmd/internal/install/install.go b/cmd/internal/install/install.go index 9f9e6dc2..c486abb3 100644 --- a/cmd/internal/install/install.go +++ b/cmd/internal/install/install.go @@ -14,7 +14,7 @@ * limitations under the License. */ -// Package install implements the “llgo install command. +// Package install implements the "llgo install" command. package install import ( diff --git a/cmd/internal/run/run.go b/cmd/internal/run/run.go new file mode 100644 index 00000000..2a9a2237 --- /dev/null +++ b/cmd/internal/run/run.go @@ -0,0 +1,85 @@ +/* + * 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 run implements the "llgo run" command. +package run + +import ( + "errors" + "path/filepath" + "strings" + + "github.com/goplus/llgo/cmd/internal/base" + "github.com/goplus/llgo/internal/build" +) + +var ( + errNoProj = errors.New("llgo: no go files listed") +) + +// llgo run +var Cmd = &base.Command{ + UsageLine: "llgo run [build flags] package [arguments...]", + Short: "Compile and run Go program", +} + +func init() { + Cmd.Run = runCmd +} + +func runCmd(cmd *base.Command, args []string) { + args, runArgs, err := parseRunArgs(args) + check(err) + conf := build.NewDefaultConf(build.ModeRun) + conf.RunArgs = runArgs + build.Do(args, conf) +} + +func parseRunArgs(args []string) ([]string, []string, error) { + n := parseArgs(args) + if n < 0 { + return nil, nil, errNoProj + } + + arg := args[n] + if isGoFile(arg) { + n++ + for n < len(args) && isGoFile(args[n]) { + n++ + } + return args[:n], args[n:], nil + } + return args[:n+1], args[n+1:], nil +} + +func parseArgs(args []string) int { + for i, arg := range args { + if !strings.HasPrefix(arg, "-") { + return i + } + } + return -1 +} + +func isGoFile(fname string) bool { + return filepath.Ext(fname) == ".go" +} + +func check(err error) { + if err != nil { + panic(err) + } +} diff --git a/cmd/llgo/llgo.go b/cmd/llgo/llgo.go index 9f30a120..24e4b67e 100644 --- a/cmd/llgo/llgo.go +++ b/cmd/llgo/llgo.go @@ -28,6 +28,7 @@ import ( "github.com/goplus/llgo/cmd/internal/build" "github.com/goplus/llgo/cmd/internal/help" "github.com/goplus/llgo/cmd/internal/install" + "github.com/goplus/llgo/cmd/internal/run" ) func mainUsage() { @@ -40,6 +41,7 @@ func init() { base.Llgo.Commands = []*base.Command{ build.Cmd, install.Cmd, + run.Cmd, } } diff --git a/internal/build/build.go b/internal/build/build.go index 49e595e1..3eb310aa 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -21,6 +21,7 @@ import ( "go/token" "log" "os" + "os/exec" "path" "path/filepath" "runtime" @@ -40,12 +41,18 @@ type Mode int const ( ModeBuild Mode = iota ModeInstall + ModeRun ) +func needLLFile(mode Mode) bool { + return mode != ModeBuild +} + type Config struct { - BinPath string - AppSuffix string // ".exe" on Windows, empty on Unix - Mode Mode + BinPath string + AppExt string // ".exe" on Windows, empty on Unix + RunArgs []string + Mode Mode } func NewDefaultConf(mode Mode) *Config { @@ -56,13 +63,18 @@ func NewDefaultConf(mode Mode) *Config { conf := &Config{ BinPath: bin, Mode: mode, - } - if runtime.GOOS == "windows" { - conf.AppSuffix = ".exe" + AppExt: DefaultAppExt(), } return conf } +func DefaultAppExt() string { + if runtime.GOOS == "windows" { + return ".exe" + } + return "" +} + // ----------------------------------------------------------------------------- const ( @@ -104,20 +116,22 @@ func Do(args []string, conf *Config) { buildPkg(prog, pkg, mode) } - if mode == ModeInstall { + if mode != ModeBuild || len(initial) == 1 { for _, pkg := range initial { if pkg.Name == "main" { - linkMainPkg(pkg, conf) + linkMainPkg(pkg, conf, mode) } } } } -func linkMainPkg(pkg *packages.Package, conf *Config) { - name := path.Base(pkg.PkgPath) +func linkMainPkg(pkg *packages.Package, conf *Config, mode Mode) { + pkgPath := pkg.PkgPath + name := path.Base(pkgPath) + app := filepath.Join(conf.BinPath, name+conf.AppExt) args := make([]string, 2, len(pkg.Imports)+3) args[0] = "-o" - args[1] = filepath.Join(conf.BinPath, name+conf.AppSuffix) + args[1] = app packages.Visit([]*packages.Package{pkg}, nil, func(p *packages.Package) { if p.PkgPath != "unsafe" { // TODO(xsw): remove this special case args = append(args, p.ExportFile+".ll") @@ -126,21 +140,31 @@ func linkMainPkg(pkg *packages.Package, conf *Config) { // TODO(xsw): show work // fmt.Fprintln(os.Stderr, "clang", args) - fmt.Fprintln(os.Stderr, args[1]) + fmt.Fprintln(os.Stderr, "#", pkgPath) err := clang.New("").Exec(args...) check(err) + + if mode == ModeRun { + cmd := exec.Command(app, conf.RunArgs...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() + } } func buildPkg(prog llssa.Program, aPkg aPackage, mode Mode) { pkg := aPkg.Package pkgPath := pkg.PkgPath - fmt.Fprintln(os.Stderr, pkgPath) + if mode != ModeRun { + fmt.Fprintln(os.Stderr, pkgPath) + } if pkgPath == "unsafe" { // TODO(xsw): remove this special case return } ret, err := cl.NewPackage(prog, aPkg.SSA, pkg.Syntax) check(err) - if mode == ModeInstall { + if needLLFile(mode) { file := pkg.ExportFile + ".ll" os.WriteFile(file, []byte(ret.String()), 0644) }