github.com/goplus/gop@v1.2.6/load.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" 24 "strings" 25 26 "github.com/goplus/gogen" 27 "github.com/goplus/gop/ast" 28 "github.com/goplus/gop/cl" 29 "github.com/goplus/gop/parser" 30 "github.com/goplus/gop/token" 31 "github.com/goplus/gop/x/c2go" 32 "github.com/goplus/gop/x/gopenv" 33 "github.com/goplus/mod/env" 34 "github.com/goplus/mod/gopmod" 35 "github.com/qiniu/x/errors" 36 ) 37 38 var ( 39 ErrNotFound = gopmod.ErrNotFound 40 ErrIgnoreNotated = errors.New("notated error ignored") 41 ) 42 43 // NotFound returns if cause err is ErrNotFound or not 44 func NotFound(err error) bool { 45 return gopmod.IsNotFound(err) 46 } 47 48 // IgnoreNotated returns if cause err is ErrIgnoreNotated or not. 49 func IgnoreNotated(err error) bool { 50 return errors.Err(err) == ErrIgnoreNotated 51 } 52 53 // ErrorPos returns where the error occurs. 54 func ErrorPos(err error) token.Pos { 55 switch v := err.(type) { 56 case *gogen.CodeError: 57 return v.Pos 58 case *gogen.MatchError: 59 return v.Pos() 60 case *gogen.ImportError: 61 return v.Pos 62 } 63 return token.NoPos 64 } 65 66 func ignNotatedErrs(err error, pkg *ast.Package, fset *token.FileSet) error { 67 switch v := err.(type) { 68 case errors.List: 69 var ret errors.List 70 for _, e := range v { 71 if isNotatedErr(e, pkg, fset) { 72 continue 73 } 74 ret = append(ret, e) 75 } 76 if len(ret) == 0 { 77 return ErrIgnoreNotated 78 } 79 return ret 80 default: 81 if isNotatedErr(err, pkg, fset) { 82 return ErrIgnoreNotated 83 } 84 return err 85 } 86 } 87 88 func isNotatedErr(err error, pkg *ast.Package, fset *token.FileSet) (notatedErr bool) { 89 pos := ErrorPos(err) 90 f := fset.File(pos) 91 if f == nil { 92 return 93 } 94 gopf, ok := pkg.Files[f.Name()] 95 if !ok { 96 return 97 } 98 lines := token.Lines(f) 99 i := f.Line(pos) - 1 // base 0 100 start := lines[i] 101 var end int 102 if i+1 < len(lines) { 103 end = lines[i+1] 104 } else { 105 end = f.Size() 106 } 107 text := string(gopf.Code[start:end]) 108 commentOff := strings.Index(text, "//") 109 if commentOff < 0 { 110 return 111 } 112 return strings.Contains(text[commentOff+2:], "compile error:") 113 } 114 115 // ----------------------------------------------------------------------------- 116 117 // Config represents a configuration for loading Go+ packages. 118 type Config struct { 119 Gop *env.Gop 120 Fset *token.FileSet 121 Mod *gopmod.Module 122 Importer *Importer 123 124 Filter func(fs.FileInfo) bool 125 126 // If not nil, it is used for returning result of checks Go+ dependencies. 127 // see https://pkg.go.dev/github.com/goplus/gogen#File.CheckGopDeps 128 GopDeps *int 129 130 // CacheFile specifies the file path of the cache. 131 CacheFile string 132 133 IgnoreNotatedError bool 134 DontUpdateGoMod bool 135 } 136 137 // ConfFlags represents configuration flags. 138 type ConfFlags int 139 140 const ( 141 ConfFlagIgnoreNotatedError ConfFlags = 1 << iota 142 ConfFlagDontUpdateGoMod 143 ConfFlagNoTestFiles 144 ConfFlagNoCacheFile 145 ) 146 147 // NewDefaultConf creates a dfault configuration for common cases. 148 func NewDefaultConf(dir string, flags ConfFlags) (conf *Config, err error) { 149 mod, err := LoadMod(dir) 150 if err != nil { 151 return 152 } 153 gop := gopenv.Get() 154 fset := token.NewFileSet() 155 imp := NewImporter(mod, gop, fset) 156 conf = &Config{ 157 Gop: gop, Fset: fset, Mod: mod, Importer: imp, 158 IgnoreNotatedError: flags&ConfFlagIgnoreNotatedError != 0, 159 DontUpdateGoMod: flags&ConfFlagDontUpdateGoMod != 0, 160 } 161 if flags&ConfFlagNoCacheFile == 0 { 162 conf.CacheFile = imp.CacheFile() 163 imp.Cache().Load(conf.CacheFile) 164 } 165 if flags&ConfFlagNoTestFiles != 0 { 166 conf.Filter = FilterNoTestFiles 167 } 168 return 169 } 170 171 // UpdateCache updates the cache. 172 func (conf *Config) UpdateCache(verbose ...bool) { 173 if conf.CacheFile != "" { 174 c := conf.Importer.Cache() 175 c.Save(conf.CacheFile) 176 if verbose != nil && verbose[0] { 177 fmt.Println("Times of calling go list:", c.ListTimes()) 178 } 179 } 180 } 181 182 // LoadMod loads a Go+ module from a specified directory. 183 func LoadMod(dir string) (mod *gopmod.Module, err error) { 184 mod, err = gopmod.Load(dir) 185 if err != nil && !gopmod.IsNotFound(err) { 186 err = errors.NewWith(err, `gopmod.Load(dir, 0)`, -2, "gopmod.Load", dir, 0) 187 return 188 } 189 if mod == nil { 190 mod = gopmod.Default 191 } 192 err = mod.ImportClasses() 193 if err != nil { 194 err = errors.NewWith(err, `mod.ImportClasses()`, -2, "(*gopmod.Module).ImportClasses", mod) 195 } 196 return 197 } 198 199 // FilterNoTestFiles filters to skip all testing files. 200 func FilterNoTestFiles(fi fs.FileInfo) bool { 201 fname := fi.Name() 202 suffix := "" 203 switch path.Ext(fname) { 204 case ".gox": 205 suffix = "test.gox" 206 case ".gop": 207 suffix = "_test.gop" 208 case ".go": 209 suffix = "_test.go" 210 default: 211 return true 212 } 213 return !strings.HasSuffix(fname, suffix) 214 } 215 216 // ----------------------------------------------------------------------------- 217 218 // LoadDir loads Go+ packages from a specified directory. 219 func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (out, test *gogen.Package, err error) { 220 if conf == nil { 221 conf = new(Config) 222 } 223 224 mod := conf.Mod 225 if mod == nil { 226 if mod, err = LoadMod(dir); err != nil { 227 err = errors.NewWith(err, `LoadMod(dir)`, -2, "gop.LoadMod", dir) 228 return 229 } 230 } 231 232 fset := conf.Fset 233 if fset == nil { 234 fset = token.NewFileSet() 235 } 236 pkgs, err := parser.ParseDirEx(fset, dir, parser.Config{ 237 ClassKind: mod.ClassKind, 238 Filter: conf.Filter, 239 Mode: parser.ParseComments | parser.SaveAbsFile, 240 }) 241 if err != nil { 242 return 243 } 244 if len(pkgs) == 0 { 245 return nil, nil, ErrNotFound 246 } 247 248 gop := conf.Gop 249 if gop == nil { 250 gop = gopenv.Get() 251 } 252 imp := conf.Importer 253 if imp == nil { 254 imp = NewImporter(mod, gop, fset) 255 } 256 257 var pkgTest *ast.Package 258 var clConf = &cl.Config{ 259 Fset: fset, 260 RelativeBase: relativeBaseOf(mod), 261 Importer: imp, 262 LookupClass: mod.LookupClass, 263 LookupPub: c2go.LookupPub(mod), 264 } 265 266 for name, pkg := range pkgs { 267 if strings.HasSuffix(name, "_test") { 268 if pkgTest != nil { 269 return nil, nil, ErrMultiTestPackges 270 } 271 pkgTest = pkg 272 continue 273 } 274 if out != nil { 275 return nil, nil, ErrMultiPackges 276 } 277 if len(pkg.Files) == 0 { // no Go+ source files 278 continue 279 } 280 if promptGenGo != nil && promptGenGo[0] { 281 fmt.Fprintln(os.Stderr, "GenGo", dir, "...") 282 } 283 out, err = cl.NewPackage("", pkg, clConf) 284 if err != nil { 285 if conf.IgnoreNotatedError { 286 err = ignNotatedErrs(err, pkg, fset) 287 } 288 return 289 } 290 } 291 if out == nil { 292 return nil, nil, ErrNotFound 293 } 294 if genTestPkg && pkgTest != nil { 295 test, err = cl.NewPackage("", pkgTest, clConf) 296 } 297 afterLoad(mod, gop, out, test, conf) 298 return 299 } 300 301 func afterLoad(mod *gopmod.Module, gop *env.Gop, out, test *gogen.Package, conf *Config) { 302 if mod.Path() == gopMod { // nothing to do for Go+ itself 303 return 304 } 305 updateMod := !conf.DontUpdateGoMod && mod.HasModfile() 306 if updateMod || conf.GopDeps != nil { 307 flags := checkGopDeps(out) 308 if conf.GopDeps != nil { // for `gop run` 309 *conf.GopDeps = flags 310 } 311 if updateMod { 312 if test != nil { 313 flags |= checkGopDeps(test) 314 } 315 if flags != 0 { 316 mod.SaveWithGopMod(gop, flags) 317 } 318 } 319 } 320 } 321 322 func checkGopDeps(pkg *gogen.Package) (flags int) { 323 pkg.ForEachFile(func(fname string, file *gogen.File) { 324 flags |= file.CheckGopDeps(pkg) 325 }) 326 return 327 } 328 329 func relativeBaseOf(mod *gopmod.Module) string { 330 if mod.HasModfile() { 331 return mod.Root() 332 } 333 dir, _ := os.Getwd() 334 return dir 335 } 336 337 // ----------------------------------------------------------------------------- 338 339 // LoadDir loads a Go+ package from specified files. 340 func LoadFiles(dir string, files []string, conf *Config) (out *gogen.Package, err error) { 341 if conf == nil { 342 conf = new(Config) 343 } 344 345 mod := conf.Mod 346 if mod == nil { 347 if mod, err = LoadMod(dir); err != nil { 348 err = errors.NewWith(err, `LoadMod(dir)`, -2, "gop.LoadMod", dir) 349 return 350 } 351 } 352 353 fset := conf.Fset 354 if fset == nil { 355 fset = token.NewFileSet() 356 } 357 pkgs, err := parser.ParseEntries(fset, files, parser.Config{ 358 ClassKind: mod.ClassKind, 359 Filter: conf.Filter, 360 Mode: parser.ParseComments | parser.SaveAbsFile, 361 }) 362 if err != nil { 363 err = errors.NewWith(err, `parser.ParseFiles(fset, files, parser.ParseComments)`, -2, "parser.ParseFiles", fset, files, parser.ParseComments) 364 return 365 } 366 if len(pkgs) != 1 { 367 err = errors.NewWith(ErrMultiPackges, `len(pkgs) != 1`, -1, "!=", len(pkgs), 1) 368 return 369 } 370 gop := conf.Gop 371 if gop == nil { 372 gop = gopenv.Get() 373 } 374 for _, pkg := range pkgs { 375 imp := conf.Importer 376 if imp == nil { 377 imp = NewImporter(mod, gop, fset) 378 } 379 clConf := &cl.Config{ 380 Fset: fset, 381 RelativeBase: relativeBaseOf(mod), 382 Importer: imp, 383 LookupClass: mod.LookupClass, 384 LookupPub: c2go.LookupPub(mod), 385 } 386 out, err = cl.NewPackage("", pkg, clConf) 387 if err != nil { 388 if conf.IgnoreNotatedError { 389 err = ignNotatedErrs(err, pkg, fset) 390 } 391 } 392 break 393 } 394 afterLoad(mod, gop, out, nil, conf) 395 return 396 } 397 398 // ----------------------------------------------------------------------------- 399 400 var ( 401 ErrMultiPackges = errors.New("multiple packages") 402 ErrMultiTestPackges = errors.New("multiple test packages") 403 ) 404 405 // ----------------------------------------------------------------------------- 406 407 // GetFileClassType get gop module file classType. 408 func GetFileClassType(mod *gopmod.Module, file *ast.File, filename string) (classType string, isTest bool, ok bool) { 409 return cl.GetFileClassType(file, filename, mod.LookupClass) 410 } 411 412 // -----------------------------------------------------------------------------