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 // -----------------------------------------------------------------------------