github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/gnovm/pkg/gnomod/file.go (about) 1 // Some part of file is copied and modified from 2 // golang.org/x/mod/modfile/read.go 3 // 4 // Copyright 2018 The Go Authors. All rights reserved. 5 // Use of this source code is governed by a BSD-style 6 // license that can be found in here[1]. 7 // 8 // [1]: https://cs.opensource.google/go/x/mod/+/master:LICENSE 9 10 package gnomod 11 12 import ( 13 "errors" 14 "fmt" 15 "log" 16 "os" 17 "path/filepath" 18 "strings" 19 20 "github.com/gnolang/gno/gnovm/pkg/transpiler" 21 "golang.org/x/mod/modfile" 22 "golang.org/x/mod/module" 23 ) 24 25 // Parsed gno.mod file. 26 type File struct { 27 Draft bool 28 Module *modfile.Module 29 Go *modfile.Go 30 Require []*modfile.Require 31 Replace []*modfile.Replace 32 33 Syntax *modfile.FileSyntax 34 } 35 36 // AddRequire sets the first require line for path to version vers, 37 // preserving any existing comments for that line and removing all 38 // other lines for path. 39 // 40 // If no line currently exists for path, AddRequire adds a new line 41 // at the end of the last require block. 42 func (f *File) AddRequire(path, vers string) error { 43 need := true 44 for _, r := range f.Require { 45 if r.Mod.Path == path { 46 if need { 47 r.Mod.Version = vers 48 updateLine(r.Syntax, "require", modfile.AutoQuote(path), vers) 49 need = false 50 } else { 51 markLineAsRemoved(r.Syntax) 52 *r = modfile.Require{} 53 } 54 } 55 } 56 57 if need { 58 f.AddNewRequire(path, vers, false) 59 } 60 return nil 61 } 62 63 // AddNewRequire adds a new require line for path at version vers at the end of 64 // the last require block, regardless of any existing require lines for path. 65 func (f *File) AddNewRequire(path, vers string, indirect bool) { 66 line := addLine(f.Syntax, nil, "require", modfile.AutoQuote(path), vers) 67 r := &modfile.Require{ 68 Mod: module.Version{Path: path, Version: vers}, 69 Syntax: line, 70 } 71 setIndirect(r, indirect) 72 f.Require = append(f.Require, r) 73 } 74 75 func (f *File) AddModuleStmt(path string) error { 76 if f.Syntax == nil { 77 f.Syntax = new(modfile.FileSyntax) 78 } 79 if f.Module == nil { 80 f.Module = &modfile.Module{ 81 Mod: module.Version{Path: path}, 82 Syntax: addLine(f.Syntax, nil, "module", modfile.AutoQuote(path)), 83 } 84 } else { 85 f.Module.Mod.Path = path 86 updateLine(f.Module.Syntax, "module", modfile.AutoQuote(path)) 87 } 88 return nil 89 } 90 91 func (f *File) AddComment(text string) { 92 if f.Syntax == nil { 93 f.Syntax = new(modfile.FileSyntax) 94 } 95 f.Syntax.Stmt = append(f.Syntax.Stmt, &modfile.CommentBlock{ 96 Comments: modfile.Comments{ 97 Before: []modfile.Comment{ 98 { 99 Token: text, 100 }, 101 }, 102 }, 103 }) 104 } 105 106 func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error { 107 return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers) 108 } 109 110 func (f *File) DropRequire(path string) error { 111 for _, r := range f.Require { 112 if r.Mod.Path == path { 113 markLineAsRemoved(r.Syntax) 114 *r = modfile.Require{} 115 } 116 } 117 return nil 118 } 119 120 func (f *File) DropReplace(oldPath, oldVers string) error { 121 for _, r := range f.Replace { 122 if r.Old.Path == oldPath && r.Old.Version == oldVers { 123 markLineAsRemoved(r.Syntax) 124 *r = modfile.Replace{} 125 } 126 } 127 return nil 128 } 129 130 // Validate validates gno.mod 131 func (f *File) Validate() error { 132 if f.Module == nil { 133 return errors.New("requires module") 134 } 135 136 return nil 137 } 138 139 // Resolve takes a Require directive from File and returns any adequate replacement 140 // following the Replace directives. 141 func (f *File) Resolve(r *modfile.Require) module.Version { 142 mod, replaced := isReplaced(r.Mod, f.Replace) 143 if replaced { 144 return mod 145 } 146 return r.Mod 147 } 148 149 // FetchDeps fetches and writes gno.mod packages 150 // in GOPATH/pkg/gnomod/ 151 func (f *File) FetchDeps(path string, remote string, verbose bool) error { 152 for _, r := range f.Require { 153 mod := f.Resolve(r) 154 if r.Mod.Path != mod.Path { 155 if modfile.IsDirectoryPath(mod.Path) { 156 continue 157 } 158 } 159 indirect := "" 160 if r.Indirect { 161 indirect = "// indirect" 162 } 163 164 _, err := os.Stat(PackageDir(path, mod)) 165 if !os.IsNotExist(err) { 166 if verbose { 167 log.Println("cached", mod.Path, indirect) 168 } 169 continue 170 } 171 if verbose { 172 log.Println("fetching", mod.Path, indirect) 173 } 174 requirements, err := writePackage(remote, path, mod.Path) 175 if err != nil { 176 return fmt.Errorf("writepackage: %w", err) 177 } 178 179 modFile := new(File) 180 modFile.AddModuleStmt(mod.Path) 181 for _, req := range requirements { 182 path := req[1 : len(req)-1] // trim leading and trailing `"` 183 if strings.HasSuffix(path, modFile.Module.Mod.Path) { 184 continue 185 } 186 // skip if `std`, special case. 187 if path == transpiler.GnoStdPkgAfter { 188 continue 189 } 190 191 if strings.HasPrefix(path, transpiler.ImportPrefix) { 192 path = strings.TrimPrefix(path, transpiler.ImportPrefix+"/examples/") 193 modFile.AddNewRequire(path, "v0.0.0-latest", true) 194 } 195 } 196 197 err = modFile.FetchDeps(path, remote, verbose) 198 if err != nil { 199 return err 200 } 201 goMod, err := GnoToGoMod(*modFile) 202 if err != nil { 203 return err 204 } 205 pkgPath := PackageDir(path, mod) 206 goModFilePath := filepath.Join(pkgPath, "go.mod") 207 err = goMod.Write(goModFilePath) 208 if err != nil { 209 return err 210 } 211 } 212 213 return nil 214 } 215 216 // writes file to the given absolute file path 217 func (f *File) Write(fname string) error { 218 f.Syntax.Cleanup() 219 data := modfile.Format(f.Syntax) 220 err := os.WriteFile(fname, data, 0o644) 221 if err != nil { 222 return fmt.Errorf("writefile %q: %w", fname, err) 223 } 224 return nil 225 } 226 227 func (f *File) Sanitize() { 228 removeDups(f.Syntax, &f.Require, &f.Replace) 229 }