forked from gnolang/gno
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Norman Meier <norman@berty.tech>
- Loading branch information
Showing
1 changed file
with
205 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/peterbourgon/ff/v3" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
func main() { | ||
fs := flag.NewFlagSet("bumpkg", flag.ContinueOnError) | ||
var ( | ||
packagesRootFlag = fs.String("root", "examples", "root directory of packages") | ||
targetPkgPathFlag = fs.String("target", "", "target package path") | ||
) | ||
|
||
err := ff.Parse(fs, os.Args[1:]) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
if targetPkgPathFlag == nil || *targetPkgPathFlag == "" { | ||
panic("target package path is required") | ||
} | ||
targetPkgPath := *targetPkgPathFlag | ||
|
||
if packagesRootFlag == nil || *packagesRootFlag == "" { | ||
panic("packages root is required") | ||
} | ||
packagesRoot := *packagesRootFlag | ||
|
||
targetPackageFSPath := filepath.Join(packagesRoot, targetPkgPath) | ||
targetPackageGnoModPath := filepath.Join(targetPackageFSPath, "gno.mod") | ||
fmt.Println("Target package:\n\n" + targetPkgPath) | ||
|
||
allGnoMods := map[string]struct{}{} | ||
if err := filepath.Walk(packagesRoot, func(path string, info os.FileInfo, err error) error { | ||
if err != nil { | ||
fmt.Println("error during walk:", err) | ||
return nil | ||
} | ||
if info.IsDir() || info.Name() != "gno.mod" { | ||
return nil | ||
} | ||
|
||
allGnoMods[path] = struct{}{} | ||
|
||
return nil | ||
}); err != nil { | ||
panic(errors.Wrap(err, "failed to walk packages")) | ||
} | ||
if _, ok := allGnoMods[targetPackageGnoModPath]; !ok { | ||
panic("target package not found") | ||
} | ||
|
||
requires := map[string][]string{} | ||
requiredBy := map[string][]string{} | ||
for gnoModPath := range allGnoMods { | ||
deps, err := gnoModDeps(gnoModPath) | ||
if err != nil { | ||
panic(errors.Wrap(err, "failed to parse "+gnoModPath)) | ||
} | ||
|
||
pkgPath := strings.TrimSuffix(strings.TrimPrefix(gnoModPath, packagesRoot+"/"), "/gno.mod") // FIXME: brittle, not cross-platform | ||
|
||
requires[pkgPath] = deps | ||
for _, dep := range deps { | ||
requiredBy[dep] = append(requiredBy[dep], pkgPath) | ||
} | ||
} | ||
|
||
upgrades := map[string]string{} | ||
|
||
roots := []string{targetPkgPath} | ||
seen := map[string]struct{}{} | ||
for len(roots) > 0 { | ||
root := roots[0] | ||
roots = roots[1:] | ||
if _, ok := seen[root]; ok { | ||
continue | ||
} | ||
seen[root] = struct{}{} | ||
roots = append(roots, requiredBy[root]...) | ||
|
||
vR := regexp.MustCompile(`_v(\d+)$`) | ||
submatches := vR.FindAllStringSubmatch(root, -1) | ||
version := uint64(1) | ||
unversioned := true | ||
if len(submatches) >= 1 && len(submatches[0]) >= 2 { | ||
vMatch := submatches[0][1] | ||
v, err := strconv.ParseUint(vMatch, 10, 64) | ||
if err != nil { | ||
panic("failed to parse version") | ||
} | ||
version = v | ||
unversioned = false | ||
} | ||
nextVersion := version + 1 | ||
basePkgPath := root | ||
if !unversioned { | ||
basePkgPath = strings.TrimSuffix(basePkgPath, "_v"+strconv.FormatUint(version, 10)) | ||
} | ||
newPkgPath := fmt.Sprintf("%s_v%d", basePkgPath, nextVersion) | ||
upgrades[root] = newPkgPath | ||
} | ||
|
||
fmt.Print("\nBumping:\n\n") | ||
|
||
for oldPkgPath, newPkgPath := range upgrades { | ||
fmt.Println(oldPkgPath, "->", newPkgPath) | ||
|
||
r := regexp.MustCompile(oldPkgPath) | ||
|
||
// change module name in gno.mod | ||
gnoModPath := filepath.Join(packagesRoot, oldPkgPath, "gno.mod") | ||
data, err := os.ReadFile(gnoModPath) | ||
if err != nil { | ||
panic(errors.Wrap(err, "failed to read "+gnoModPath)) | ||
} | ||
edited := r.ReplaceAll(data, []byte(newPkgPath)) | ||
if err := os.WriteFile(gnoModPath, edited, 0644); err != nil { | ||
panic(errors.Wrap(err, "failed to write "+gnoModPath)) | ||
} | ||
|
||
for _, child := range requiredBy[oldPkgPath] { | ||
// change import paths in dependent .gno files | ||
if err := filepath.Walk(filepath.Join(packagesRoot, child), func(path string, info os.FileInfo, err error) error { | ||
if err != nil { | ||
fmt.Println("error during walk:", err) | ||
return nil | ||
} | ||
|
||
if info.IsDir() || !strings.HasSuffix(path, ".gno") { | ||
return nil | ||
} | ||
|
||
// replace oldPkgPath with newPkgPath in file | ||
data, err := os.ReadFile(path) | ||
if err != nil { | ||
return errors.Wrap(err, "failed to read "+path) | ||
} | ||
edited := r.ReplaceAll(data, []byte(newPkgPath)) | ||
if err := os.WriteFile(path, edited, 0644); err != nil { | ||
return errors.Wrap(err, "failed to write "+path) | ||
} | ||
|
||
return nil | ||
}); err != nil { | ||
panic(errors.Wrap(err, "failed to walk packages")) | ||
} | ||
|
||
// change import paths in dependent gno.mod files | ||
gnoModPath := filepath.Join(packagesRoot, child, "gno.mod") | ||
data, err := os.ReadFile(gnoModPath) | ||
if err != nil { | ||
panic(errors.Wrap(err, "failed to read "+gnoModPath)) | ||
} | ||
edited := r.ReplaceAll(data, []byte(newPkgPath)) | ||
if err := os.WriteFile(gnoModPath, edited, 0644); err != nil { | ||
panic(errors.Wrap(err, "failed to write "+gnoModPath)) | ||
} | ||
} | ||
} | ||
|
||
for oldPkgPath, newPkgPath := range upgrades { | ||
// rename directory | ||
if err := os.Rename(filepath.Join(packagesRoot, oldPkgPath), filepath.Join(packagesRoot, newPkgPath)); err != nil { | ||
panic(errors.Wrap(err, "failed to rename "+oldPkgPath)) | ||
} | ||
} | ||
} | ||
|
||
func gnoModDeps(gnoModPath string) ([]string, error) { | ||
data, err := os.ReadFile(gnoModPath) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to read "+gnoModPath) | ||
} | ||
r := regexp.MustCompile(`(?s)require.+?\((.+?)\)`) | ||
submatches := r.FindAllStringSubmatch(string(data), -1) | ||
if len(submatches) < 1 || len(submatches[0]) < 2 { | ||
return nil, nil | ||
} | ||
lines := strings.Split(submatches[0][1], "\n") | ||
depEntries := []string{} | ||
for _, line := range lines { | ||
line = strings.TrimSpace(line) | ||
if line == "" { | ||
continue | ||
} | ||
depR := regexp.MustCompile(`"(.+)"`) | ||
submatches := depR.FindAllStringSubmatch(line, -1) | ||
if len(submatches) < 1 || len(submatches[0]) < 2 { | ||
return nil, fmt.Errorf("failed to parse dep line: %q", line) | ||
} | ||
depEntry := submatches[0][1] | ||
depEntries = append(depEntries, depEntry) | ||
} | ||
return depEntries, nil | ||
} |