Files
llgo/internal/build/build.go

446 lines
10 KiB
Go
Raw Normal View History

2024-04-24 07:55:51 +08:00
/*
* 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 (
2024-05-07 21:06:47 +08:00
"archive/zip"
2024-04-24 11:13:17 +08:00
"fmt"
"go/token"
2024-04-27 17:39:25 +08:00
"go/types"
2024-05-07 21:06:47 +08:00
"io"
2024-04-24 07:55:51 +08:00
"os"
2024-04-25 01:41:44 +08:00
"os/exec"
2024-04-25 00:53:42 +08:00
"path"
"path/filepath"
"runtime"
2024-04-24 07:55:51 +08:00
"strings"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/ssa"
"github.com/goplus/llgo/cl"
2024-04-24 11:49:43 +08:00
"github.com/goplus/llgo/x/clang"
2024-04-25 00:53:42 +08:00
llssa "github.com/goplus/llgo/ssa"
2024-04-24 07:55:51 +08:00
)
type Mode int
const (
ModeBuild Mode = iota
ModeInstall
2024-04-25 01:41:44 +08:00
ModeRun
2024-04-24 07:55:51 +08:00
)
2024-04-25 01:41:44 +08:00
func needLLFile(mode Mode) bool {
return mode != ModeBuild
}
2024-04-25 00:53:42 +08:00
type Config struct {
2024-04-25 01:41:44 +08:00
BinPath string
AppExt string // ".exe" on Windows, empty on Unix
OutFile string // only valid for ModeBuild when len(pkgs) == 1
RunArgs []string // only valid for ModeRun
2024-04-25 01:41:44 +08:00
Mode Mode
2024-04-25 00:53:42 +08:00
}
func NewDefaultConf(mode Mode) *Config {
bin := os.Getenv("GOBIN")
if bin == "" {
bin = filepath.Join(runtime.GOROOT(), "bin")
}
conf := &Config{
BinPath: bin,
Mode: mode,
2024-04-25 01:41:44 +08:00
AppExt: DefaultAppExt(),
2024-04-25 00:53:42 +08:00
}
2024-04-25 01:41:44 +08:00
return conf
}
func DefaultAppExt() string {
2024-04-25 00:53:42 +08:00
if runtime.GOOS == "windows" {
2024-04-25 01:41:44 +08:00
return ".exe"
2024-04-25 00:53:42 +08:00
}
2024-04-25 01:41:44 +08:00
return ""
2024-04-25 00:53:42 +08:00
}
2024-04-24 07:55:51 +08:00
// -----------------------------------------------------------------------------
const (
loadFiles = packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles
loadImports = loadFiles | packages.NeedImports
loadTypes = loadImports | packages.NeedTypes | packages.NeedTypesSizes
loadSyntax = loadTypes | packages.NeedSyntax | packages.NeedTypesInfo
)
2024-04-25 00:53:42 +08:00
func Do(args []string, conf *Config) {
flags, patterns, verbose := ParseArgs(args, buildFlags)
2024-04-24 07:55:51 +08:00
cfg := &packages.Config{
Mode: loadSyntax | packages.NeedDeps | packages.NeedModule | packages.NeedExportFile,
2024-04-24 07:55:51 +08:00
BuildFlags: flags,
}
if patterns == nil {
patterns = []string{"."}
}
initial, err := packages.Load(cfg, patterns...)
check(err)
2024-04-29 01:34:21 +08:00
mode := conf.Mode
if len(initial) == 1 && len(initial[0].CompiledGoFiles) > 0 {
if mode == ModeBuild {
mode = ModeInstall
}
} else if mode == ModeRun {
if len(initial) > 1 {
fmt.Fprintln(os.Stderr, "cannot run multiple packages")
} else {
fmt.Fprintln(os.Stderr, "no Go files in matched packages")
}
return
}
2024-04-24 07:55:51 +08:00
llssa.Initialize(llssa.InitAll)
2024-04-24 14:27:14 +08:00
if verbose {
llssa.SetDebug(llssa.DbgFlagAll)
cl.SetDebug(cl.DbgFlagAll)
}
2024-04-24 11:13:17 +08:00
2024-04-27 22:13:40 +08:00
var rt []*packages.Package
2024-04-24 07:55:51 +08:00
prog := llssa.NewProgram(nil)
2024-04-27 17:39:25 +08:00
prog.SetRuntime(func() *types.Package {
rt, err = packages.Load(cfg, llssa.PkgRuntime)
2024-04-27 17:39:25 +08:00
check(err)
return rt[0].Types
})
2024-04-27 22:13:40 +08:00
pkgs := buildAllPkgs(prog, initial, mode, verbose)
2024-04-27 22:13:40 +08:00
var runtimeFiles []string
2024-04-27 22:13:40 +08:00
if rt != nil {
runtimeFiles = allLinkFiles(rt)
2024-04-24 11:49:43 +08:00
}
if mode != ModeBuild {
2024-04-29 01:34:21 +08:00
nErr := 0
2024-04-25 00:53:42 +08:00
for _, pkg := range initial {
if pkg.Name == "main" {
nErr += linkMainPkg(pkg, pkgs, runtimeFiles, conf, mode, verbose)
2024-04-25 00:53:42 +08:00
}
}
2024-04-29 01:34:21 +08:00
if nErr > 0 {
2024-04-30 16:15:36 +08:00
os.Exit(nErr)
2024-04-29 01:34:21 +08:00
}
2024-04-24 11:13:17 +08:00
}
}
2024-04-29 00:49:17 +08:00
func setNeedRuntime(pkg *packages.Package) {
2024-04-29 01:34:21 +08:00
pkg.ID = "" // just use pkg.Module to mark it needs runtime
2024-04-29 00:49:17 +08:00
}
func isNeedRuntime(pkg *packages.Package) bool {
2024-04-29 01:34:21 +08:00
return pkg.ID == ""
2024-04-29 00:49:17 +08:00
}
func buildAllPkgs(prog llssa.Program, initial []*packages.Package, mode Mode, verbose bool) (pkgs []*aPackage) {
2024-04-27 22:13:40 +08:00
// Create SSA-form program representation.
ssaProg, pkgs, errPkgs := allPkgs(initial, ssa.SanityCheckFunctions)
ssaProg.Build()
for _, errPkg := range errPkgs {
2024-04-30 15:58:01 +08:00
for _, err := range errPkg.Errors {
fmt.Fprintln(os.Stderr, err)
}
2024-04-29 01:34:21 +08:00
fmt.Fprintln(os.Stderr, "cannot build SSA for package", errPkg)
2024-04-27 22:13:40 +08:00
}
for _, aPkg := range pkgs {
pkg := aPkg.Package
switch cl.PkgKindOf(pkg.Types) {
case cl.PkgDeclOnly:
// skip packages that only contain declarations
// and set no export file
pkg.ExportFile = ""
case cl.PkgLinkOnly:
// skip packages that don't need to be compiled but need to be linked
pkgPath := pkg.PkgPath
if isPkgInLLGo(pkgPath) {
pkg.ExportFile = strings.TrimSuffix(llgoPkgLinkFile(pkgPath), ".ll")
} else {
panic("todo")
}
default:
buildPkg(prog, aPkg, mode, verbose)
if prog.NeedRuntime() {
setNeedRuntime(pkg)
}
2024-04-29 00:49:17 +08:00
}
2024-04-27 22:13:40 +08:00
}
return
2024-04-27 22:13:40 +08:00
}
func linkMainPkg(pkg *packages.Package, pkgs []*aPackage, runtimeFiles []string, conf *Config, mode Mode, verbose bool) (nErr int) {
2024-04-25 01:41:44 +08:00
pkgPath := pkg.PkgPath
name := path.Base(pkgPath)
app := conf.OutFile
if app == "" {
app = filepath.Join(conf.BinPath, name+conf.AppExt)
}
const N = 3
args := make([]string, N, len(pkg.Imports)+len(runtimeFiles)+(N+1))
2024-04-25 00:53:42 +08:00
args[0] = "-o"
2024-04-25 01:41:44 +08:00
args[1] = app
args[2] = "-Wno-override-module"
2024-04-29 00:49:17 +08:00
needRuntime := false
2024-04-25 00:53:42 +08:00
packages.Visit([]*packages.Package{pkg}, nil, func(p *packages.Package) {
2024-05-01 13:30:13 +08:00
if p.ExportFile != "" && !isRuntimePkg(p.PkgPath) { // skip packages that only contain declarations
2024-04-25 00:53:42 +08:00
args = append(args, p.ExportFile+".ll")
2024-04-29 00:49:17 +08:00
if !needRuntime {
needRuntime = isNeedRuntime(p)
}
2024-04-25 00:53:42 +08:00
}
})
2024-04-29 00:49:17 +08:00
if needRuntime && runtimeFiles != nil {
args = append(args, runtimeFiles...)
} else {
for _, aPkg := range pkgs {
if aPkg.Package == pkg { // make empty runtime.init if no runtime needed
lpkg := aPkg.LPkg
lpkg.FuncOf(cl.RuntimeInit).MakeBody(1).Return()
if needLLFile(mode) {
file := pkg.ExportFile + ".ll"
os.WriteFile(file, []byte(lpkg.String()), 0644)
}
2024-05-07 21:06:47 +08:00
break
}
}
2024-04-29 00:49:17 +08:00
}
2024-04-25 00:53:42 +08:00
2024-04-29 01:34:21 +08:00
if verbose || mode != ModeRun {
fmt.Fprintln(os.Stderr, "#", pkgPath)
}
2024-04-29 01:34:21 +08:00
defer func() {
if e := recover(); e != nil {
nErr = 1
}
}()
// TODO(xsw): show work
2024-05-01 13:30:13 +08:00
if verbose {
fmt.Fprintln(os.Stderr, "clang", args)
}
2024-04-25 00:53:42 +08:00
err := clang.New("").Exec(args...)
check(err)
2024-04-25 01:41:44 +08:00
if mode == ModeRun {
cmd := exec.Command(app, conf.RunArgs...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
}
2024-04-29 01:34:21 +08:00
return
2024-04-25 00:53:42 +08:00
}
func buildPkg(prog llssa.Program, aPkg *aPackage, mode Mode, verbose bool) {
2024-04-25 00:53:42 +08:00
pkg := aPkg.Package
2024-04-24 11:13:17 +08:00
pkgPath := pkg.PkgPath
if verbose {
2024-04-25 01:41:44 +08:00
fmt.Fprintln(os.Stderr, pkgPath)
}
2024-04-26 02:05:49 +08:00
if pkgPath == "unsafe" { // TODO(xsw): maybe can remove this special case
2024-04-25 00:53:42 +08:00
return
2024-04-24 11:13:17 +08:00
}
2024-04-25 00:53:42 +08:00
ret, err := cl.NewPackage(prog, aPkg.SSA, pkg.Syntax)
2024-04-24 11:13:17 +08:00
check(err)
2024-04-25 01:41:44 +08:00
if needLLFile(mode) {
2024-04-24 11:49:43 +08:00
file := pkg.ExportFile + ".ll"
os.WriteFile(file, []byte(ret.String()), 0644)
2024-04-24 11:13:17 +08:00
}
aPkg.LPkg = ret
2024-04-24 11:13:17 +08:00
}
type aPackage struct {
*packages.Package
SSA *ssa.Package
LPkg llssa.Package
2024-04-24 11:13:17 +08:00
}
func allPkgs(initial []*packages.Package, mode ssa.BuilderMode) (prog *ssa.Program, all []*aPackage, errs []*packages.Package) {
2024-04-24 11:13:17 +08:00
var fset *token.FileSet
if len(initial) > 0 {
fset = initial[0].Fset
2024-04-24 07:55:51 +08:00
}
2024-04-24 11:13:17 +08:00
prog = ssa.NewProgram(fset, mode)
packages.Visit(initial, nil, func(p *packages.Package) {
if p.Types != nil && !p.IllTyped {
ssaPkg := prog.CreatePackage(p.Types, p.Syntax, p.TypesInfo, true)
all = append(all, &aPackage{p, ssaPkg, nil})
2024-04-24 11:13:17 +08:00
} else {
errs = append(errs, p)
}
})
return
2024-04-24 07:55:51 +08:00
}
var (
// TODO(xsw): complete build flags
buildFlags = map[string]bool{
"-C": true, // -C dir: Change to dir before running the command
"-a": false, // -a: force rebuilding of packages that are already up-to-date
"-n": false, // -n: print the commands but do not run them
"-p": true, // -p n: the number of programs to run in parallel
"-race": false, // -race: enable data race detection
"-cover": false, // -cover: enable coverage analysis
"-covermode": true, // -covermode mode: set the mode for coverage analysis
"-v": false, // -v: print the names of packages as they are compiled
"-work": false, // -work: print the name of the temporary work directory and do not delete it when exiting
"-x": false, // -x: print the commands
"-tags": true, // -tags 'tag,list': a space-separated list of build tags to consider satisfied during the build
"-pkgdir": true, // -pkgdir dir: install and load all packages from dir instead of the usual locations
}
)
func ParseArgs(args []string, swflags map[string]bool) (flags, patterns []string, verbose bool) {
n := len(args)
for i := 0; i < n; i++ {
arg := args[i]
if strings.HasPrefix(arg, "-") {
checkFlag(arg, &i, &verbose, swflags)
} else {
2024-04-24 14:27:14 +08:00
flags, patterns = args[:i], args[i:]
return
}
2024-04-24 07:55:51 +08:00
}
2024-04-24 14:27:14 +08:00
flags = args
return
2024-04-24 07:55:51 +08:00
}
2024-04-27 06:41:24 +08:00
func SkipFlagArgs(args []string) int {
n := len(args)
for i := 0; i < n; i++ {
arg := args[i]
if strings.HasPrefix(arg, "-") {
checkFlag(arg, &i, nil, buildFlags)
} else {
return i
}
}
return -1
}
func checkFlag(arg string, i *int, verbose *bool, swflags map[string]bool) {
if hasarg, ok := swflags[arg]; ok {
if hasarg {
*i++
} else if verbose != nil && arg == "-v" {
*verbose = true
}
} else {
panic("unknown flag: " + arg)
}
}
func allLinkFiles(rt []*packages.Package) (outFiles []string) {
outFiles = make([]string, 0, len(rt))
packages.Visit(rt, nil, func(p *packages.Package) {
2024-05-01 13:30:13 +08:00
pkgPath := p.PkgPath
if isRuntimePkg(pkgPath) {
outFile := llgoPkgLinkFile(pkgPath)
outFiles = append(outFiles, outFile)
}
})
return
}
2024-05-01 13:30:13 +08:00
const (
pkgAbi = llgoModPath + "/internal/abi"
pkgRuntime = llgoModPath + "/internal/runtime"
)
func isRuntimePkg(pkgPath string) bool {
switch pkgPath {
case pkgRuntime, pkgAbi:
return true
2024-04-29 11:34:59 +08:00
}
return false
}
var (
rootDir string
)
func llgoRoot() string {
if rootDir == "" {
root := os.Getenv("LLGOROOT")
if root == "" {
panic("todo: LLGOROOT not set")
}
rootDir, _ = filepath.Abs(root)
}
return rootDir
}
func llgoPkgLinkFile(pkgPath string) string {
2024-05-07 21:06:47 +08:00
llFile := filepath.Join(llgoRoot()+pkgPath[len(llgoModPath):], "llgo_autogen.ll")
if _, err := os.Stat(llFile); os.IsNotExist(err) {
decodeLinkFile(llFile)
}
return llFile
}
const (
llgoModPath = "github.com/goplus/llgo"
)
func isPkgInLLGo(pkgPath string) bool {
return isPkgInMod(pkgPath, llgoModPath)
}
func isPkgInMod(pkgPath, modPath string) bool {
if strings.HasPrefix(pkgPath, modPath) {
suffix := pkgPath[len(modPath):]
return suffix == "" || suffix[0] == '/'
}
return false
}
2024-05-07 21:06:47 +08:00
// *.ll => *.lla
func decodeLinkFile(llFile string) {
zipFile := llFile + "a"
zipf, err := zip.OpenReader(zipFile)
if err != nil {
return
}
defer zipf.Close()
f, err := zipf.Open("llgo_autogen.ll")
if err != nil {
return
}
defer f.Close()
data, err := io.ReadAll(f)
if err == nil {
os.WriteFile(llFile, data, 0644)
}
}
2024-04-24 07:55:51 +08:00
func check(err error) {
if err != nil {
panic(err)
}
}
// -----------------------------------------------------------------------------