Skip to content

Commit

Permalink
feat: use modfile package to write modfile (gnolang#1077)
Browse files Browse the repository at this point in the history
The previous implementation manually iterates over the `Require` and
`Replace` and writes them in the string var to construct the modfile,
which is very inefficient and doesn't handles comments and other cases.

Changed it use `modfile` package to write modfile(gno.mod/go.mod). It
uses `*modfile.FileSyntax`. Copied few methods from `modfile` package to
manipulate `*modfile.FileSyntax`.

<details><summary>Contributors' checklist...</summary>

- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [x] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>

---------

Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com>
  • Loading branch information
harry-hov and moul committed Oct 5, 2023
1 parent 4af3eb2 commit d865095
Show file tree
Hide file tree
Showing 5 changed files with 875 additions and 76 deletions.
2 changes: 1 addition & 1 deletion gnovm/cmd/gno/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func execModDownload(cfg *modDownloadCfg, args []string, io *commands.IO) error
}

// write go.mod file
err = gomod.WriteToPath(filepath.Join(path, "go.mod"))
err = gomod.Write(filepath.Join(path, "go.mod"))
if err != nil {
return fmt.Errorf("write go.mod file: %w", err)
}
Expand Down
162 changes: 113 additions & 49 deletions gnovm/pkg/gnomod/file.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
// Some part of file is copied and modified from
// golang.org/x/mod/modfile/read.go
//
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in here[1].
//
// [1]: https://cs.opensource.google/go/x/mod/+/master:LICENSE

package gnomod

import (
Expand All @@ -24,6 +33,100 @@ type File struct {
Syntax *modfile.FileSyntax
}

// AddRequire sets the first require line for path to version vers,
// preserving any existing comments for that line and removing all
// other lines for path.
//
// If no line currently exists for path, AddRequire adds a new line
// at the end of the last require block.
func (f *File) AddRequire(path, vers string) error {
need := true
for _, r := range f.Require {
if r.Mod.Path == path {
if need {
r.Mod.Version = vers
updateLine(r.Syntax, "require", modfile.AutoQuote(path), vers)
need = false
} else {
markLineAsRemoved(r.Syntax)
*r = modfile.Require{}
}
}
}

if need {
f.AddNewRequire(path, vers, false)
}
return nil
}

// AddNewRequire adds a new require line for path at version vers at the end of
// the last require block, regardless of any existing require lines for path.
func (f *File) AddNewRequire(path, vers string, indirect bool) {
line := addLine(f.Syntax, nil, "require", modfile.AutoQuote(path), vers)
r := &modfile.Require{
Mod: module.Version{Path: path, Version: vers},
Syntax: line,
}
setIndirect(r, indirect)
f.Require = append(f.Require, r)
}

func (f *File) AddModuleStmt(path string) error {
if f.Syntax == nil {
f.Syntax = new(modfile.FileSyntax)
}
if f.Module == nil {
f.Module = &modfile.Module{
Mod: module.Version{Path: path},
Syntax: addLine(f.Syntax, nil, "module", modfile.AutoQuote(path)),
}
} else {
f.Module.Mod.Path = path
updateLine(f.Module.Syntax, "module", modfile.AutoQuote(path))
}
return nil
}

func (f *File) AddComment(text string) {
if f.Syntax == nil {
f.Syntax = new(modfile.FileSyntax)
}
f.Syntax.Stmt = append(f.Syntax.Stmt, &modfile.CommentBlock{
Comments: modfile.Comments{
Before: []modfile.Comment{
{
Token: text,
},
},
},
})
}

func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers)
}

func (f *File) DropRequire(path string) error {
for _, r := range f.Require {
if r.Mod.Path == path {
markLineAsRemoved(r.Syntax)
*r = modfile.Require{}
}
}
return nil
}

func (f *File) DropReplace(oldPath, oldVers string) error {
for _, r := range f.Replace {
if r.Old.Path == oldPath && r.Old.Version == oldVers {
markLineAsRemoved(r.Syntax)
*r = modfile.Replace{}
}
}
return nil
}

