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  }