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