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  }