github.com/goplus/gop@v1.2.6/gengo.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 gop
    18  
    19  import (
    20  	"fmt"
    21  	"io/fs"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  	"syscall"
    26  
    27  	"github.com/goplus/mod/gopmod"
    28  	"github.com/goplus/mod/modcache"
    29  	"github.com/goplus/mod/modfetch"
    30  	"github.com/qiniu/x/errors"
    31  )
    32  
    33  const (
    34  	testingGoFile    = "_test"
    35  	autoGenFile      = "gop_autogen.go"
    36  	autoGenTestFile  = "gop_autogen_test.go"
    37  	autoGen2TestFile = "gop_autogen2_test.go"
    38  )
    39  
    40  type GenFlags int
    41  
    42  const (
    43  	GenFlagCheckOnly GenFlags = 1 << iota
    44  	GenFlagSingleFile
    45  	GenFlagPrintError
    46  	GenFlagPrompt
    47  )
    48  
    49  // -----------------------------------------------------------------------------
    50  
    51  // GenGo generates gop_autogen.go for a Go+ package directory.
    52  func GenGo(dir string, conf *Config, genTestPkg bool) (string, bool, error) {
    53  	return GenGoEx(dir, conf, genTestPkg, 0)
    54  }
    55  
    56  // GenGoEx generates gop_autogen.go for a Go+ package directory.
    57  func GenGoEx(dir string, conf *Config, genTestPkg bool, flags GenFlags) (string, bool, error) {
    58  	recursively := strings.HasSuffix(dir, "/...")
    59  	if recursively {
    60  		dir = dir[:len(dir)-4]
    61  	}
    62  	return dir, recursively, genGoDir(dir, conf, genTestPkg, recursively, flags)
    63  }
    64  
    65  func genGoDir(dir string, conf *Config, genTestPkg, recursively bool, flags GenFlags) (err error) {
    66  	if conf == nil {
    67  		conf = new(Config)
    68  	}
    69  	if recursively {
    70  		var (
    71  			list errors.List
    72  			fn   func(path string, d fs.DirEntry, err error) error
    73  		)
    74  		if flags&GenFlagSingleFile != 0 {
    75  			fn = func(path string, d fs.DirEntry, err error) error {
    76  				if err != nil {
    77  					return err
    78  				}
    79  				return genGoEntry(&list, path, d, conf, flags)
    80  			}
    81  		} else {
    82  			fn = func(path string, d fs.DirEntry, err error) error {
    83  				if err == nil && d.IsDir() {
    84  					if strings.HasPrefix(d.Name(), "_") || (path != dir && hasMod(path)) { // skip _
    85  						return filepath.SkipDir
    86  					}
    87  					if e := genGoIn(path, conf, genTestPkg, flags); e != nil && notIgnNotated(e, conf) {
    88  						if flags&GenFlagPrintError != 0 {
    89  							fmt.Fprintln(os.Stderr, e)
    90  						}
    91  						list.Add(e)
    92  					}
    93  				}
    94  				return err
    95  			}
    96  		}
    97  		err = filepath.WalkDir(dir, fn)
    98  		if err != nil {
    99  			return errors.NewWith(err, `filepath.WalkDir(dir, fn)`, -2, "filepath.WalkDir", dir, fn)
   100  		}
   101  		return list.ToError()
   102  	}
   103  	if flags&GenFlagSingleFile != 0 {
   104  		var list errors.List
   105  		var entries, e = os.ReadDir(dir)
   106  		if e != nil {
   107  			return errors.NewWith(e, `os.ReadDir(dir)`, -2, "os.ReadDir", dir)
   108  		}
   109  		for _, d := range entries {
   110  			genGoEntry(&list, filepath.Join(dir, d.Name()), d, conf, flags)
   111  		}
   112  		return list.ToError()
   113  	}
   114  	if e := genGoIn(dir, conf, genTestPkg, flags); e != nil && notIgnNotated(e, conf) {
   115  		if (flags & GenFlagPrintError) != 0 {
   116  			fmt.Fprintln(os.Stderr, e)
   117  		}
   118  		err = e
   119  	}
   120  	return
   121  }
   122  
   123  func hasMod(dir string) bool {
   124  	_, err := os.Lstat(dir + "/go.mod")
   125  	return err == nil
   126  }
   127  
   128  func notIgnNotated(e error, conf *Config) bool {
   129  	return !(conf != nil && conf.IgnoreNotatedError && IgnoreNotated(e))
   130  }
   131  
   132  func genGoEntry(list *errors.List, path string, d fs.DirEntry, conf *Config, flags GenFlags) error {
   133  	fname := d.Name()
   134  	if strings.HasPrefix(fname, "_") { // skip _
   135  		if d.IsDir() {
   136  			return filepath.SkipDir
   137  		}
   138  	} else if !d.IsDir() && strings.HasSuffix(fname, ".gop") {
   139  		if e := genGoSingleFile(path, conf, flags); e != nil && notIgnNotated(e, conf) {
   140  			if flags&GenFlagPrintError != 0 {
   141  				fmt.Fprintln(os.Stderr, e)
   142  			}
   143  			list.Add(e)
   144  		}
   145  	}
   146  	return nil
   147  }
   148  
   149  func genGoSingleFile(file string, conf *Config, flags GenFlags) (err error) {
   150  	dir, fname := filepath.Split(file)
   151  	autogen := dir + strings.TrimSuffix(fname, ".gop") + "_autogen.go"
   152  	if (flags & GenFlagPrompt) != 0 {
   153  		fmt.Fprintln(os.Stderr, "GenGo", file, "...")
   154  	}
   155  	out, err := LoadFiles(".", []string{file}, conf)
   156  	if err != nil {
   157  		return errors.NewWith(err, `LoadFiles(files, conf)`, -2, "gop.LoadFiles", file)
   158  	}
   159  	if flags&GenFlagCheckOnly != 0 {
   160  		return nil
   161  	}
   162  	if err := out.WriteFile(autogen); err != nil {
   163  		return errors.NewWith(err, `out.WriteFile(autogen)`, -2, "(*gogen.Package).WriteFile", out, autogen)
   164  	}
   165  	return nil
   166  }
   167  
   168  func genGoIn(dir string, conf *Config, genTestPkg bool, flags GenFlags, gen ...*bool) (err error) {
   169  	out, test, err := LoadDir(dir, conf, genTestPkg, (flags&GenFlagPrompt) != 0)
   170  	if err != nil {
   171  		if NotFound(err) { // no Go+ source files
   172  			return nil
   173  		}
   174  		return errors.NewWith(err, `LoadDir(dir, conf, genTestPkg)`, -5, "gop.LoadDir", dir, conf, genTestPkg)
   175  	}
   176  	if flags&GenFlagCheckOnly != 0 {
   177  		return nil
   178  	}
   179  	os.MkdirAll(dir, 0755)
   180  	file := filepath.Join(dir, autoGenFile)
   181  	err = out.WriteFile(file)
   182  	if err != nil {
   183  		return errors.NewWith(err, `out.WriteFile(file)`, -2, "(*gogen.Package).WriteFile", out, file)
   184  	}
   185  	if gen != nil { // say `gop_autogen.go generated`
   186  		*gen[0] = true
   187  	}
   188  
   189  	testFile := filepath.Join(dir, autoGenTestFile)
   190  	err = out.WriteFile(testFile, testingGoFile)
   191  	if err != nil && err != syscall.ENOENT {
   192  		return errors.NewWith(err, `out.WriteFile(testFile, testingGoFile)`, -2, "(*gogen.Package).WriteFile", out, testFile, testingGoFile)
   193  	}
   194  
   195  	if test != nil {
   196  		testFile = filepath.Join(dir, autoGen2TestFile)
   197  		err = test.WriteFile(testFile, testingGoFile)
   198  		if err != nil {
   199  			return errors.NewWith(err, `test.WriteFile(testFile, testingGoFile)`, -2, "(*gogen.Package).WriteFile", test, testFile, testingGoFile)
   200  		}
   201  	} else {
   202  		err = nil
   203  	}
   204  	return
   205  }
   206  
   207  // -----------------------------------------------------------------------------
   208  
   209  const (
   210  	modWritable = 0755
   211  	modReadonly = 0555
   212  )
   213  
   214  // GenGoPkgPath generates gop_autogen.go for a Go+ package.
   215  func GenGoPkgPath(workDir, pkgPath string, conf *Config, allowExtern bool) (localDir string, recursively bool, err error) {
   216  	return GenGoPkgPathEx(workDir, pkgPath, conf, allowExtern, 0)
   217  }
   218  
   219  func remotePkgPath(pkgPath string, conf *Config, recursively bool, flags GenFlags) (localDir string, _recursively bool, err error) {
   220  	remotePkgPathDo(pkgPath, func(dir, _ string) {
   221  		os.Chmod(dir, modWritable)
   222  		defer os.Chmod(dir, modReadonly)
   223  		localDir = dir
   224  		_recursively = recursively
   225  		err = genGoDir(dir, conf, false, recursively, flags)
   226  	}, func(e error) {
   227  		err = e
   228  	})
   229  	return
   230  }
   231  
   232  // GenGoPkgPathEx generates gop_autogen.go for a Go+ package.
   233  func GenGoPkgPathEx(workDir, pkgPath string, conf *Config, allowExtern bool, flags GenFlags) (localDir string, recursively bool, err error) {
   234  	recursively = strings.HasSuffix(pkgPath, "/...")
   235  	if recursively {
   236  		pkgPath = pkgPath[:len(pkgPath)-4]
   237  	} else if allowExtern && strings.Contains(pkgPath, "@") {
   238  		return remotePkgPath(pkgPath, conf, false, flags)
   239  	}
   240  
   241  	mod, err := gopmod.Load(workDir)
   242  	if NotFound(err) && allowExtern {
   243  		return remotePkgPath(pkgPath, conf, recursively, flags)
   244  	} else if err != nil {
   245  		return
   246  	}
   247  
   248  	pkg, err := mod.Lookup(pkgPath)
   249  	if err != nil {
   250  		return
   251  	}
   252  	localDir = pkg.Dir
   253  	if pkg.Type == gopmod.PkgtExtern {
   254  		os.Chmod(localDir, modWritable)
   255  		defer os.Chmod(localDir, modReadonly)
   256  	}
   257  	err = genGoDir(localDir, conf, false, recursively, flags)
   258  	return
   259  }
   260  
   261  func remotePkgPathDo(pkgPath string, doSth func(pkgDir, modDir string), onErr func(e error)) {
   262  	modVer, leftPart, err := modfetch.GetPkg(pkgPath, "")
   263  	if err != nil {
   264  		onErr(err)
   265  	} else if dir, err := modcache.Path(modVer); err != nil {
   266  		onErr(err)
   267  	} else {
   268  		doSth(filepath.Join(dir, leftPart), dir)
   269  	}
   270  }
   271  
   272  // -----------------------------------------------------------------------------
   273  
   274  // GenGoFiles generates gop_autogen.go for specified Go+ files.
   275  func GenGoFiles(autogen string, files []string, conf *Config) (outFiles []string, err error) {
   276  	if conf == nil {
   277  		conf = new(Config)
   278  	}
   279  	if autogen == "" {
   280  		autogen = "gop_autogen.go"
   281  		if len(files) == 1 {
   282  			file := files[0]
   283  			srcDir, fname := filepath.Split(file)
   284  			if hasMultiFiles(srcDir, ".gop") {
   285  				autogen = filepath.Join(srcDir, "gop_autogen_"+fname+".go")
   286  			}
   287  		}
   288  	}
   289  	out, err := LoadFiles(".", files, conf)
   290  	if err != nil {
   291  		err = errors.NewWith(err, `LoadFiles(files, conf)`, -2, "gop.LoadFiles", files, conf)
   292  		return
   293  	}
   294  	err = out.WriteFile(autogen)
   295  	if err != nil {
   296  		err = errors.NewWith(err, `out.WriteFile(autogen)`, -2, "(*gogen.Package).WriteFile", out, autogen)
   297  	}
   298  	outFiles = []string{autogen}
   299  	return
   300  }
   301  
   302  func hasMultiFiles(srcDir string, ext string) bool {
   303  	var has bool
   304  	if f, err := os.Open(srcDir); err == nil {
   305  		defer f.Close()
   306  		fis, _ := f.ReadDir(-1)
   307  		for _, fi := range fis {
   308  			if !fi.IsDir() && filepath.Ext(fi.Name()) == ext {
   309  				if has {
   310  					return true
   311  				}
   312  				has = true
   313  			}
   314  		}
   315  	}
   316  	return false
   317  }
   318  
   319  // -----------------------------------------------------------------------------