github.com/aacfactory/fns@v1.2.86-0.20240310083819-80d667fc0a17/cmd/generates/sources/modules.go (about) 1 /* 2 * Copyright 2023 Wang Min Xiang 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 18 package sources 19 20 import ( 21 "bytes" 22 "context" 23 "fmt" 24 "github.com/aacfactory/errors" 25 "github.com/aacfactory/fns/cmd/generates/files" 26 "golang.org/x/mod/modfile" 27 "golang.org/x/sync/singleflight" 28 "os" 29 "path/filepath" 30 "sort" 31 "strings" 32 "sync" 33 ) 34 35 func New(path string) (v *Module, err error) { 36 v, err = NewWithWork(path, "") 37 return 38 } 39 40 func NewWithWork(path string, workPath string) (v *Module, err error) { 41 path = filepath.ToSlash(path) 42 if !filepath.IsAbs(path) { 43 absolute, absoluteErr := filepath.Abs(path) 44 if absoluteErr != nil { 45 err = errors.Warning("sources: new module failed"). 46 WithCause(errors.Warning("sources: get absolute representation of module file path failed").WithCause(absoluteErr).WithMeta("path", path)) 47 return 48 } 49 path = absolute 50 } 51 if !files.ExistFile(path) { 52 err = errors.Warning("sources: new module failed"). 53 WithCause(errors.Warning("sources: file was not found").WithMeta("path", path)) 54 return 55 } 56 pkgErr := initPkgDir() 57 if pkgErr != nil { 58 err = errors.Warning("sources: new module failed"). 59 WithCause(pkgErr) 60 return 61 } 62 if workPath != "" { 63 work := &Work{ 64 Dir: filepath.ToSlash(filepath.Dir(workPath)), 65 Filename: filepath.ToSlash(workPath), 66 Uses: nil, 67 Replaces: nil, 68 parsed: false, 69 } 70 parseWorkErr := work.Parse() 71 if parseWorkErr != nil { 72 err = errors.Warning("sources: new module failed"). 73 WithCause(parseWorkErr) 74 return 75 } 76 dir := filepath.ToSlash(filepath.Dir(path)) 77 for _, use := range work.Uses { 78 if use.Dir == dir { 79 v = use 80 break 81 } 82 } 83 if v == nil { 84 err = errors.Warning("sources: new module failed"). 85 WithCause(errors.Warning("can not find in workspace")) 86 return 87 } 88 } else { 89 v = &Module{ 90 Dir: filepath.ToSlash(filepath.Dir(path)), 91 Path: "", 92 Version: "", 93 Requires: nil, 94 Work: nil, 95 Replace: nil, 96 locker: &sync.Mutex{}, 97 parsed: false, 98 sources: nil, 99 builtinTypes: map[string]*Type{}, 100 types: nil, 101 } 102 registerBuiltinTypes(v) 103 } 104 return 105 } 106 107 type Module struct { 108 Dir string 109 Path string 110 Version string 111 Requires Requires 112 Work *Work 113 Replace *Module 114 locker sync.Locker 115 parsed bool 116 sources *Sources 117 builtinTypes map[string]*Type 118 types *Types 119 } 120 121 func (mod *Module) RegisterBuiltinType(typ *Type) { 122 if mod.builtinTypes == nil { 123 mod.builtinTypes = make(map[string]*Type) 124 } 125 key := fmt.Sprintf("%s.%s", typ.Path, typ.Name) 126 mod.builtinTypes[key] = typ 127 } 128 129 func (mod *Module) GetBuiltinType(path string, name string) (typ *Type, has bool) { 130 if mod.builtinTypes == nil { 131 return 132 } 133 typ, has = mod.builtinTypes[fmt.Sprintf("%s.%s", path, name)] 134 return 135 } 136 137 func (mod *Module) Parse(ctx context.Context) (err error) { 138 err = mod.parse(ctx, nil) 139 return 140 } 141 142 func (mod *Module) parse(ctx context.Context, host *Module) (err error) { 143 if ctx.Err() != nil { 144 err = errors.Warning("sources: parse mod failed"). 145 WithCause(ctx.Err()) 146 return 147 } 148 mod.locker.Lock() 149 defer mod.locker.Unlock() 150 if mod.parsed { 151 return 152 } 153 if mod.Replace != nil { 154 err = mod.Replace.parse(ctx, host) 155 if err != nil { 156 return 157 } 158 mod.parsed = true 159 return 160 } 161 162 modFilepath := filepath.ToSlash(filepath.Join(mod.Dir, "go.mod")) 163 if !files.ExistFile(modFilepath) { 164 err = errors.Warning("sources: parse mod failed"). 165 WithCause(errors.Warning("sources: mod file was not found"). 166 WithMeta("file", modFilepath)) 167 return 168 } 169 modData, readModErr := os.ReadFile(modFilepath) 170 if readModErr != nil { 171 err = errors.Warning("sources: parse mod failed"). 172 WithCause(errors.Warning("sources: read mod file failed"). 173 WithCause(readModErr). 174 WithMeta("file", modFilepath)) 175 return 176 } 177 mf, parseModErr := modfile.Parse(modFilepath, modData, nil) 178 if parseModErr != nil { 179 err = errors.Warning("sources: parse mod failed"). 180 WithCause(errors.Warning("sources: parse mod file failed").WithCause(parseModErr).WithMeta("file", modFilepath)) 181 return 182 } 183 mod.Path = mf.Module.Mod.Path 184 mod.Version = mf.Module.Mod.Version 185 mod.Requires = make([]*Module, 0, 1) 186 if mf.Require != nil && len(mf.Require) > 0 { 187 for _, require := range mf.Require { 188 if mod.Work != nil { 189 use, used := mod.Work.Use(require.Mod.Path) 190 if used { 191 mod.Requires = append(mod.Requires, use) 192 continue 193 } 194 } 195 requireDir := filepath.ToSlash(filepath.Join(PKG(), fmt.Sprintf("%s@%s", require.Mod.Path, require.Mod.Version))) 196 197 mod.Requires = append(mod.Requires, &Module{ 198 Dir: requireDir, 199 Path: require.Mod.Path, 200 Version: require.Mod.Version, 201 Requires: nil, 202 Work: nil, 203 Replace: nil, 204 locker: &sync.Mutex{}, 205 parsed: false, 206 sources: nil, 207 builtinTypes: mod.builtinTypes, 208 types: nil, 209 }) 210 } 211 } 212 if mf.Replace != nil && len(mf.Replace) > 0 { 213 for _, replace := range mf.Replace { 214 replaceDir := "" 215 if replace.New.Version != "" { 216 replaceDir = filepath.Join(PKG(), fmt.Sprintf("%s@%s", replace.New.Path, replace.New.Version)) 217 } else { 218 if filepath.IsAbs(replace.New.Path) { 219 replaceDir = replace.New.Path 220 } else { 221 replaceDir = filepath.Join(mod.Dir, replace.New.Path) 222 } 223 } 224 replaceDir = filepath.ToSlash(replaceDir) 225 if !files.ExistFile(replaceDir) { 226 err = errors.Warning("sources: parse mod failed").WithMeta("mod", mod.Path). 227 WithCause(errors.Warning("sources: replace dir was not found").WithMeta("replace", replaceDir)) 228 return 229 } 230 replaceFile := filepath.ToSlash(filepath.Join(replaceDir, "go.mod")) 231 if !files.ExistFile(replaceFile) { 232 err = errors.Warning("sources: parse mod failed").WithMeta("mod", mod.Path). 233 WithCause(errors.Warning("sources: replace mod file was not found"). 234 WithMeta("replace", replaceFile)) 235 return 236 } 237 replaceData, readReplaceErr := os.ReadFile(replaceFile) 238 if readReplaceErr != nil { 239 err = errors.Warning("sources: parse mod failed").WithMeta("mod", mod.Path). 240 WithCause(errors.Warning("sources: read replace mod file failed").WithCause(readReplaceErr).WithMeta("replace", replaceFile)) 241 return 242 } 243 rmf, parseReplaceModErr := modfile.Parse(replaceFile, replaceData, nil) 244 if parseReplaceModErr != nil { 245 err = errors.Warning("sources: parse mod failed").WithMeta("mod", mod.Path). 246 WithCause(errors.Warning("sources: parse replace mod file failed").WithCause(parseReplaceModErr).WithMeta("replace", replaceFile)) 247 return 248 } 249 for _, require := range mod.Requires { 250 if require.Path == replace.Old.Path && require.Version == replace.Old.Version { 251 require.Replace = &Module{ 252 Dir: replaceDir, 253 Path: rmf.Module.Mod.Path, 254 Version: rmf.Module.Mod.Version, 255 Requires: nil, 256 Work: nil, 257 Replace: nil, 258 locker: &sync.Mutex{}, 259 parsed: false, 260 sources: nil, 261 builtinTypes: mod.builtinTypes, 262 types: nil, 263 } 264 } 265 } 266 } 267 } 268 work := mod.Work 269 if host != nil && len(mod.Requires) > 0 { 270 if host.Replace != nil { 271 host = host.Replace 272 } 273 if host.Work != nil && work == nil { 274 work = host.Work 275 } 276 if host.Requires != nil { 277 for i, require := range mod.Requires { 278 if require.Work != nil || require.Replace != nil { 279 continue 280 } 281 for _, hr := range host.Requires { 282 if require.Path == hr.Path { 283 mod.Requires[i] = hr 284 break 285 } 286 } 287 } 288 } 289 } 290 if work != nil && len(work.Replaces) > 0 && len(mod.Requires) > 0 { 291 for i, require := range mod.Requires { 292 if require.Work != nil || require.Replace != nil { 293 continue 294 } 295 for _, replace := range work.Replaces { 296 if require.Path == replace.Path { 297 mod.Requires[i] = replace 298 break 299 } 300 } 301 } 302 } 303 if mod.Requires.Len() > 0 { 304 sort.Sort(sort.Reverse(mod.Requires)) 305 } 306 307 if host != nil { 308 mod.types = host.types 309 } else { 310 mod.types = &Types{ 311 values: sync.Map{}, 312 group: singleflight.Group{}, 313 } 314 } 315 316 mod.sources = newSource(mod.Path, mod.Dir) 317 mod.parsed = true 318 return 319 } 320 321 func (mod *Module) findModuleByPath(ctx context.Context, path string) (v *Module, has bool, err error) { 322 if ctx.Err() != nil { 323 err = errors.Warning("sources: find module by path failed"). 324 WithCause(ctx.Err()) 325 return 326 } 327 if mod.Requires != nil { 328 for _, require := range mod.Requires { 329 if path == require.Path || strings.HasPrefix(path, require.Path+"/") { 330 parseErr := require.parse(ctx, mod) 331 if parseErr != nil { 332 err = errors.Warning("sources: find module by path failed"). 333 WithCause(parseErr) 334 return 335 } 336 if require.Replace != nil { 337 require = require.Replace 338 } 339 v, has, err = require.findModuleByPath(ctx, path) 340 if has || err != nil { 341 return 342 } 343 } 344 } 345 } 346 if path == mod.Path || strings.HasPrefix(path, mod.Path+"/") { 347 if mod.Replace != nil { 348 v = mod.Replace 349 } else { 350 v = mod 351 } 352 has = true 353 return 354 } 355 return 356 } 357 358 func (mod *Module) ParseType(ctx context.Context, path string, name string) (typ *Type, err error) { 359 // module 360 typeModule, hasTypeModule, findTypeModuleErr := mod.findModuleByPath(ctx, path) 361 if findTypeModuleErr != nil { 362 err = errors.Warning("sources: mod parse type failed"). 363 WithMeta("path", path).WithMeta("name", name). 364 WithCause(findTypeModuleErr) 365 return 366 } 367 if !hasTypeModule { 368 err = errors.Warning("sources: mod parse type failed"). 369 WithMeta("path", path).WithMeta("name", name). 370 WithCause(errors.Warning("sources: module of type was not found")) 371 return 372 } 373 // spec 374 spec, specImports, genDoc, findSpecErr := typeModule.sources.FindTypeSpec(path, name) 375 if findSpecErr != nil { 376 err = errors.Warning("sources: mod parse type failed"). 377 WithMeta("path", path).WithMeta("name", name). 378 WithCause(findSpecErr) 379 return 380 } 381 382 typ, err = typeModule.types.parseType(ctx, spec, &TypeScope{ 383 Path: path, 384 Mod: typeModule, 385 Imports: specImports, 386 GenericDoc: genDoc, 387 }) 388 return 389 } 390 391 func (mod *Module) GetType(path string, name string) (typ *Type, has bool) { 392 key := formatTypeKey(path, name) 393 value, exist := mod.types.values.Load(key) 394 if !exist { 395 return 396 } 397 typ, has = value.(*Type) 398 return 399 } 400 401 func (mod *Module) Sources() *Sources { 402 return mod.sources 403 } 404 405 func (mod *Module) Types() (types *Types) { 406 types = mod.types 407 return 408 } 409 410 func (mod *Module) String() (s string) { 411 buf := bytes.NewBuffer([]byte{}) 412 _, _ = buf.WriteString(fmt.Sprintf("path: %s\n", mod.Path)) 413 _, _ = buf.WriteString(fmt.Sprintf("version: %s\n", mod.Version)) 414 for _, require := range mod.Requires { 415 _, _ = buf.WriteString(fmt.Sprintf("requre: %s@%s", require.Path, require.Version)) 416 if require.Replace != nil { 417 _, _ = buf.WriteString(fmt.Sprintf("=> %s", require.Replace.Path)) 418 if require.Replace.Version != "" { 419 _, _ = buf.WriteString(fmt.Sprintf("@%s", require.Replace.Version)) 420 } 421 } 422 _, _ = buf.WriteString("\n") 423 } 424 s = buf.String() 425 return 426 } 427 428 type Requires []*Module 429 430 func (requires Requires) Len() int { 431 return len(requires) 432 } 433 434 func (requires Requires) Less(i, j int) (ok bool) { 435 on := strings.Split(requires[i].Path, "/") 436 tn := strings.Split(requires[j].Path, "/") 437 n := len(on) 438 if len(on) > len(tn) { 439 n = len(tn) 440 } 441 x := 0 442 for x = 0; x < n; x++ { 443 if on[x] != tn[x] { 444 break 445 } 446 } 447 if x < n { 448 ok = on[x] > tn[x] 449 } else { 450 ok = len(on) < len(tn) 451 } 452 return 453 } 454 455 func (requires Requires) Swap(i, j int) { 456 requires[i], requires[j] = requires[j], requires[i] 457 return 458 }