// Validate validates gno.mod
func (f *File) Validate() error {
if f.Module == nil {
Expand Down Expand Up @@ -73,13 +176,8 @@ func (f *File) FetchDeps(path string, remote string, verbose bool) error {
return fmt.Errorf("writepackage: %w", err)
}

modFile := &File{
Module: &modfile.Module{
Mod: module.Version{
Path: mod.Path,
},
},
}
modFile := new(File)
modFile.AddModuleStmt(mod.Path)
for _, req := range requirements {
path := req[1 : len(req)-1] // trim leading and trailing `"`
if strings.HasSuffix(path, modFile.Module.Mod.Path) {
Expand All @@ -92,13 +190,7 @@ func (f *File) FetchDeps(path string, remote string, verbose bool) error {

if strings.HasPrefix(path, gnolang.ImportPrefix) {
path = strings.TrimPrefix(path, gnolang.ImportPrefix+"/examples/")
modFile.Require = append(modFile.Require, &modfile.Require{
Mod: module.Version{
Path: path,
Version: "v0.0.0", // TODO: Use latest?
},
Indirect: true,
})
modFile.AddNewRequire(path, "v0.0.0-latest", true)
}
}

Expand All @@ -112,7 +204,7 @@ func (f *File) FetchDeps(path string, remote string, verbose bool) error {
}
pkgPath := PackageDir(path, mod)
goModFilePath := filepath.Join(pkgPath, "go.mod")
err = goMod.WriteToPath(goModFilePath)
err = goMod.Write(goModFilePath)
if err != nil {
return err
}
Expand All @@ -121,42 +213,14 @@ func (f *File) FetchDeps(path string, remote string, verbose bool) error {
return nil
}

// WriteToPath writes file to the given absolute file path
// TODO: Find better way to do this. Try to use `modfile`
// package to manage this.
func (f *File) WriteToPath(absFilePath string) error {
if f.Module == nil {
return errors.New("writing go.mod: module not found")
}

data := "module " + f.Module.Mod.Path + "\n"

if f.Go != nil {
data += "\ngo " + f.Go.Version + "\n"
}

if f.Require != nil {
data += "\nrequire (" + "\n"
for _, req := range f.Require {
data += "\t" + req.Mod.Path + " " + req.Mod.Version + "\n"
}
data += ")\n"
}

if f.Replace != nil {
data += "\nreplace (" + "\n"
for _, rep := range f.Replace {
data += "\t" + rep.Old.Path + " " + rep.Old.Version +
" => " + rep.New.Path + "\n"
}
data += ")\n"
}

err := os.WriteFile(absFilePath, []byte(data), 0o644)
// writes file to the given absolute file path
func (f *File) Write(fname string) error {
f.Syntax.Cleanup()
data := modfile.Format(f.Syntax)
err := os.WriteFile(fname, data, 0o644)
if err != nil {
return fmt.Errorf("writefile %q: %w", absFilePath, err)
return fmt.Errorf("writefile %q: %w", fname, err)
}

return nil
}

Expand Down
37 changes: 13 additions & 24 deletions gnovm/pkg/gnomod/gnomod.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func GnoToGoMod(f File) (*File, error) {

if strings.HasPrefix(f.Module.Mod.Path, gnolang.GnoRealmPkgsPrefixBefore) ||
strings.HasPrefix(f.Module.Mod.Path, gnolang.GnoPackagePrefixBefore) {
f.Module.Mod.Path = gnolang.ImportPrefix + "/examples/" + f.Module.Mod.Path
f.AddModuleStmt(gnolang.ImportPrefix + "/examples/" + f.Module.Mod.Path)
}

for i := range f.Require {
Expand All @@ -113,20 +113,17 @@ func GnoToGoMod(f File) (*File, error) {
path := f.Require[i].Mod.Path
if strings.HasPrefix(f.Require[i].Mod.Path, gnolang.GnoRealmPkgsPrefixBefore) ||
strings.HasPrefix(f.Require[i].Mod.Path, gnolang.GnoPackagePrefixBefore) {
f.Require[i].Mod.Path = gnolang.ImportPrefix + "/examples/" + f.Require[i].Mod.Path
// Add dependency with a modified import path
f.AddRequire(gnolang.ImportPrefix+"/examples/"+f.Require[i].Mod.Path, f.Require[i].Mod.Version)
}

f.Replace = append(f.Replace, &modfile.Replace{
Old: module.Version{
Path: f.Require[i].Mod.Path,
Version: f.Require[i].Mod.Version,
},
New: module.Version{
Path: filepath.Join(gnoModPath, path),
},
})
f.AddReplace(f.Require[i].Mod.Path, f.Require[i].Mod.Version, filepath.Join(gnoModPath, path), "")
// Remove the old require since the new dependency was added above
f.DropRequire(f.Require[i].Mod.Path)
}

// Remove replacements that are not replaced by directories.
//
// Explanation:
// By this stage every replacement should be replace by dir.
// If not replaced by dir, remove it.
//
Expand All @@ -153,14 +150,11 @@ func GnoToGoMod(f File) (*File, error) {
// ```
//
// Remove `gno.land/p/demo/avl v1.2.3 => gno.land/p/demo/avl v3.2.1`.
repl := make([]*modfile.Replace, 0, len(f.Replace))
for _, r := range f.Replace {
if !modfile.IsDirectoryPath(r.New.Path) {
continue
f.DropReplace(r.Old.Path, r.Old.Version)
}
repl = append(repl, r)
}
f.Replace = repl

return &f, nil
}
Expand Down Expand Up @@ -215,14 +209,9 @@ func CreateGnoModFile(rootDir, modPath string) error {
return err
}

modFile := &File{
Module: &modfile.Module{
Mod: module.Version{
Path: modPath,
},
},
}
modFile.WriteToPath(filepath.Join(rootDir, "gno.mod"))
modfile := new(File)
modfile.AddModuleStmt(modPath)
modfile.Write(filepath.Join(rootDir, "gno.mod"))

return nil
}
Expand Down
Loading

0 comments on commit d865095

Please sign in to comment.