github.com/rsc/tmp@v0.0.0-20240517235954-6deaab19748b/diffmod/diffmod.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Diffmod identifies differences in the dependencies
     6  // implied by each of a set of go.mod files.
     7  //
     8  // Usage:
     9  //
    10  //	diffmod go.mod other/go.mod ...
    11  //
    12  // If the version of any dependency used by one go.mod file
    13  // is different from the version used by another go.mod file
    14  // (ignoring those that don't use the dependency at all),
    15  // then diffmod prints a stanza of the form:
    16  //
    17  //	module/path
    18  //		go.mod: version used in go.mod
    19  //		other/go.mod: version used in other/go.mod
    20  //		...
    21  //
    22  // Diffmod prints one stanza for each dependency that differs
    23  // across the set of go.mod files.
    24  //
    25  package main
    26  
    27  import (
    28  	"bytes"
    29  	"encoding/json"
    30  	"flag"
    31  	"fmt"
    32  	"io"
    33  	"log"
    34  	"os"
    35  	"os/exec"
    36  	"path/filepath"
    37  	"sort"
    38  )
    39  
    40  func usage() {
    41  	fmt.Fprintf(os.Stderr, "usage: diffmod go.mod other/go.mod...\n")
    42  	os.Exit(2)
    43  }
    44  
    45  var deps = make(map[string]map[string]string) // go.mod -> module path -> Module
    46  
    47  type Module struct {
    48  	Path    string
    49  	Main    bool
    50  	Version string
    51  	Replace Replacement
    52  }
    53  
    54  func (m Module) VersionString() string {
    55  	var s string
    56  	if m.Version != "" {
    57  		s += " " + m.Version
    58  	}
    59  	if m.Replace.Dir != "" {
    60  		s += " => " + m.Replace.Dir
    61  	} else if m.Replace.Path != "" {
    62  		s += " => " + m.Replace.Path + " " + m.Replace.Version
    63  	}
    64  	if s == "" {
    65  		return ""
    66  	}
    67  	return s[1:]
    68  }
    69  
    70  type Replacement struct {
    71  	Path    string
    72  	Version string
    73  	Dir     string
    74  }
    75  
    76  func main() {
    77  	log.SetPrefix("syncmod: ")
    78  	log.SetFlags(0)
    79  	flag.Usage = usage
    80  	flag.Parse()
    81  	gomods := flag.Args()
    82  	if len(gomods) < 2 {
    83  		usage()
    84  	}
    85  
    86  	havePath := make(map[string]bool)
    87  	var paths []string
    88  
    89  	for _, gomod := range flag.Args() {
    90  		if filepath.Base(gomod) != "go.mod" {
    91  			log.Fatalf("not a go.mod: %s", gomod)
    92  		}
    93  		deps[gomod] = make(map[string]string)
    94  		cmd := exec.Command("vgo", "list", "-m", "-json", "all")
    95  		cmd.Dir = filepath.Dir(gomod)
    96  		out, err := cmd.Output()
    97  		if err != nil {
    98  			log.Fatal(err)
    99  		}
   100  		dec := json.NewDecoder(bytes.NewReader(out))
   101  		for {
   102  			var m Module
   103  			err := dec.Decode(&m)
   104  			if err == io.EOF {
   105  				break
   106  			}
   107  			if err != nil {
   108  				log.Fatalf("%s: parsing json: %v", gomod, err)
   109  			}
   110  			if m.Main {
   111  				continue
   112  			}
   113  			if !havePath[m.Path] {
   114  				havePath[m.Path] = true
   115  				paths = append(paths, m.Path)
   116  			}
   117  			deps[gomod][m.Path] = m.VersionString()
   118  		}
   119  	}
   120  
   121  	sort.Strings(paths)
   122  	for _, path := range paths {
   123  		ok := true
   124  		var m1 string
   125  		for _, gomod := range gomods {
   126  			m := deps[gomod][path]
   127  			if m1 == "" {
   128  				m1 = m
   129  			}
   130  			if m != "" && m != m1 {
   131  				ok = false
   132  			}
   133  		}
   134  		if ok {
   135  			continue
   136  		}
   137  		fmt.Printf("%s\n", path)
   138  		for _, gomod := range gomods {
   139  			m := deps[gomod][path]
   140  			if m != "" {
   141  				fmt.Printf("\t%s: %s\n", gomod, m)
   142  			}
   143  		}
   144  	}
   145  }