github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/gnovm/pkg/gnomod/pkg.go (about) 1 package gnomod 2 3 import ( 4 "fmt" 5 "io/fs" 6 "os" 7 "path/filepath" 8 "strings" 9 ) 10 11 type Pkg struct { 12 Dir string // absolute path to package dir 13 Name string // package name 14 Requires []string // dependencies 15 Draft bool // whether the package is a draft 16 } 17 18 type SubPkg struct { 19 Dir string // absolute path to package dir 20 ImportPath string // import path of package 21 Root string // Root dir containing this package, i.e dir containing gno.mod file 22 Imports []string // imports used by this package 23 24 GnoFiles []string // .gno source files (excluding TestGnoFiles, FiletestGnoFiles) 25 TestGnoFiles []string // _test.gno source files 26 FiletestGnoFiles []string // _filetest.gno source files 27 } 28 29 type ( 30 PkgList []Pkg 31 SortedPkgList []Pkg 32 ) 33 34 // sortPkgs sorts the given packages by their dependencies. 35 func (pl PkgList) Sort() (SortedPkgList, error) { 36 visited := make(map[string]bool) 37 onStack := make(map[string]bool) 38 sortedPkgs := make([]Pkg, 0, len(pl)) 39 40 // Visit all packages 41 for _, p := range pl { 42 if err := visitPackage(p, pl, visited, onStack, &sortedPkgs); err != nil { 43 return nil, err 44 } 45 } 46 47 return sortedPkgs, nil 48 } 49 50 // visitNode visits a package's and its dependencies dependencies and adds them to the sorted list. 51 func visitPackage(pkg Pkg, pkgs []Pkg, visited, onStack map[string]bool, sortedPkgs *[]Pkg) error { 52 if onStack[pkg.Name] { 53 return fmt.Errorf("cycle detected: %s", pkg.Name) 54 } 55 if visited[pkg.Name] { 56 return nil 57 } 58 59 visited[pkg.Name] = true 60 onStack[pkg.Name] = true 61 62 // Visit package's dependencies 63 for _, req := range pkg.Requires { 64 found := false 65 for _, p := range pkgs { 66 if p.Name != req { 67 continue 68 } 69 if err := visitPackage(p, pkgs, visited, onStack, sortedPkgs); err != nil { 70 return err 71 } 72 found = true 73 break 74 } 75 if !found { 76 return fmt.Errorf("missing dependency '%s' for package '%s'", req, pkg.Name) 77 } 78 } 79 80 onStack[pkg.Name] = false 81 *sortedPkgs = append(*sortedPkgs, pkg) 82 return nil 83 } 84 85 // ListPkgs lists all gno packages in the given root directory. 86 func ListPkgs(root string) (PkgList, error) { 87 var pkgs []Pkg 88 89 err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { 90 if err != nil { 91 return err 92 } 93 if !d.IsDir() { 94 return nil 95 } 96 gnoModPath := filepath.Join(path, "gno.mod") 97 data, err := os.ReadFile(gnoModPath) 98 if os.IsNotExist(err) { 99 return nil 100 } 101 if err != nil { 102 return err 103 } 104 105 gnoMod, err := Parse(gnoModPath, data) 106 if err != nil { 107 return fmt.Errorf("parse: %w", err) 108 } 109 gnoMod.Sanitize() 110 if err := gnoMod.Validate(); err != nil { 111 return fmt.Errorf("validate: %w", err) 112 } 113 114 pkgs = append(pkgs, Pkg{ 115 Dir: path, 116 Name: gnoMod.Module.Mod.Path, 117 Draft: gnoMod.Draft, 118 Requires: func() []string { 119 var reqs []string 120 for _, req := range gnoMod.Require { 121 reqs = append(reqs, req.Mod.Path) 122 } 123 return reqs 124 }(), 125 }) 126 return nil 127 }) 128 if err != nil { 129 return nil, err 130 } 131 132 return pkgs, nil 133 } 134 135 // GetNonDraftPkgs returns packages that are not draft 136 // and have no direct or indirect draft dependencies. 137 func (sp SortedPkgList) GetNonDraftPkgs() SortedPkgList { 138 res := make([]Pkg, 0, len(sp)) 139 draft := make(map[string]bool) 140 141 for _, pkg := range sp { 142 if pkg.Draft { 143 draft[pkg.Name] = true 144 continue 145 } 146 dependsOnDraft := false 147 for _, req := range pkg.Requires { 148 if draft[req] { 149 dependsOnDraft = true 150 draft[pkg.Name] = true 151 break 152 } 153 } 154 if !dependsOnDraft { 155 res = append(res, pkg) 156 } 157 } 158 return res 159 } 160 161 // SubPkgsFromPaths returns a list of subpackages from the given paths. 162 func SubPkgsFromPaths(paths []string) ([]*SubPkg, error) { 163 for _, path := range paths { 164 fi, err := os.Stat(path) 165 if err != nil { 166 return nil, err 167 } 168 if fi.IsDir() { 169 continue 170 } 171 if filepath.Ext(path) != ".gno" { 172 return nil, fmt.Errorf("files must be .gno files: %s", path) 173 } 174 175 subPkg, err := GnoFileSubPkg(paths) 176 if err != nil { 177 return nil, err 178 } 179 return []*SubPkg{subPkg}, nil 180 } 181 182 subPkgs := make([]*SubPkg, 0, len(paths)) 183 for _, path := range paths { 184 subPkg := SubPkg{} 185 186 matches, err := filepath.Glob(filepath.Join(path, "*.gno")) 187 if err != nil { 188 return nil, fmt.Errorf("failed to match pattern: %w", err) 189 } 190 191 subPkg.Dir = path 192 for _, match := range matches { 193 if strings.HasSuffix(match, "_test.gno") { 194 subPkg.TestGnoFiles = append(subPkg.TestGnoFiles, match) 195 continue 196 } 197 198 if strings.HasSuffix(match, "_filetest.gno") { 199 subPkg.FiletestGnoFiles = append(subPkg.FiletestGnoFiles, match) 200 continue 201 } 202 subPkg.GnoFiles = append(subPkg.GnoFiles, match) 203 } 204 205 subPkgs = append(subPkgs, &subPkg) 206 } 207 208 return subPkgs, nil 209 } 210 211 // GnoFileSubPkg returns a subpackage from the given .gno files. 212 func GnoFileSubPkg(files []string) (*SubPkg, error) { 213 subPkg := SubPkg{} 214 firstDir := "" 215 for _, file := range files { 216 if filepath.Ext(file) != ".gno" { 217 return nil, fmt.Errorf("files must be .gno files: %s", file) 218 } 219 220 fi, err := os.Stat(file) 221 if err != nil { 222 return nil, err 223 } 224 if fi.IsDir() { 225 return nil, fmt.Errorf("%s is a directory, should be a Gno file", file) 226 } 227 228 dir := filepath.Dir(file) 229 if firstDir == "" { 230 firstDir = dir 231 } 232 if dir != firstDir { 233 return nil, fmt.Errorf("all files must be in one directory; have %s and %s", firstDir, dir) 234 } 235 236 if strings.HasSuffix(file, "_test.gno") { 237 subPkg.TestGnoFiles = append(subPkg.TestGnoFiles, file) 238 continue 239 } 240 241 if strings.HasSuffix(file, "_filetest.gno") { 242 subPkg.FiletestGnoFiles = append(subPkg.FiletestGnoFiles, file) 243 continue 244 } 245 subPkg.GnoFiles = append(subPkg.GnoFiles, file) 246 } 247 subPkg.Dir = firstDir 248 249 return &subPkg, nil 250 }