github.com/goplus/igop@v0.25.0/gopbuild/build.go (about)

     1  /*
     2   * Copyright (c) 2022 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 gopbuild
    18  
    19  //go:generate go run ../cmd/qexp -outdir ../pkg github.com/goplus/gop/builtin
    20  //go:generate go run ../cmd/qexp -outdir ../pkg github.com/goplus/gop/builtin/ng
    21  //go:generate go run ../cmd/qexp -outdir ../pkg github.com/goplus/gop/builtin/iox
    22  //go:generate go run ../cmd/qexp -outdir ../pkg github.com/qiniu/x/errors
    23  //go:generate go run ../cmd/qexp -outdir ../pkg github.com/qiniu/x/gsh
    24  
    25  import (
    26  	"bytes"
    27  	"fmt"
    28  	goast "go/ast"
    29  	"go/types"
    30  	"path/filepath"
    31  
    32  	"github.com/goplus/gogen"
    33  	"github.com/goplus/gop/ast"
    34  	"github.com/goplus/gop/cl"
    35  	"github.com/goplus/gop/parser"
    36  	"github.com/goplus/gop/token"
    37  	"github.com/goplus/igop"
    38  	"github.com/goplus/mod/modfile"
    39  
    40  	_ "github.com/goplus/igop/pkg/bufio"
    41  	_ "github.com/goplus/igop/pkg/context"
    42  	_ "github.com/goplus/igop/pkg/errors"
    43  	_ "github.com/goplus/igop/pkg/fmt"
    44  	_ "github.com/goplus/igop/pkg/github.com/goplus/gop/builtin"
    45  	_ "github.com/goplus/igop/pkg/github.com/goplus/gop/builtin/iox"
    46  	_ "github.com/goplus/igop/pkg/github.com/goplus/gop/builtin/ng"
    47  	_ "github.com/goplus/igop/pkg/github.com/qiniu/x/errors"
    48  	_ "github.com/goplus/igop/pkg/github.com/qiniu/x/gsh"
    49  	_ "github.com/goplus/igop/pkg/io"
    50  	_ "github.com/goplus/igop/pkg/log"
    51  	_ "github.com/goplus/igop/pkg/math"
    52  	_ "github.com/goplus/igop/pkg/math/big"
    53  	_ "github.com/goplus/igop/pkg/math/bits"
    54  	_ "github.com/goplus/igop/pkg/os"
    55  	_ "github.com/goplus/igop/pkg/os/exec"
    56  	_ "github.com/goplus/igop/pkg/path/filepath"
    57  	_ "github.com/goplus/igop/pkg/runtime"
    58  	_ "github.com/goplus/igop/pkg/strconv"
    59  	_ "github.com/goplus/igop/pkg/strings"
    60  )
    61  
    62  type Class = cl.Class
    63  
    64  var (
    65  	projects = make(map[string]*cl.Project)
    66  )
    67  
    68  func RegisterClassFileType(ext string, class string, works []*Class, pkgPaths ...string) {
    69  	cls := &cl.Project{
    70  		Ext:      ext,
    71  		Class:    class,
    72  		Works:    works,
    73  		PkgPaths: pkgPaths,
    74  	}
    75  	if ext != "" {
    76  		projects[ext] = cls
    77  	}
    78  	for _, w := range works {
    79  		projects[w.Ext] = cls
    80  	}
    81  }
    82  
    83  func init() {
    84  	cl.SetDebug(cl.FlagNoMarkAutogen)
    85  	igop.RegisterFileProcess(".gop", BuildFile)
    86  	igop.RegisterFileProcess(".gox", BuildFile)
    87  	igop.RegisterFileProcess(".gsh", BuildFile)
    88  	RegisterClassFileType(".gmx", "Game", []*Class{{Ext: ".spx", Class: "Sprite"}}, "github.com/goplus/spx", "math")
    89  	RegisterClassFileType(".gsh", "App", nil, "github.com/qiniu/x/gsh", "math")
    90  }
    91  
    92  func BuildFile(ctx *igop.Context, filename string, src interface{}) (data []byte, err error) {
    93  	defer func() {
    94  		r := recover()
    95  		if r != nil {
    96  			err = fmt.Errorf("compile %v failed. %v", filename, r)
    97  		}
    98  	}()
    99  	c := NewContext(ctx)
   100  	pkg, err := c.ParseFile(filename, src)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	return pkg.ToSource()
   105  }
   106  
   107  func BuildFSDir(ctx *igop.Context, fs parser.FileSystem, dir string) (data []byte, err error) {
   108  	defer func() {
   109  		r := recover()
   110  		if r != nil {
   111  			err = fmt.Errorf("compile %v failed. %v", dir, err)
   112  		}
   113  	}()
   114  	c := NewContext(ctx)
   115  	pkg, err := c.ParseFSDir(fs, dir)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	return pkg.ToSource()
   120  }
   121  
   122  func BuildDir(ctx *igop.Context, dir string) (data []byte, err error) {
   123  	defer func() {
   124  		r := recover()
   125  		if r != nil {
   126  			err = fmt.Errorf("compile %v failed. %v", dir, err)
   127  		}
   128  	}()
   129  	c := NewContext(ctx)
   130  	pkg, err := c.ParseDir(dir)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	return pkg.ToSource()
   135  }
   136  
   137  type Package struct {
   138  	Fset *token.FileSet
   139  	Pkg  *gogen.Package
   140  }
   141  
   142  func (p *Package) ToSource() ([]byte, error) {
   143  	var buf bytes.Buffer
   144  	if err := p.Pkg.WriteTo(&buf); err != nil {
   145  		return nil, err
   146  	}
   147  	return buf.Bytes(), nil
   148  }
   149  
   150  func (p *Package) ToAst() *goast.File {
   151  	return p.Pkg.ASTFile()
   152  }
   153  
   154  type Context struct {
   155  	ctx  *igop.Context
   156  	fset *token.FileSet
   157  	imp  *igop.Importer
   158  	gop  igop.Loader
   159  }
   160  
   161  func ClassKind(fname string) (isProj, ok bool) {
   162  	ext := modfile.ClassExt(fname)
   163  	switch ext {
   164  	case ".gmx", ".gsh":
   165  		return true, true
   166  	case ".spx":
   167  		return fname == "main.spx", true
   168  	default:
   169  		if c, ok := projects[ext]; ok {
   170  			for _, w := range c.Works {
   171  				if w.Ext == ext {
   172  					if ext != c.Ext || fname != "main"+ext {
   173  						return false, true
   174  					}
   175  					break
   176  				}
   177  			}
   178  			return true, true
   179  		}
   180  	}
   181  	return
   182  }
   183  
   184  func NewContext(ctx *igop.Context) *Context {
   185  	if ctx.IsEvalMode() {
   186  		ctx = igop.NewContext(0)
   187  	}
   188  	ctx.Mode |= igop.CheckGopOverloadFunc
   189  	return &Context{ctx: ctx, imp: igop.NewImporter(ctx), fset: token.NewFileSet(), gop: igop.NewTypesLoader(ctx, 0)}
   190  }
   191  
   192  func isGopPackage(path string) bool {
   193  	if pkg, ok := igop.LookupPackage(path); ok {
   194  		if _, ok := pkg.UntypedConsts["GopPackage"]; ok {
   195  			return true
   196  		}
   197  	}
   198  	return false
   199  }
   200  
   201  func (c *Context) Import(path string) (*types.Package, error) {
   202  	if isGopPackage(path) {
   203  		return c.gop.Import(path)
   204  	}
   205  	return c.imp.Import(path)
   206  }
   207  
   208  func (c *Context) ParseDir(dir string) (*Package, error) {
   209  	pkgs, err := parser.ParseDirEx(c.fset, dir, parser.Config{
   210  		ClassKind: ClassKind,
   211  	})
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  	return c.loadPackage(dir, pkgs)
   216  }
   217  
   218  func (c *Context) ParseFSDir(fs parser.FileSystem, dir string) (*Package, error) {
   219  	pkgs, err := parser.ParseFSDir(c.fset, fs, dir, parser.Config{
   220  		ClassKind: ClassKind,
   221  	})
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  	return c.loadPackage(dir, pkgs)
   226  }
   227  
   228  func (c *Context) ParseFile(fname string, src interface{}) (*Package, error) {
   229  	srcDir, _ := filepath.Split(fname)
   230  	fnameRmGox := fname
   231  	ext := filepath.Ext(fname)
   232  	var err error
   233  	var isProj, isClass, isNormalGox, rmGox bool
   234  	switch ext {
   235  	case ".go", ".gop":
   236  	case ".gox":
   237  		isClass = true
   238  		t := fname[:len(fname)-4]
   239  		if c := filepath.Ext(t); c != "" {
   240  			ext, fnameRmGox, rmGox = c, t, true
   241  		} else {
   242  			isNormalGox = true
   243  		}
   244  		fallthrough
   245  	default:
   246  		if !isNormalGox {
   247  			if isProj, isClass = ClassKind(fnameRmGox); !isClass {
   248  				if rmGox {
   249  					return nil, fmt.Errorf("not found Go+ class by ext %q for %q", ext, fname)
   250  				}
   251  				return nil, nil
   252  			}
   253  		}
   254  	}
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  	mode := parser.ParseComments
   259  	if isClass {
   260  		mode |= parser.ParseGoPlusClass
   261  	}
   262  	f, err := parser.ParseFile(c.fset, fname, src, mode)
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  	f.IsProj, f.IsClass, f.IsNormalGox = isProj, isClass, isNormalGox
   267  	name := f.Name.Name
   268  	pkgs := map[string]*ast.Package{
   269  		name: &ast.Package{
   270  			Name: name,
   271  			Files: map[string]*ast.File{
   272  				fname: f,
   273  			},
   274  		},
   275  	}
   276  	return c.loadPackage(srcDir, pkgs)
   277  }
   278  
   279  func (c *Context) loadPackage(srcDir string, pkgs map[string]*ast.Package) (*Package, error) {
   280  	mainPkg, ok := pkgs["main"]
   281  	if !ok {
   282  		for _, v := range pkgs {
   283  			mainPkg = v
   284  			break
   285  		}
   286  	}
   287  	if c.ctx.Mode&igop.DisableCustomBuiltin == 0 {
   288  		if f, err := igop.ParseBuiltin(c.fset, mainPkg.Name); err == nil {
   289  			mainPkg.GoFiles = map[string]*goast.File{"_igop_builtin.go": f}
   290  		}
   291  	}
   292  	conf := &cl.Config{Fset: c.fset}
   293  	conf.Importer = c
   294  	conf.LookupClass = func(ext string) (c *cl.Project, ok bool) {
   295  		c, ok = projects[ext]
   296  		return
   297  	}
   298  	if c.ctx.IsEvalMode() {
   299  		conf.NoSkipConstant = true
   300  	}
   301  	out, err := cl.NewPackage("", mainPkg, conf)
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  	return &Package{c.fset, out}, nil
   306  }