github.com/goplus/gop@v1.2.6/imp.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  	"crypto/sha256"
    21  	"encoding/base64"
    22  	"fmt"
    23  	"go/token"
    24  	"go/types"
    25  	"io"
    26  	"log"
    27  	"os"
    28  	"os/exec"
    29  	"path"
    30  	"path/filepath"
    31  	"runtime"
    32  	"strings"
    33  
    34  	"github.com/goplus/gogen/packages"
    35  	"github.com/goplus/gogen/packages/cache"
    36  	"github.com/goplus/mod/env"
    37  	"github.com/goplus/mod/gopmod"
    38  	"github.com/goplus/mod/modfetch"
    39  	"github.com/goplus/mod/modfile"
    40  )
    41  
    42  // -----------------------------------------------------------------------------
    43  
    44  // Importer represents a Go+ importer.
    45  type Importer struct {
    46  	impFrom *packages.Importer
    47  	mod     *gopmod.Module
    48  	gop     *env.Gop
    49  	fset    *token.FileSet
    50  
    51  	Flags GenFlags // can change this for loading Go+ modules
    52  }
    53  
    54  // NewImporter creates a Go+ Importer.
    55  func NewImporter(mod *gopmod.Module, gop *env.Gop, fset *token.FileSet) *Importer {
    56  	const (
    57  		defaultFlags = GenFlagPrompt | GenFlagPrintError
    58  	)
    59  	if mod == nil || !mod.HasModfile() {
    60  		if modGop, e := gopmod.LoadFrom(filepath.Join(gop.Root, "go.mod"), ""); e == nil {
    61  			modGop.ImportClasses()
    62  			mod = modGop
    63  		} else {
    64  			mod = gopmod.Default
    65  		}
    66  	}
    67  	dir := mod.Root()
    68  	impFrom := packages.NewImporter(fset, dir)
    69  	ret := &Importer{mod: mod, gop: gop, impFrom: impFrom, fset: fset, Flags: defaultFlags}
    70  	impFrom.SetCache(cache.New(ret.PkgHash))
    71  	return ret
    72  }
    73  
    74  // CacheFile returns file path of the cache.
    75  func (p *Importer) CacheFile() string {
    76  	cacheDir, _ := os.UserCacheDir()
    77  	cacheDir += "/gop-build/"
    78  	os.MkdirAll(cacheDir, 0755)
    79  
    80  	fname := ""
    81  	h := sha256.New()
    82  	if root := p.mod.Root(); root != "" {
    83  		io.WriteString(h, root)
    84  		fname = filepath.Base(root)
    85  	}
    86  	hash := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
    87  	return cacheDir + hash + fname
    88  }
    89  
    90  // Cache returns the cache object.
    91  func (p *Importer) Cache() *cache.Impl {
    92  	return p.impFrom.Cache().(*cache.Impl)
    93  }
    94  
    95  // PkgHash calculates hash value for a package.
    96  // It is required by cache.New func.
    97  func (p *Importer) PkgHash(pkgPath string, self bool) string {
    98  	if pkg, e := p.mod.Lookup(pkgPath); e == nil {
    99  		switch pkg.Type {
   100  		case gopmod.PkgtStandard:
   101  			return cache.HashSkip
   102  		case gopmod.PkgtExtern:
   103  			if pkg.Real.Version != "" {
   104  				return pkg.Real.String()
   105  			}
   106  			fallthrough
   107  		case gopmod.PkgtModule:
   108  			return dirHash(p.mod, p.gop, pkg.Dir, self)
   109  		}
   110  	}
   111  	if isPkgInMod(pkgPath, gopMod) {
   112  		return cache.HashSkip
   113  	}
   114  	log.Println("PkgHash: unexpected package -", pkgPath)
   115  	return cache.HashInvalid
   116  }
   117  
   118  const (
   119  	gopMod = "github.com/goplus/gop"
   120  )
   121  
   122  // Import imports a Go/Go+ package.
   123  func (p *Importer) Import(pkgPath string) (pkg *types.Package, err error) {
   124  	if strings.HasPrefix(pkgPath, gopMod) {
   125  		if suffix := pkgPath[len(gopMod):]; suffix == "" || suffix[0] == '/' {
   126  			gopRoot := p.gop.Root
   127  			if suffix == "/cl/internal/gop-in-go/foo" { // for test github.com/goplus/gop/cl
   128  				if err = p.genGoExtern(gopRoot+suffix, false); err != nil {
   129  					return
   130  				}
   131  			}
   132  			return p.impFrom.ImportFrom(pkgPath, gopRoot, 0)
   133  		}
   134  	}
   135  	if isPkgInMod(pkgPath, "github.com/qiniu/x") {
   136  		return p.impFrom.ImportFrom(pkgPath, p.gop.Root, 0)
   137  	}
   138  	if mod := p.mod; mod.HasModfile() {
   139  		ret, e := mod.Lookup(pkgPath)
   140  		if e != nil {
   141  			return nil, e
   142  		}
   143  		switch ret.Type {
   144  		case gopmod.PkgtExtern:
   145  			isExtern := ret.Real.Version != ""
   146  			if isExtern {
   147  				if _, err = modfetch.Get(ret.Real.String()); err != nil {
   148  					return
   149  				}
   150  			}
   151  			modDir := ret.ModDir
   152  			goModfile := filepath.Join(modDir, "go.mod")
   153  			if _, e := os.Lstat(goModfile); e != nil { // no go.mod
   154  				os.Chmod(modDir, modWritable)
   155  				defer os.Chmod(modDir, modReadonly)
   156  				os.WriteFile(goModfile, defaultGoMod(ret.ModPath), 0644)
   157  			}
   158  			return p.impFrom.ImportFrom(pkgPath, ret.ModDir, 0)
   159  		case gopmod.PkgtModule, gopmod.PkgtLocal:
   160  			if err = p.genGoExtern(ret.Dir, false); err != nil {
   161  				return
   162  			}
   163  		case gopmod.PkgtStandard:
   164  			return p.impFrom.ImportFrom(pkgPath, p.gop.Root, 0)
   165  		}
   166  	}
   167  	return p.impFrom.Import(pkgPath)
   168  }
   169  
   170  func (p *Importer) genGoExtern(dir string, isExtern bool) (err error) {
   171  	genfile := filepath.Join(dir, autoGenFile)
   172  	if _, err = os.Lstat(genfile); err != nil { // no gop_autogen.go
   173  		if isExtern {
   174  			os.Chmod(dir, modWritable)
   175  			defer os.Chmod(dir, modReadonly)
   176  		}
   177  		gen := false
   178  		err = genGoIn(dir, &Config{Gop: p.gop, Importer: p, Fset: p.fset}, false, p.Flags, &gen)
   179  		if err != nil {
   180  			return
   181  		}
   182  		if gen {
   183  			cmd := exec.Command("go", "mod", "tidy")
   184  			cmd.Stdout = os.Stdout
   185  			cmd.Stderr = os.Stderr
   186  			cmd.Dir = dir
   187  			err = cmd.Run()
   188  		}
   189  	}
   190  	return
   191  }
   192  
   193  func isPkgInMod(pkgPath, modPath string) bool {
   194  	if strings.HasPrefix(pkgPath, modPath) {
   195  		suffix := pkgPath[len(modPath):]
   196  		return suffix == "" || suffix[0] == '/'
   197  	}
   198  	return false
   199  }
   200  
   201  func defaultGoMod(modPath string) []byte {
   202  	return []byte(`module ` + modPath + `
   203  
   204  go 1.16
   205  `)
   206  }
   207  
   208  func dirHash(mod *gopmod.Module, gop *env.Gop, dir string, self bool) string {
   209  	h := sha256.New()
   210  	if self {
   211  		fmt.Fprintf(h, "go\t%s\n", runtime.Version())
   212  		fmt.Fprintf(h, "gop\t%s\n", gop.Version)
   213  	}
   214  	if fis, err := os.ReadDir(dir); err == nil {
   215  		for _, fi := range fis {
   216  			if fi.IsDir() {
   217  				continue
   218  			}
   219  			fname := fi.Name()
   220  			if strings.HasPrefix(fname, "_") || !canCl(mod, fname) {
   221  				continue
   222  			}
   223  			if v, e := fi.Info(); e == nil {
   224  				fmt.Fprintf(h, "file\t%s\t%x\t%x\n", fname, v.Size(), v.ModTime().UnixNano())
   225  			}
   226  		}
   227  	}
   228  	return base64.RawStdEncoding.EncodeToString(h.Sum(nil))
   229  }
   230  
   231  func canCl(mod *gopmod.Module, fname string) bool {
   232  	switch path.Ext(fname) {
   233  	case ".go", ".gop", ".gox":
   234  		return true
   235  	default:
   236  		ext := modfile.ClassExt(fname)
   237  		return mod.IsClass(ext)
   238  	}
   239  }
   240  
   241  // -----------------------------------------------------------------------------