github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/gnovm/pkg/gnomod/gnomod.go (about) 1 package gnomod 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/gnolang/gno/gnovm/pkg/gnoenv" 11 "github.com/gnolang/gno/gnovm/pkg/gnolang" 12 "github.com/gnolang/gno/gnovm/pkg/transpiler" 13 "github.com/gnolang/gno/tm2/pkg/std" 14 "golang.org/x/mod/modfile" 15 "golang.org/x/mod/module" 16 ) 17 18 const queryPathFile = "vm/qfile" 19 20 // GetGnoModPath returns the path for gno modules 21 func GetGnoModPath() string { 22 return filepath.Join(gnoenv.HomeDir(), "pkg", "mod") 23 } 24 25 // PackageDir resolves a given module.Version to the path on the filesystem. 26 // If root is dir, it is defaulted to the value of [GetGnoModPath]. 27 func PackageDir(root string, v module.Version) string { 28 // This is also used internally exactly like filepath.Join; but we'll keep 29 // the calls centralized to make sure we can change the path centrally should 30 // we start including the module version in the path. 31 32 if root == "" { 33 root = GetGnoModPath() 34 } 35 return filepath.Join(root, v.Path) 36 } 37 38 func writePackage(remote, basePath, pkgPath string) (requirements []string, err error) { 39 res, err := queryChain(remote, queryPathFile, []byte(pkgPath)) 40 if err != nil { 41 return nil, fmt.Errorf("querychain (%s): %w", pkgPath, err) 42 } 43 44 dirPath, fileName := std.SplitFilepath(pkgPath) 45 if fileName == "" { 46 // Is Dir 47 // Create Dir if not exists 48 dirPath := filepath.Join(basePath, dirPath) 49 if _, err = os.Stat(dirPath); os.IsNotExist(err) { 50 if err = os.MkdirAll(dirPath, 0o755); err != nil { 51 return nil, fmt.Errorf("mkdir %q: %w", dirPath, err) 52 } 53 } 54 55 files := strings.Split(string(res.Data), "\n") 56 for _, file := range files { 57 reqs, err := writePackage(remote, basePath, filepath.Join(pkgPath, file)) 58 if err != nil { 59 return nil, fmt.Errorf("writepackage: %w", err) 60 } 61 requirements = append(requirements, reqs...) 62 } 63 } else { 64 // Is File 65 // Transpile and write generated go file 66 if strings.HasSuffix(fileName, ".gno") { 67 filePath := filepath.Join(basePath, pkgPath) 68 targetFilename, _ := transpiler.GetTranspileFilenameAndTags(filePath) 69 transpileRes, err := transpiler.Transpile(string(res.Data), "", fileName) 70 if err != nil { 71 return nil, fmt.Errorf("transpile: %w", err) 72 } 73 74 for _, i := range transpileRes.Imports { 75 requirements = append(requirements, i.Path.Value) 76 } 77 78 targetFileNameWithPath := filepath.Join(basePath, dirPath, targetFilename) 79 err = os.WriteFile(targetFileNameWithPath, []byte(transpileRes.Translated), 0o644) 80 if err != nil { 81 return nil, fmt.Errorf("writefile %q: %w", targetFileNameWithPath, err) 82 } 83 } 84 85 // Write file 86 fileNameWithPath := filepath.Join(basePath, dirPath, fileName) 87 err = os.WriteFile(fileNameWithPath, res.Data, 0o644) 88 if err != nil { 89 return nil, fmt.Errorf("writefile %q: %w", fileNameWithPath, err) 90 } 91 } 92 93 return removeDuplicateStr(requirements), nil 94 } 95 96 // GnoToGoMod make necessary modifications in the gno.mod 97 // and return go.mod file. 98 func GnoToGoMod(f File) (*File, error) { 99 gnoModPath := GetGnoModPath() 100 101 if strings.HasPrefix(f.Module.Mod.Path, transpiler.GnoRealmPkgsPrefixBefore) || 102 strings.HasPrefix(f.Module.Mod.Path, transpiler.GnoPackagePrefixBefore) { 103 f.AddModuleStmt(transpiler.ImportPrefix + "/examples/" + f.Module.Mod.Path) 104 } 105 106 for i := range f.Require { 107 mod, replaced := isReplaced(f.Require[i].Mod, f.Replace) 108 if replaced { 109 if modfile.IsDirectoryPath(mod.Path) { 110 continue 111 } 112 } 113 path := f.Require[i].Mod.Path 114 if strings.HasPrefix(f.Require[i].Mod.Path, transpiler.GnoRealmPkgsPrefixBefore) || 115 strings.HasPrefix(f.Require[i].Mod.Path, transpiler.GnoPackagePrefixBefore) { 116 // Add dependency with a modified import path 117 f.AddRequire(transpiler.ImportPrefix+"/examples/"+f.Require[i].Mod.Path, f.Require[i].Mod.Version) 118 } 119 f.AddReplace(f.Require[i].Mod.Path, f.Require[i].Mod.Version, filepath.Join(gnoModPath, path), "") 120 // Remove the old require since the new dependency was added above 121 f.DropRequire(f.Require[i].Mod.Path) 122 } 123 124 // Remove replacements that are not replaced by directories. 125 // 126 // Explanation: 127 // By this stage every replacement should be replace by dir. 128 // If not replaced by dir, remove it. 129 // 130 // e.g: 131 // 132 // ``` 133 // require ( 134 // gno.land/p/demo/avl v1.2.3 135 // ) 136 // 137 // replace ( 138 // gno.land/p/demo/avl v1.2.3 => gno.land/p/demo/avl v3.2.1 139 // ) 140 // ``` 141 // 142 // In above case we will fetch `gno.land/p/demo/avl v3.2.1` and 143 // replace will look something like: 144 // 145 // ``` 146 // replace ( 147 // gno.land/p/demo/avl v1.2.3 => gno.land/p/demo/avl v3.2.1 148 // gno.land/p/demo/avl v3.2.1 => /path/to/avl/version/v3.2.1 149 // ) 150 // ``` 151 // 152 // Remove `gno.land/p/demo/avl v1.2.3 => gno.land/p/demo/avl v3.2.1`. 153 for _, r := range f.Replace { 154 if !modfile.IsDirectoryPath(r.New.Path) { 155 f.DropReplace(r.Old.Path, r.Old.Version) 156 } 157 } 158 159 return &f, nil 160 } 161 162 func CreateGnoModFile(rootDir, modPath string) error { 163 if !filepath.IsAbs(rootDir) { 164 return fmt.Errorf("dir %q is not absolute", rootDir) 165 } 166 167 modFilePath := filepath.Join(rootDir, "gno.mod") 168 if _, err := os.Stat(modFilePath); err == nil { 169 return errors.New("gno.mod file already exists") 170 } 171 172 if modPath == "" { 173 // Check .gno files for package name 174 // and use it as modPath 175 files, err := os.ReadDir(rootDir) 176 if err != nil { 177 return fmt.Errorf("read dir %q: %w", rootDir, err) 178 } 179 180 var pkgName gnolang.Name 181 for _, file := range files { 182 if file.IsDir() || !strings.HasSuffix(file.Name(), ".gno") || strings.HasSuffix(file.Name(), "_filetest.gno") { 183 continue 184 } 185 186 fpath := filepath.Join(rootDir, file.Name()) 187 bz, err := os.ReadFile(fpath) 188 if err != nil { 189 return fmt.Errorf("read file %q: %w", fpath, err) 190 } 191 192 pn := gnolang.PackageNameFromFileBody(file.Name(), string(bz)) 193 if strings.HasSuffix(string(pkgName), "_test") { 194 pkgName = pkgName[:len(pkgName)-len("_test")] 195 } 196 if pkgName == "" { 197 pkgName = pn 198 } 199 if pkgName != pn { 200 return fmt.Errorf("package name mismatch: [%q] and [%q]", pkgName, pn) 201 } 202 } 203 if pkgName == "" { 204 return errors.New("cannot determine package name") 205 } 206 modPath = string(pkgName) 207 } 208 if err := module.CheckImportPath(modPath); err != nil { 209 return err 210 } 211 212 modfile := new(File) 213 modfile.AddModuleStmt(modPath) 214 modfile.Write(filepath.Join(rootDir, "gno.mod")) 215 216 return nil 217 } 218 219 func isReplaced(mod module.Version, repl []*modfile.Replace) (module.Version, bool) { 220 for _, r := range repl { 221 hasNoVersion := r.Old.Path == mod.Path && r.Old.Version == "" 222 hasExactVersion := r.Old == mod 223 if hasNoVersion || hasExactVersion { 224 return r.New, true 225 } 226 } 227 return module.Version{}, false 228 } 229 230 func removeDuplicateStr(str []string) (res []string) { 231 m := make(map[string]struct{}, len(str)) 232 for _, s := range str { 233 if _, ok := m[s]; !ok { 234 m[s] = struct{}{} 235 res = append(res, s) 236 } 237 } 238 return 239 }