github.com/goplus/llgo@v0.8.3/internal/build/build.go (about)

     1  /*
     2   * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package build
    18  
    19  import (
    20  	"archive/zip"
    21  	"fmt"
    22  	"go/token"
    23  	"go/types"
    24  	"io"
    25  	"os"
    26  	"os/exec"
    27  	"path"
    28  	"path/filepath"
    29  	"runtime"
    30  	"strings"
    31  
    32  	"golang.org/x/tools/go/packages"
    33  	"golang.org/x/tools/go/ssa"
    34  
    35  	"github.com/goplus/llgo/cl"
    36  	"github.com/goplus/llgo/xtool/clang"
    37  
    38  	llssa "github.com/goplus/llgo/ssa"
    39  )
    40  
    41  type Mode int
    42  
    43  const (
    44  	ModeBuild Mode = iota
    45  	ModeInstall
    46  	ModeRun
    47  )
    48  
    49  func needLLFile(mode Mode) bool {
    50  	return mode != ModeBuild
    51  }
    52  
    53  type Config struct {
    54  	BinPath string
    55  	AppExt  string   // ".exe" on Windows, empty on Unix
    56  	OutFile string   // only valid for ModeBuild when len(pkgs) == 1
    57  	RunArgs []string // only valid for ModeRun
    58  	Mode    Mode
    59  }
    60  
    61  func NewDefaultConf(mode Mode) *Config {
    62  	bin := os.Getenv("GOBIN")
    63  	if bin == "" {
    64  		bin = filepath.Join(runtime.GOROOT(), "bin")
    65  	}
    66  	conf := &Config{
    67  		BinPath: bin,
    68  		Mode:    mode,
    69  		AppExt:  DefaultAppExt(),
    70  	}
    71  	return conf
    72  }
    73  
    74  func DefaultAppExt() string {
    75  	if runtime.GOOS == "windows" {
    76  		return ".exe"
    77  	}
    78  	return ""
    79  }
    80  
    81  // -----------------------------------------------------------------------------
    82  
    83  const (
    84  	loadFiles   = packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles
    85  	loadImports = loadFiles | packages.NeedImports
    86  	loadTypes   = loadImports | packages.NeedTypes | packages.NeedTypesSizes
    87  	loadSyntax  = loadTypes | packages.NeedSyntax | packages.NeedTypesInfo
    88  )
    89  
    90  func Do(args []string, conf *Config) {
    91  	flags, patterns, verbose := ParseArgs(args, buildFlags)
    92  	cfg := &packages.Config{
    93  		Mode:       loadSyntax | packages.NeedDeps | packages.NeedModule | packages.NeedExportFile,
    94  		BuildFlags: flags,
    95  	}
    96  
    97  	if patterns == nil {
    98  		patterns = []string{"."}
    99  	}
   100  	initial, err := packages.Load(cfg, patterns...)
   101  	check(err)
   102  
   103  	mode := conf.Mode
   104  	if len(initial) == 1 && len(initial[0].CompiledGoFiles) > 0 {
   105  		if mode == ModeBuild {
   106  			mode = ModeInstall
   107  		}
   108  	} else if mode == ModeRun {
   109  		if len(initial) > 1 {
   110  			fmt.Fprintln(os.Stderr, "cannot run multiple packages")
   111  		} else {
   112  			fmt.Fprintln(os.Stderr, "no Go files in matched packages")
   113  		}
   114  		return
   115  	}
   116  
   117  	llssa.Initialize(llssa.InitAll)
   118  	if verbose {
   119  		llssa.SetDebug(llssa.DbgFlagAll)
   120  		cl.SetDebug(cl.DbgFlagAll)
   121  	}
   122  
   123  	var needRt bool
   124  	var rt []*packages.Package
   125  	prog := llssa.NewProgram(nil)
   126  	load := func() []*packages.Package {
   127  		if rt == nil {
   128  			var err error
   129  			rt, err = packages.Load(cfg, llssa.PkgRuntime, llssa.PkgPython)
   130  			check(err)
   131  		}
   132  		return rt
   133  	}
   134  	prog.SetRuntime(func() *types.Package {
   135  		needRt = true
   136  		rt := load()
   137  		return rt[0].Types
   138  	})
   139  	prog.SetPython(func() *types.Package {
   140  		rt := load()
   141  		return rt[1].Types
   142  	})
   143  
   144  	pkgs := buildAllPkgs(prog, initial, mode, verbose)
   145  
   146  	var runtimeFiles []string
   147  	if needRt {
   148  		runtimeFiles = allLinkFiles(rt)
   149  	}
   150  	if mode != ModeBuild {
   151  		nErr := 0
   152  		for _, pkg := range initial {
   153  			if pkg.Name == "main" {
   154  				nErr += linkMainPkg(pkg, pkgs, runtimeFiles, conf, mode, verbose)
   155  			}
   156  		}
   157  		if nErr > 0 {
   158  			os.Exit(nErr)
   159  		}
   160  	}
   161  }
   162  
   163  func setNeedRuntimeOrPyInit(pkg *packages.Package, needRuntime, needPyInit bool) {
   164  	v := []byte{'0', '0'}
   165  	if needRuntime {
   166  		v[0] = '1'
   167  	}
   168  	if needPyInit {
   169  		v[1] = '1'
   170  	}
   171  	pkg.ID = string(v) // just use pkg.ID to mark it needs runtime
   172  }
   173  
   174  func isNeedRuntimeOrPyInit(pkg *packages.Package) (needRuntime, needPyInit bool) {
   175  	if len(pkg.ID) == 2 {
   176  		return pkg.ID[0] == '1', pkg.ID[1] == '1'
   177  	}
   178  	return
   179  }
   180  
   181  func buildAllPkgs(prog llssa.Program, initial []*packages.Package, mode Mode, verbose bool) (pkgs []*aPackage) {
   182  	// Create SSA-form program representation.
   183  	ssaProg, pkgs, errPkgs := allPkgs(initial, ssa.SanityCheckFunctions)
   184  	ssaProg.Build()
   185  	for _, errPkg := range errPkgs {
   186  		for _, err := range errPkg.Errors {
   187  			fmt.Fprintln(os.Stderr, err)
   188  		}
   189  		fmt.Fprintln(os.Stderr, "cannot build SSA for package", errPkg)
   190  	}
   191  	for _, aPkg := range pkgs {
   192  		pkg := aPkg.Package
   193  		switch kind, param := cl.PkgKindOf(pkg.Types); kind {
   194  		case cl.PkgDeclOnly:
   195  			// skip packages that only contain declarations
   196  			// and set no export file
   197  			pkg.ExportFile = ""
   198  		case cl.PkgLinkIR, cl.PkgLinkExtern, cl.PkgPyModule:
   199  			pkgPath := pkg.PkgPath
   200  			if isPkgInLLGo(pkgPath) {
   201  				pkg.ExportFile = concatPkgLinkFiles(pkgPath)
   202  			} else {
   203  				// panic("todo")
   204  				// TODO(xsw): support packages out of llgo
   205  				pkg.ExportFile = ""
   206  			}
   207  			if kind == cl.PkgLinkExtern { // need to be linked with external library
   208  				linkFile := os.ExpandEnv(strings.TrimSpace(param))
   209  				dir, lib := filepath.Split(linkFile)
   210  				command := " -l " + lib
   211  				if dir != "" {
   212  					command += " -L " + dir[:len(dir)-1]
   213  				}
   214  				if isSingleLinkFile(pkg.ExportFile) {
   215  					pkg.ExportFile = command + " " + pkg.ExportFile
   216  				} else {
   217  					pkg.ExportFile = command + pkg.ExportFile
   218  				}
   219  			}
   220  		default:
   221  			buildPkg(prog, aPkg, mode, verbose)
   222  			setNeedRuntimeOrPyInit(pkg, prog.NeedRuntime, prog.NeedPyInit)
   223  		}
   224  	}
   225  	return
   226  }
   227  
   228  func linkMainPkg(pkg *packages.Package, pkgs []*aPackage, runtimeFiles []string, conf *Config, mode Mode, verbose bool) (nErr int) {
   229  	pkgPath := pkg.PkgPath
   230  	name := path.Base(pkgPath)
   231  	app := conf.OutFile
   232  	if app == "" {
   233  		app = filepath.Join(conf.BinPath, name+conf.AppExt)
   234  	}
   235  	const N = 3
   236  	args := make([]string, N, len(pkg.Imports)+len(runtimeFiles)+(N+1))
   237  	args[0] = "-o"
   238  	args[1] = app
   239  	args[2] = "-Wno-override-module"
   240  	needRuntime := false
   241  	needPyInit := false
   242  	packages.Visit([]*packages.Package{pkg}, nil, func(p *packages.Package) {
   243  		if p.ExportFile != "" && !isRuntimePkg(p.PkgPath) { // skip packages that only contain declarations
   244  			args = appendLinkFiles(args, p.ExportFile)
   245  			need1, need2 := isNeedRuntimeOrPyInit(p)
   246  			if !needRuntime {
   247  				needRuntime = need1
   248  			}
   249  			if !needPyInit {
   250  				needPyInit = need2
   251  			}
   252  		}
   253  	})
   254  
   255  	var aPkg *aPackage
   256  	for _, v := range pkgs {
   257  		if v.Package == pkg { // found this package
   258  			aPkg = v
   259  			break
   260  		}
   261  	}
   262  
   263  	dirty := false
   264  	if needRuntime && runtimeFiles != nil {
   265  		args = append(args, runtimeFiles...)
   266  	} else {
   267  		dirty = true
   268  		fn := aPkg.LPkg.FuncOf(cl.RuntimeInit)
   269  		fn.MakeBody(1).Return()
   270  	}
   271  	if needPyInit {
   272  		dirty = aPkg.LPkg.PyInit()
   273  	}
   274  
   275  	if dirty && needLLFile(mode) {
   276  		lpkg := aPkg.LPkg
   277  		os.WriteFile(pkg.ExportFile, []byte(lpkg.String()), 0644)
   278  	}
   279  
   280  	if verbose || mode != ModeRun {
   281  		fmt.Fprintln(os.Stderr, "#", pkgPath)
   282  	}
   283  	defer func() {
   284  		if e := recover(); e != nil {
   285  			nErr = 1
   286  		}
   287  	}()
   288  
   289  	// TODO(xsw): show work
   290  	if verbose {
   291  		fmt.Fprintln(os.Stderr, "clang", args)
   292  	}
   293  	err := clang.New("").Exec(args...)
   294  	check(err)
   295  
   296  	if mode == ModeRun {
   297  		cmd := exec.Command(app, conf.RunArgs...)
   298  		cmd.Stdin = os.Stdin
   299  		cmd.Stdout = os.Stdout
   300  		cmd.Stderr = os.Stderr
   301  		cmd.Run()
   302  	}
   303  	return
   304  }
   305  
   306  func buildPkg(prog llssa.Program, aPkg *aPackage, mode Mode, verbose bool) {
   307  	pkg := aPkg.Package
   308  	pkgPath := pkg.PkgPath
   309  	if verbose {
   310  		fmt.Fprintln(os.Stderr, pkgPath)
   311  	}
   312  	if canSkipToBuild(pkgPath) {
   313  		pkg.ExportFile = ""
   314  		return
   315  	}
   316  	ret, err := cl.NewPackage(prog, aPkg.SSA, pkg.Syntax)
   317  	check(err)
   318  	if needLLFile(mode) {
   319  		pkg.ExportFile += ".ll"
   320  		os.WriteFile(pkg.ExportFile, []byte(ret.String()), 0644)
   321  	}
   322  	aPkg.LPkg = ret
   323  }
   324  
   325  func canSkipToBuild(pkgPath string) bool {
   326  	switch pkgPath {
   327  	case "unsafe", "runtime", "errors", "sync", "sync/atomic":
   328  		return true
   329  	default:
   330  		return strings.HasPrefix(pkgPath, "internal/") ||
   331  			strings.HasPrefix(pkgPath, "runtime/internal/")
   332  	}
   333  }
   334  
   335  type aPackage struct {
   336  	*packages.Package
   337  	SSA  *ssa.Package
   338  	LPkg llssa.Package
   339  }
   340  
   341  func allPkgs(initial []*packages.Package, mode ssa.BuilderMode) (prog *ssa.Program, all []*aPackage, errs []*packages.Package) {
   342  	var fset *token.FileSet
   343  	if len(initial) > 0 {
   344  		fset = initial[0].Fset
   345  	}
   346  
   347  	prog = ssa.NewProgram(fset, mode)
   348  	packages.Visit(initial, nil, func(p *packages.Package) {
   349  		if p.Types != nil && !p.IllTyped {
   350  			ssaPkg := prog.CreatePackage(p.Types, p.Syntax, p.TypesInfo, true)
   351  			all = append(all, &aPackage{p, ssaPkg, nil})
   352  		} else {
   353  			errs = append(errs, p)
   354  		}
   355  	})
   356  	return
   357  }
   358  
   359  var (
   360  	// TODO(xsw): complete build flags
   361  	buildFlags = map[string]bool{
   362  		"-C":         true,  // -C dir: Change to dir before running the command
   363  		"-a":         false, // -a: force rebuilding of packages that are already up-to-date
   364  		"-n":         false, // -n: print the commands but do not run them
   365  		"-p":         true,  // -p n: the number of programs to run in parallel
   366  		"-race":      false, // -race: enable data race detection
   367  		"-cover":     false, // -cover: enable coverage analysis
   368  		"-covermode": true,  // -covermode mode: set the mode for coverage analysis
   369  		"-v":         false, // -v: print the names of packages as they are compiled
   370  		"-work":      false, // -work: print the name of the temporary work directory and do not delete it when exiting
   371  		"-x":         false, // -x: print the commands
   372  		"-tags":      true,  // -tags 'tag,list': a space-separated list of build tags to consider satisfied during the build
   373  		"-pkgdir":    true,  // -pkgdir dir: install and load all packages from dir instead of the usual locations
   374  		"-ldflags":   true,  // --ldflags 'flag list': arguments to pass on each go tool link invocation
   375  	}
   376  )
   377  
   378  func ParseArgs(args []string, swflags map[string]bool) (flags, patterns []string, verbose bool) {
   379  	n := len(args)
   380  	for i := 0; i < n; i++ {
   381  		arg := args[i]
   382  		if strings.HasPrefix(arg, "-") {
   383  			checkFlag(arg, &i, &verbose, swflags)
   384  		} else {
   385  			flags, patterns = args[:i], args[i:]
   386  			return
   387  		}
   388  	}
   389  	flags = args
   390  	return
   391  }
   392  
   393  func SkipFlagArgs(args []string) int {
   394  	n := len(args)
   395  	for i := 0; i < n; i++ {
   396  		arg := args[i]
   397  		if strings.HasPrefix(arg, "-") {
   398  			checkFlag(arg, &i, nil, buildFlags)
   399  		} else {
   400  			return i
   401  		}
   402  	}
   403  	return -1
   404  }
   405  
   406  func checkFlag(arg string, i *int, verbose *bool, swflags map[string]bool) {
   407  	if pos := strings.IndexByte(arg, '='); pos > 0 {
   408  		if verbose != nil && arg == "-v=true" {
   409  			*verbose = true
   410  		}
   411  	} else if hasarg, ok := swflags[arg]; ok {
   412  		if hasarg {
   413  			*i++
   414  		} else if verbose != nil && arg == "-v" {
   415  			*verbose = true
   416  		}
   417  	} else {
   418  		panic("unknown flag: " + arg)
   419  	}
   420  }
   421  
   422  func allLinkFiles(rt []*packages.Package) (outFiles []string) {
   423  	outFiles = make([]string, 0, len(rt))
   424  	packages.Visit(rt, nil, func(p *packages.Package) {
   425  		pkgPath := p.PkgPath
   426  		if isRuntimePkg(pkgPath) {
   427  			llgoPkgLinkFiles(pkgPath, func(linkFile string) {
   428  				outFiles = append(outFiles, linkFile)
   429  			})
   430  		}
   431  	})
   432  	return
   433  }
   434  
   435  const (
   436  	pkgAbi     = llgoModPath + "/internal/abi"
   437  	pkgRuntime = llgoModPath + "/internal/runtime"
   438  )
   439  
   440  func isRuntimePkg(pkgPath string) bool {
   441  	switch pkgPath {
   442  	case pkgRuntime, pkgAbi:
   443  		return true
   444  	}
   445  	return false
   446  }
   447  
   448  var (
   449  	rootDir string
   450  )
   451  
   452  func llgoRoot() string {
   453  	if rootDir == "" {
   454  		root := os.Getenv("LLGOROOT")
   455  		if root == "" {
   456  			panic("todo: LLGOROOT not set")
   457  		}
   458  		rootDir, _ = filepath.Abs(root)
   459  	}
   460  	return rootDir
   461  }
   462  
   463  func appendLinkFiles(args []string, file string) []string {
   464  	if isSingleLinkFile(file) {
   465  		return append(args, file)
   466  	}
   467  	return append(args, strings.Split(file[1:], " ")...)
   468  }
   469  
   470  func isSingleLinkFile(ret string) bool {
   471  	return len(ret) > 0 && ret[0] != ' '
   472  }
   473  
   474  func concatPkgLinkFiles(pkgPath string) string {
   475  	var b strings.Builder
   476  	var ret string
   477  	var n int
   478  	llgoPkgLinkFiles(pkgPath, func(linkFile string) {
   479  		if n == 0 {
   480  			ret = linkFile
   481  		} else {
   482  			b.WriteByte(' ')
   483  			b.WriteString(linkFile)
   484  		}
   485  		n++
   486  	})
   487  	if n > 1 {
   488  		b.WriteByte(' ')
   489  		b.WriteString(ret)
   490  		return b.String()
   491  	}
   492  	return ret
   493  }
   494  
   495  func llgoPkgLinkFiles(pkgPath string, procFile func(linkFile string)) {
   496  	dir := llgoRoot() + pkgPath[len(llgoModPath):] + "/"
   497  	llFile := dir + "llgo_autogen.ll"
   498  	llaFile := llFile + "a"
   499  	zipf, err := zip.OpenReader(llaFile)
   500  	if err != nil {
   501  		procFile(llFile)
   502  		return
   503  	}
   504  	defer zipf.Close()
   505  
   506  	for _, f := range zipf.File {
   507  		procFile(dir + f.Name)
   508  	}
   509  	if _, err := os.Stat(llFile); os.IsNotExist(err) {
   510  		for _, f := range zipf.File {
   511  			decodeFile(dir+f.Name, f)
   512  		}
   513  	}
   514  }
   515  
   516  const (
   517  	llgoModPath = "github.com/goplus/llgo"
   518  )
   519  
   520  func isPkgInLLGo(pkgPath string) bool {
   521  	return isPkgInMod(pkgPath, llgoModPath)
   522  }
   523  
   524  func isPkgInMod(pkgPath, modPath string) bool {
   525  	if strings.HasPrefix(pkgPath, modPath) {
   526  		suffix := pkgPath[len(modPath):]
   527  		return suffix == "" || suffix[0] == '/'
   528  	}
   529  	return false
   530  }
   531  
   532  /*
   533  func llgoPkgLinkFile(pkgPath string) string {
   534  	// if kind == cl.PkgLinkBitCode {
   535  	//	return filepath.Join(llgoRoot()+pkgPath[len(llgoModPath):], "llgo_autogen.bc")
   536  	// }
   537  	llFile := filepath.Join(llgoRoot()+pkgPath[len(llgoModPath):], "llgo_autogen.ll")
   538  	if _, err := os.Stat(llFile); os.IsNotExist(err) {
   539  		decodeLinkFile(llFile)
   540  	}
   541  	return llFile
   542  }
   543  
   544  // *.ll => *.lla
   545  func decodeLinkFile(llFile string) {
   546  	zipFile := llFile + "a"
   547  	zipf, err := zip.OpenReader(zipFile)
   548  	if err != nil {
   549  		return
   550  	}
   551  	defer zipf.Close()
   552  	f, err := zipf.Open("llgo_autogen.ll")
   553  	if err != nil {
   554  		return
   555  	}
   556  	defer f.Close()
   557  	data, err := io.ReadAll(f)
   558  	if err == nil {
   559  		os.WriteFile(llFile, data, 0644)
   560  	}
   561  }
   562  */
   563  
   564  func decodeFile(outFile string, zipf *zip.File) (err error) {
   565  	f, err := zipf.Open()
   566  	if err != nil {
   567  		return
   568  	}
   569  	defer f.Close()
   570  	data, err := io.ReadAll(f)
   571  	if err == nil {
   572  		err = os.WriteFile(outFile, data, 0644)
   573  	}
   574  	return
   575  }
   576  
   577  func check(err error) {
   578  	if err != nil {
   579  		panic(err)
   580  	}
   581  }
   582  
   583  // -----------------------------------------------------------------------------