11package main
22
33import (
4+ "errors"
45 "io/ioutil"
56 "log"
67 "os"
78 "os/exec"
89 "path/filepath"
10+ "strings"
11+ "unicode"
912
1013 "github.com/go-sharp/color"
1114)
1215
1316type PackCmd struct {
14- Module []string `short:"m" long:"module" description:"Modules to pack (github.com/jessevdk/go-flags or github.com/jessevdk/go-flags@v1.4.0)"`
15- ModFile string `short:"g" long:"go-mod-file" description:"Pack all dependencies specified in go.mod file."`
16- Output string `short:"o" long:"out" description:"Output file name of the zip archive." default:"gop_dependencies.zip"`
17+ Module []string `short:"m" long:"module" description:"Modules to pack (github.com/jessevdk/go-flags or github.com/jessevdk/go-flags@v1.4.0)"`
18+ ModFile string `short:"g" long:"go-mod-file" description:"Pack all dependencies specified in go.mod file."`
19+ Output string `short:"o" long:"out" description:"Output file name of the zip archive." default:"gop_dependencies.zip"`
20+ DoTransitive bool `short:"t" long:"transitive" description:"Ensure all transitive dependencies are included."`
1721}
1822
1923// Execute will be called for the last active (sub)command. The
@@ -53,21 +57,20 @@ func (p *PackCmd) Execute(args []string) error {
5357
5458 for _ , m := range p .Module {
5559 verboseF ("adding module: %v\n " , color .BlueString (m ))
56- cmd := exec .Command (commonOpts .GoBinPath , "get" , m )
57- cmd .Dir = workDir
58- cmd .Env = append (os .Environ (), "GOMODCACHE=" + modCache )
59- if output , err := cmd .CombinedOutput (); err != nil {
60+ if output , err := getGoCommand (workDir , modCache , "get" , m ).CombinedOutput (); err != nil {
6061 log .Printf ("failed to add module: %v\n " , color .RedString (m ))
6162 verboseF ("%v: \n %s" , color .RedString ("error" ), output )
6263 }
6364 }
65+
66+ }
67+
68+ if p .DoTransitive {
69+ p .addTransitive (workDir , modCache )
6470 }
6571
6672 log .Println ("download all dependencies" )
67- cmd := exec .Command (commonOpts .GoBinPath , "mod" , "download" , "all" )
68- cmd .Dir = workDir
69- cmd .Env = append (os .Environ (), "GOMODCACHE=" + modCache )
70- if err := cmd .Run (); err != nil {
73+ if err := getGoCommand (workDir , modCache , "mod" , "download" , "all" ).Run (); err != nil {
7174 log .Fatalln ("failed to download dependencies:" , color .RedString (err .Error ()))
7275 }
7376
@@ -78,3 +81,75 @@ func (p *PackCmd) Execute(args []string) error {
7881 log .Println ("archive created:" , color .GreenString (p .Output ))
7982 return nil
8083}
84+
85+ func (p * PackCmd ) addTransitive (workDir , modCache string ) {
86+ hasMore := false
87+
88+ for {
89+ output , err := getGoCommand (workDir , modCache , "mod" , "graph" ).Output ()
90+ if err != nil {
91+ log .Println ("failed to add transitive dependencies:" , color .RedString (err .Error ()))
92+ return
93+ }
94+
95+ deps := strings .Split (string (output ), "\n " )
96+ if len (deps ) == 0 {
97+ return
98+ }
99+
100+ for _ , dep := range deps {
101+ mods := strings .Split (dep , " " )
102+ mod := strings .Trim (mods [len (mods )- 1 ], " " )
103+
104+ if mod == "" || folderExists (filepath .Join (modCache , moduleNameToCaseInsensitive (mod ))) {
105+ continue
106+ }
107+
108+ verboseF ("adding transitive module: %v\n " , color .BlueString (mod ))
109+ if output , err := getGoCommand (workDir , modCache , "get" , mod ).CombinedOutput (); err != nil {
110+ log .Printf ("failed to add module: %v\n " , color .RedString (mod ))
111+ verboseF ("%v: \n %s" , color .RedString ("error" ), output )
112+ }
113+ hasMore = true
114+ }
115+
116+ if hasMore {
117+ hasMore = false
118+ continue
119+ }
120+ break
121+ }
122+
123+ }
124+
125+ func getGoCommand (workDir , modCache string , args ... string ) * exec.Cmd {
126+ cmd := exec .Command (commonOpts .GoBinPath , args ... )
127+ cmd .Dir = workDir
128+ cmd .Env = append (os .Environ (), "GOMODCACHE=" + modCache )
129+
130+ return cmd
131+ }
132+
133+ func folderExists (name string ) bool {
134+ if _ , err := os .Stat (name ); errors .Is (err , os .ErrNotExist ) {
135+ return false
136+ }
137+
138+ return true
139+ }
140+
141+ func moduleNameToCaseInsensitive (name string ) string {
142+ name = filepath .ToSlash (name )
143+ var modName []rune
144+
145+ for _ , v := range name {
146+ if unicode .IsUpper (v ) {
147+ modName = append (modName , '!' , unicode .ToLower (v ))
148+ continue
149+ }
150+
151+ modName = append (modName , v )
152+ }
153+
154+ return string (modName )
155+ }
0 commit comments