├── README.md ├── go.mod ├── go.sum └── main.go /README.md: -------------------------------------------------------------------------------- 1 | # gomodmerge 2 | 3 | Sometimes we'll have a project that uses Go modules but one of its dependencies do not. Updating your project when its dependencies change can be awkward because there's no easy way of merging dependency requirements from non-module build systems such as dep. 4 | 5 | The `gomodmerge` tool makes it possible to do that. In your non-module dependency, run `go mod init` to create a module file for that dependency. Then in your project, run `gomodmerge $dependency/go.mod` to merge any versions that are newer. 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rogpeppe/gomodmerge 2 | 3 | require ( 4 | github.com/rogpeppe/go-internal v1.0.0-alpha 5 | gopkg.in/errgo.v2 v2.1.0 6 | ) 7 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 2 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 3 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 4 | github.com/rogpeppe/go-internal v0.0.8 h1:AXvcOSBIr8szCL4Ml897LRBY33zaEJSpmdt3YEDTMmg= 5 | github.com/rogpeppe/go-internal v1.0.0-alpha h1:zJtNjia0DW6sKVGzv5i+3TwGRJEJmaPnU9F12IRf/gI= 6 | github.com/rogpeppe/go-internal v1.0.0-alpha/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 7 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 8 | gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= 9 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 10 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "os" 11 | "os/exec" 12 | "path/filepath" 13 | "sort" 14 | 15 | "github.com/rogpeppe/go-internal/semver" 16 | "gopkg.in/errgo.v2/fmt/errors" 17 | ) 18 | 19 | func main() { 20 | flag.Usage = func() { 21 | fmt.Fprint(os.Stderr, `gomodmerge gomodfile 22 | 23 | Update the local module's dependencies by merging them 24 | from the dependencies implied by the argument go.mod file. 25 | 26 | Dependencies that are older than the current module's dependencies 27 | will be ignored. 28 | `) 29 | os.Exit(2) 30 | } 31 | flag.Parse() 32 | if flag.NArg() != 1 { 33 | flag.Usage() 34 | } 35 | if err := mergeMod(flag.Arg(0)); err != nil { 36 | fmt.Fprintf(os.Stderr, "%v (%s)\n", err, errors.Details(err)) 37 | os.Exit(1) 38 | } 39 | } 40 | 41 | func mergeMod(modfile string) error { 42 | localVersions, err := moduleVersions("") 43 | if err != nil { 44 | return errors.Wrap(err) 45 | } 46 | dir, err := ioutil.TempDir("", "") 47 | if err != nil { 48 | return errors.Wrap(err) 49 | } 50 | defer os.RemoveAll(dir) 51 | goMod, err := ioutil.ReadFile(modfile) 52 | if err != nil { 53 | return errors.Wrap(err) 54 | } 55 | if err := ioutil.WriteFile(filepath.Join(dir, "go.mod"), goMod, 0666); err != nil { 56 | return errors.Wrap(err) 57 | } 58 | otherVersions, err := moduleVersions(dir) 59 | if err != nil { 60 | return errors.Wrap(err) 61 | } 62 | updates := make(map[string]string) 63 | for mod, version := range otherVersions { 64 | localVersion, ok := localVersions[mod] 65 | if !ok || semver.Compare(version, localVersion) > 0 { 66 | updates[mod] = version 67 | } 68 | } 69 | if len(updates) == 0 { 70 | fmt.Println("no updates required") 71 | return nil 72 | } 73 | updateMods := make([]string, 0, len(updates)) 74 | for mod := range updates { 75 | updateMods = append(updateMods, mod) 76 | } 77 | sort.Strings(updateMods) 78 | editArgs := []string{"mod", "edit"} 79 | for _, mod := range updateMods { 80 | editArgs = append(editArgs, "-require="+mod+"@"+updates[mod]) 81 | } 82 | c := exec.Command("go", editArgs...) 83 | c.Stderr = os.Stderr 84 | if err := c.Run(); err != nil { 85 | return errors.Wrap(err) 86 | } 87 | for _, mod := range updateMods { 88 | fmt.Printf("%s %s\n", mod, updates[mod]) 89 | } 90 | return nil 91 | } 92 | 93 | type goMod struct { 94 | Path string 95 | Version string 96 | } 97 | 98 | func moduleVersions(dir string) (map[string]string, error) { 99 | var out bytes.Buffer 100 | c := exec.Command("go", "list", "-m", "-json", "all") 101 | c.Dir = dir 102 | c.Stderr = os.Stderr 103 | c.Stdout = &out 104 | if err := c.Run(); err != nil { 105 | return nil, errors.Wrap(err) 106 | } 107 | dec := json.NewDecoder(&out) 108 | mods := make(map[string]string) 109 | for { 110 | var m goMod 111 | if err := dec.Decode(&m); err != nil { 112 | if err == io.EOF { 113 | break 114 | } 115 | return nil, errors.Wrap(err) 116 | } 117 | if m.Version != "" { 118 | mods[m.Path] = m.Version 119 | } 120 | } 121 | return mods, nil 122 | } 123 | --------------------------------------------------------------------------------