Skip to content

Commit

Permalink
feat: add go mod tidy flag (#4)
Browse files Browse the repository at this point in the history
Signed-off-by: Hector Fernandez <[email protected]>
  • Loading branch information
hectorj2f authored Dec 20, 2023
1 parent 56fa50c commit bdb1ce1
Show file tree
Hide file tree
Showing 9 changed files with 295 additions and 71 deletions.
30 changes: 25 additions & 5 deletions cmd/gobump/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,46 @@ var rootCmd = &cobra.Command{
os.Exit(1)
}
packages := strings.Split(rootFlags.packages, ",")
pkgVersions := []*types.Package{}
pkgVersions := map[string]*types.Package{}
for _, pkg := range packages {
parts := strings.Split(pkg, "@")
if len(parts) != 2 {
fmt.Println("Usage: gobump -packages=<package@version>,...")
os.Exit(1)
}
pkgVersions = append(pkgVersions, &types.Package{
pkgVersions[parts[0]] = &types.Package{
Name: parts[0],
Version: parts[1],
})
}
}

var replaces []string
if len(rootFlags.replaces) != 0 {
replaces = strings.Split(rootFlags.replaces, " ")
for _, replace := range replaces {
parts := strings.Split(replace, "=")
if len(parts) != 2 {
fmt.Println("Usage: gobump -replaces=<oldpackage=newpackage@version>,...")
os.Exit(1)
}
// extract the new package name and version
rep := strings.Split(strings.TrimPrefix(replace, fmt.Sprintf("%s=", parts[0])), "@")
if len(rep) != 2 {
fmt.Println("Usage: gobump -replaces=<oldpackage=newpackage@version>,...")
os.Exit(1)
}
// Merge/Add the packages to replace reusing the initial list of packages
pkgVersions[rep[0]] = &types.Package{
OldName: parts[0],
Name: rep[0],
Version: rep[1],
Replace: true,
}
}
}

if _, err := update.DoUpdate(pkgVersions, replaces, rootFlags.modroot); err != nil {
fmt.Println("Error running update: ", err)
if _, err := update.DoUpdate(pkgVersions, rootFlags.modroot, rootFlags.tidy); err != nil {
fmt.Println("failed running update: ", err)
os.Exit(1)
}
},
Expand Down
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ=
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
sigs.k8s.io/release-utils v0.7.7 h1:JKDOvhCk6zW8ipEOkpTGDH/mW3TI+XqtPp16aaQ79FU=
Expand Down
65 changes: 65 additions & 0 deletions pkg/run/gorun.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package run

import (
"fmt"
"log"
"os/exec"
"strings"
)

func GoModTidy(modroot string) (string, error) {
log.Println("Running go mod tidy ...")
cmd := exec.Command("go", "mod", "tidy")
cmd.Dir = modroot
if bytes, err := cmd.CombinedOutput(); err != nil {
return strings.TrimSpace(string(bytes)), err
}
return "", nil
}

func GoGetModule(name, version, modroot string) (string, error) {
cmd := exec.Command("go", "get", fmt.Sprintf("%s@%s", name, version)) //nolint:gosec
cmd.Dir = modroot
if bytes, err := cmd.CombinedOutput(); err != nil {
return strings.TrimSpace(string(bytes)), err
}
return "", nil
}

func GoModEditReplaceModule(nameOld, nameNew, version, modroot string) (string, error) {
cmd := exec.Command("go", "mod", "edit", "-dropreplace", nameOld) //nolint:gosec
cmd.Dir = modroot
if bytes, err := cmd.CombinedOutput(); err != nil {
return strings.TrimSpace(string(bytes)), fmt.Errorf("Error running go command to drop replace modules: %w", err)
}

cmd = exec.Command("go", "mod", "edit", "-replace", fmt.Sprintf("%s=%s@%s", nameOld, nameNew, version)) //nolint:gosec
cmd.Dir = modroot
if bytes, err := cmd.CombinedOutput(); err != nil {
return strings.TrimSpace(string(bytes)), fmt.Errorf("Error running go command to replace modules: %w", err)
}
return "", nil
}

func GoModEditDropRequireModule(name, modroot string) (string, error) {
cmd := exec.Command("go", "mod", "edit", "-droprequire", name) //nolint:gosec
cmd.Dir = modroot
if bytes, err := cmd.CombinedOutput(); err != nil {
return strings.TrimSpace(string(bytes)), err
}

return "", nil
}

func GoModEditRequireModule(name, version, modroot string) (string, error) {
if bytes, err := GoModEditDropRequireModule(name, modroot); err != nil {
return strings.TrimSpace(string(bytes)), err
}

cmd := exec.Command("go", "mod", "edit", "-require", fmt.Sprintf("%s@%s", name, version)) //nolint:gosec
cmd.Dir = modroot
if bytes, err := cmd.CombinedOutput(); err != nil {
return strings.TrimSpace(string(bytes)), err
}
return "", nil
}
1 change: 1 addition & 0 deletions pkg/types/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types

type Package struct {
OldName string
Name string
Version string
Replace bool
Expand Down
10 changes: 10 additions & 0 deletions pkg/update/testdata/hello/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/puerco/hello

go 1.19

require github.com/sirupsen/logrus v1.8.0

require (
github.com/magefile/mage v1.10.0 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
)
13 changes: 13 additions & 0 deletions pkg/update/testdata/hello/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g=
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU=
github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
17 changes: 17 additions & 0 deletions pkg/update/testdata/hello/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
Copyright 2022 Puerco J. Cerdo
SPDX-License-Identifier: Apache-2.0
*/

package main

import (
"fmt"

"github.com/sirupsen/logrus"
)

func main() {
logrus.Info("preparing to say hi...")
fmt.Println("Hello World!")
}
143 changes: 91 additions & 52 deletions pkg/update/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,65 +2,123 @@ package update

import (
"fmt"
"log"
"os"
"os/exec"
"path"

"golang.org/x/mod/modfile"
"golang.org/x/mod/semver"

"github.com/chainguard-dev/gobump/pkg/run"
"github.com/chainguard-dev/gobump/pkg/types"
)

func DoUpdate(pkgVersions []*types.Package, replaces []string, modroot string) (*modfile.File, error) {
modpath := path.Join(modroot, "go.mod")
modFileContent, err := os.ReadFile(modpath)
func ParseGoModfile(path string) (*modfile.File, error) {
content, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("error reading go.mod: %w", err)
return nil, err
}

modFile, err := modfile.Parse("go.mod", modFileContent, nil)
mod, err := modfile.Parse("go.mod", content, nil)
if err != nil {
return nil, fmt.Errorf("error parsing go.mod: %w", err)
return nil, err
}

// Do replaces in the beginning
for _, replace := range replaces {
cmd := exec.Command("go", "mod", "edit", "-replace", replace)
cmd.Dir = modroot
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("error running go mod edit -replace %s: %w", replace, err)
return mod, nil
}

func checkPackageValues(pkgVersions map[string]*types.Package, modFile *modfile.File) error {
// Detect if the list of packages contain any replace statement for the package, if so we might drop that replace with a new one.
for _, replace := range modFile.Replace {
if replace != nil {
if _, ok := pkgVersions[replace.New.Path]; ok {
// pkg is already been replaced
pkgVersions[replace.New.Path].Replace = true
if semver.IsValid(pkgVersions[replace.New.Path].Version) {
if semver.Compare(replace.New.Version, pkgVersions[replace.New.Path].Version) > 0 {
return fmt.Errorf("package %s with version '%s' is already at version %s", replace.New.Path, replace.New.Version, pkgVersions[replace.New.Path].Version)
}
} else {
fmt.Printf("Requesting pin to %s.\n This is not a valid SemVer, so skipping version check.\n", pkgVersions[replace.New.Path].Version)
}
}
}
}
// Detect if the list of packages contain any require statement for the package, if so we might drop that require with a new one.
for _, require := range modFile.Require {
if require != nil {
if _, ok := pkgVersions[require.Mod.Path]; ok {
// pkg is already been required
pkgVersions[require.Mod.Path].Require = true
// Sometimes we request to pin to a specific commit.
// In that case, skip the compare check.
if semver.IsValid(pkgVersions[require.Mod.Path].Version) {
if semver.Compare(require.Mod.Version, pkgVersions[require.Mod.Path].Version) > 0 {
return fmt.Errorf("package %s with version '%s' is already at version %s", require.Mod.Path, require.Mod.Version, pkgVersions[require.Mod.Path].Version)
}
} else {
fmt.Printf("Requesting pin to %s.\n This is not a valid SemVer, so skipping version check.\n", pkgVersions[require.Mod.Path].Version)
}
}
}
}

for _, pkg := range pkgVersions {
currentVersion := getVersion(modFile, pkg.Name)
if currentVersion == "" {
return nil, fmt.Errorf("package %s not found in go.mod", pkg.Name)
return nil
}

func DoUpdate(pkgVersions map[string]*types.Package, modroot string, tidy bool) (*modfile.File, error) {
// Read the entire go.mod one more time into memory and check that all the version constraints are met.
modpath := path.Join(modroot, "go.mod")
modFile, err := ParseGoModfile(modpath)
if err != nil {
return nil, fmt.Errorf("unable to parse the go mod file with error: %v", err)
}

// Detect require/replace modules and validate the version values
err = checkPackageValues(pkgVersions, modFile)
if err != nil {
return nil, err
}

// Replace the packages first.
for k, pkg := range pkgVersions {
if pkg.Replace {
log.Printf("Update package: %s\n", k)
log.Println("Running go mod edit replace ...")
if output, err := run.GoModEditReplaceModule(pkg.Name, pkg.Name, pkg.Version, modroot); err != nil {
return nil, fmt.Errorf("failed to run 'go mod edit -replace': %v with output: %v", err, output)
}
}
// Sometimes we request to pin to a specific commit.
// In that case, skip the compare check.
if semver.IsValid(pkg.Version) {
if semver.Compare(currentVersion, pkg.Version) > 0 {
return nil, fmt.Errorf("package %s is already at version %s", pkg.Name, pkg.Version)
}
// Bump the require or new get packages.
for k, pkg := range pkgVersions {
// Skip the replace that have been updated above
if !pkg.Replace {
log.Printf("Update package: %s\n", k)
if pkg.Require {
log.Println("Running go mod edit -droprequire ...")
if output, err := run.GoModEditDropRequireModule(pkg.Name, modroot); err != nil {
return nil, fmt.Errorf("failed to run 'go mod edit -droprequire': %v with output: %v", err, output)
}
}
log.Println("Running go get ...")
if output, err := run.GoGetModule(pkg.Name, pkg.Version, modroot); err != nil {
return nil, fmt.Errorf("failed to run 'go get': %v with output: %v", err, output)
}
} else {
fmt.Printf("Requesting pin to %s\n. This is not a valid SemVer, so skipping version check.", pkg.Version)
}
}

if err := updatePackage(modFile, pkg.Name, pkg.Version, modroot); err != nil {
return nil, fmt.Errorf("error updating package: %w", err)
// Run go mod tidy
if tidy {
output, err := run.GoModTidy(modroot)
if err != nil {
return nil, fmt.Errorf("failed to run 'go mod tidy': %v with output: %v", err, output)
}
}

// Read the entire go.mod one more time into memory and check that all the version constraints are met.
newFileContent, err := os.ReadFile(modpath)
if err != nil {
return nil, fmt.Errorf("error reading go.mod: %w", err)
}
newModFile, err := modfile.Parse("go.mod", newFileContent, nil)
newModFile, err := ParseGoModfile(modpath)
if err != nil {
return nil, fmt.Errorf("error parsing go.mod: %w", err)
return nil, fmt.Errorf("unable to parse the go mod file with error: %v", err)
}
for _, pkg := range pkgVersions {
verStr := getVersion(newModFile, pkg.Name)
Expand All @@ -72,25 +130,6 @@ func DoUpdate(pkgVersions []*types.Package, replaces []string, modroot string) (
return newModFile, nil
}

func updatePackage(modFile *modfile.File, name, version, modroot string) error {
// Check if the package is replaced first
for _, replace := range modFile.Replace {
if replace.Old.Path == name {
cmd := exec.Command("go", "mod", "edit", "-replace", fmt.Sprintf("%s=%s@%s", replace.Old.Path, name, version)) //nolint:gosec
cmd.Dir = modroot
return cmd.Run()
}
}

// No replace, just update!
cmd := exec.Command("go", "get", fmt.Sprintf("%s@%s", name, version)) //nolint:gosec
cmd.Dir = modroot
if err := cmd.Run(); err != nil {
return err
}
return nil
}

func getVersion(modFile *modfile.File, packageName string) string {
// Handle package update, including 'replace' clause

Expand Down
Loading

0 comments on commit bdb1ce1

Please sign in to comment.