@@ -2,6 +2,7 @@ package main
22
33import (
44 "fmt"
5+ "golang.org/x/mod/semver"
56 "io/ioutil"
67 "log"
78 "net/url"
@@ -44,12 +45,27 @@ variable is 32.
4445 fmt .Fprintf (os .Stderr , "Usage:\n \n %s\n " , os .Args [0 ])
4546}
4647
48+ var goVersion = ""
49+
50+ // Returns the current Go version as returned by 'go version', e.g. go1.14.4
4751func getEnvGoVersion () string {
48- gover , err := exec .Command ("go" , "version" ).CombinedOutput ()
49- if err != nil {
50- log .Fatalf ("Unable to run the go command, is it installed?\n Error: %s" , err .Error ())
52+ if goVersion == "" {
53+ gover , err := exec .Command ("go" , "version" ).CombinedOutput ()
54+ if err != nil {
55+ log .Fatalf ("Unable to run the go command, is it installed?\n Error: %s" , err .Error ())
56+ }
57+ goVersion = strings .Fields (string (gover ))[2 ]
5158 }
52- return strings .Fields (string (gover ))[2 ]
59+ return goVersion
60+ }
61+
62+ // Returns the current Go version in semver format, e.g. v1.14.4
63+ func getEnvGoSemVer () string {
64+ goVersion := getEnvGoVersion ()
65+ if ! strings .HasPrefix (goVersion , "go" ) {
66+ log .Fatalf ("Expected 'go version' output of the form 'go1.2.3'; got '%s'" , goVersion )
67+ }
68+ return "v" + goVersion [2 :]
5369}
5470
5571func run (cmd * exec.Cmd ) bool {
@@ -141,6 +157,55 @@ const (
141157 Glide
142158)
143159
160+ // ModMode corresponds to the possible values of the -mod flag for the Go compiler
161+ type ModMode int
162+
163+ const (
164+ ModUnset ModMode = iota
165+ ModReadonly
166+ ModMod
167+ ModVendor
168+ )
169+
170+ func (m ModMode ) argsForGoVersion (version string ) []string {
171+ switch m {
172+ case ModUnset :
173+ return []string {}
174+ case ModReadonly :
175+ return []string {"-mod=readonly" }
176+ case ModMod :
177+ if ! semver .IsValid (version ) {
178+ log .Fatalf ("Invalid Go semver: '%s'" , version )
179+ }
180+ if semver .Compare (version , "v1.14" ) < 0 {
181+ return []string {} // -mod=mod is the default behaviour for go <= 1.13, and is not accepted as an argument
182+ } else {
183+ return []string {"-mod=mod" }
184+ }
185+ case ModVendor :
186+ return []string {"-mod=vendor" }
187+ }
188+ return nil
189+ }
190+
191+ // addVersionToMod add a go version directive, e.g. `go 1.14` to a `go.mod` file.
192+ func addVersionToMod (goMod []byte , version string ) bool {
193+ cmd := exec .Command ("go" , "mod" , "edit" , "-go=" + version )
194+ return run (cmd )
195+ }
196+
197+ // checkVendor tests to see whether a vendor directory is inconsistent according to the go frontend
198+ func checkVendor () bool {
199+ vendorCheckCmd := exec .Command ("go" , "list" , "-mod=vendor" , "./..." )
200+ outp , err := vendorCheckCmd .CombinedOutput ()
201+ if err != nil {
202+ badVendorRe := regexp .MustCompile (`(?m)^go: inconsistent vendoring in .*:$` )
203+ return ! badVendorRe .Match (outp )
204+ }
205+
206+ return true
207+ }
208+
144209func main () {
145210 if len (os .Args ) > 1 {
146211 usage ()
@@ -168,6 +233,7 @@ func main() {
168233 // determine how to install dependencies and whether a GOPATH needs to be set up before
169234 // extraction
170235 depMode := GoGetNoModules
236+ modMode := ModUnset
171237 needGopath := true
172238 if util .FileExists ("go.mod" ) {
173239 depMode = GoGetWithModules
@@ -183,7 +249,40 @@ func main() {
183249
184250 // if a vendor/modules.txt file exists, we assume that there are vendored Go dependencies, and
185251 // skip the dependency installation step and run the extractor with `-mod=vendor`
186- hasVendor := util .FileExists ("vendor/modules.txt" )
252+ if util .FileExists ("vendor/modules.txt" ) {
253+ modMode = ModVendor
254+ } else if util .DirExists ("vendor" ) {
255+ modMode = ModMod
256+ }
257+
258+ if modMode == ModVendor {
259+ // fix go vendor issues with go versions >= 1.14 when no go version is specified in the go.mod
260+ // if this is the case, and dependencies were vendored with an old go version (and therefore
261+ // do not contain a '## explicit' annotation, the go command will fail and refuse to do any
262+ // work
263+ //
264+ // we work around this by adding an explicit go version of 1.13, which is the last version
265+ // where this is not an issue
266+ if depMode == GoGetWithModules {
267+ goMod , err := ioutil .ReadFile ("go.mod" )
268+ if err != nil {
269+ log .Println ("Failed to read go.mod to check for missing Go version" )
270+ } else if versionRe := regexp .MustCompile (`(?m)^go[ \t\r]+[0-9]+\.[0-9]+$` ); ! versionRe .Match (goMod ) {
271+ // if the go.mod does not contain a version line
272+ modulesTxt , err := ioutil .ReadFile ("vendor/modules.txt" )
273+ if err != nil {
274+ log .Println ("Failed to read vendor/modules.txt to check for mismatched Go version" )
275+ } else if explicitRe := regexp .MustCompile ("(?m)^## explicit$" ); ! explicitRe .Match (modulesTxt ) {
276+ // and the modules.txt does not contain an explicit annotation
277+ log .Println ("Adding a version directive to the go.mod file as the modules.txt does not have explicit annotations" )
278+ if ! addVersionToMod (goMod , "1.13" ) {
279+ log .Println ("Failed to add a version to the go.mod file to fix explicitly required package bug; not using vendored dependencies" )
280+ modMode = ModMod
281+ }
282+ }
283+ }
284+ }
285+ }
187286
188287 // if `LGTM_INDEX_NEED_GOPATH` is set, it overrides the value for `needGopath` inferred above
189288 if needGopathOverride := os .Getenv ("LGTM_INDEX_NEED_GOPATH" ); needGopathOverride != "" {
@@ -291,7 +390,7 @@ func main() {
291390
292391 // check whether an explicit dependency installation command was provided
293392 inst := util .Getenv ("CODEQL_EXTRACTOR_GO_BUILD_COMMAND" , "LGTM_INDEX_BUILD_COMMAND" )
294- var install * exec. Cmd
393+ shouldInstallDependencies := false
295394 if inst == "" {
296395 // if there is a build file, run the corresponding build tool
297396 buildSucceeded := tryBuild ("Makefile" , "make" ) ||
@@ -302,54 +401,8 @@ func main() {
302401 tryBuild ("build.sh" , "./build.sh" )
303402
304403 if ! buildSucceeded {
305- if hasVendor {
306- log .Printf ("Skipping dependency installation because a Go vendor directory was found." )
307- } else {
308- // automatically determine command to install dependencies
309- if depMode == Dep {
310- // set up the dep cache if SEMMLE_CACHE is set
311- cacheDir := os .Getenv ("SEMMLE_CACHE" )
312- if cacheDir != "" {
313- depCacheDir := filepath .Join (cacheDir , "go" , "dep" )
314- log .Printf ("Attempting to create dep cache dir %s\n " , depCacheDir )
315- err := os .MkdirAll (depCacheDir , 0755 )
316- if err != nil {
317- log .Printf ("Failed to create dep cache directory: %s\n " , err .Error ())
318- } else {
319- log .Printf ("Setting dep cache directory to %s\n " , depCacheDir )
320- err = os .Setenv ("DEPCACHEDIR" , depCacheDir )
321- if err != nil {
322- log .Println ("Failed to set dep cache directory" )
323- } else {
324- err = os .Setenv ("DEPCACHEAGE" , "720h" ) // 30 days
325- if err != nil {
326- log .Println ("Failed to set dep cache age" )
327- }
328- }
329- }
330- }
331-
332- if util .FileExists ("Gopkg.lock" ) {
333- // if Gopkg.lock exists, don't update it and only vendor dependencies
334- install = exec .Command ("dep" , "ensure" , "-v" , "-vendor-only" )
335- } else {
336- install = exec .Command ("dep" , "ensure" , "-v" )
337- }
338- log .Println ("Installing dependencies using `dep ensure`." )
339- } else if depMode == Glide {
340- install = exec .Command ("glide" , "install" )
341- log .Println ("Installing dependencies using `glide install`" )
342- } else {
343- if depMode == GoGetWithModules {
344- // enable go modules if used
345- os .Setenv ("GO111MODULE" , "on" )
346- }
347-
348- // get dependencies
349- install = exec .Command ("go" , "get" , "-v" , "./..." )
350- log .Println ("Installing dependencies using `go get -v ./...`." )
351- }
352- }
404+ // Build failed; we'll try to install dependencies ourselves
405+ shouldInstallDependencies = true
353406 }
354407 } else {
355408 // write custom build commands into a script, then run it
@@ -380,12 +433,70 @@ func main() {
380433 log .Fatalf ("Unable to close temporary script holding custom build commands: %s\n " , err .Error ())
381434 }
382435 os .Chmod (script .Name (), 0700 )
383- install = exec .Command (script .Name ())
384436 log .Println ("Installing dependencies using custom build command." )
437+ run (exec .Command (script .Name ()))
385438 }
386439
387- if install != nil {
388- run (install )
440+ if modMode == ModVendor {
441+ // test if running `go` with -mod=vendor works, and if it doesn't, try to fallback to -mod=mod
442+ // or not set if the go version < 1.14. Note we check this post-build in case the build brings
443+ // the vendor directory up to date.
444+ if ! checkVendor () {
445+ modMode = ModMod
446+ log .Println ("The vendor directory is not consistent with the go.mod; not using vendored dependencies." )
447+ }
448+ }
449+
450+ if shouldInstallDependencies {
451+ if modMode == ModVendor {
452+ log .Printf ("Skipping dependency installation because a Go vendor directory was found." )
453+ } else {
454+ // automatically determine command to install dependencies
455+ var install * exec.Cmd
456+ if depMode == Dep {
457+ // set up the dep cache if SEMMLE_CACHE is set
458+ cacheDir := os .Getenv ("SEMMLE_CACHE" )
459+ if cacheDir != "" {
460+ depCacheDir := filepath .Join (cacheDir , "go" , "dep" )
461+ log .Printf ("Attempting to create dep cache dir %s\n " , depCacheDir )
462+ err := os .MkdirAll (depCacheDir , 0755 )
463+ if err != nil {
464+ log .Printf ("Failed to create dep cache directory: %s\n " , err .Error ())
465+ } else {
466+ log .Printf ("Setting dep cache directory to %s\n " , depCacheDir )
467+ err = os .Setenv ("DEPCACHEDIR" , depCacheDir )
468+ if err != nil {
469+ log .Println ("Failed to set dep cache directory" )
470+ } else {
471+ err = os .Setenv ("DEPCACHEAGE" , "720h" ) // 30 days
472+ if err != nil {
473+ log .Println ("Failed to set dep cache age" )
474+ }
475+ }
476+ }
477+ }
478+
479+ if util .FileExists ("Gopkg.lock" ) {
480+ // if Gopkg.lock exists, don't update it and only vendor dependencies
481+ install = exec .Command ("dep" , "ensure" , "-v" , "-vendor-only" )
482+ } else {
483+ install = exec .Command ("dep" , "ensure" , "-v" )
484+ }
485+ log .Println ("Installing dependencies using `dep ensure`." )
486+ } else if depMode == Glide {
487+ install = exec .Command ("glide" , "install" )
488+ log .Println ("Installing dependencies using `glide install`" )
489+ } else {
490+ if depMode == GoGetWithModules {
491+ // enable go modules if used
492+ os .Setenv ("GO111MODULE" , "on" )
493+ }
494+ // get dependencies
495+ install = exec .Command ("go" , "get" , "-v" , "./..." )
496+ log .Println ("Installing dependencies using `go get -v ./...`." )
497+ }
498+ run (install )
499+ }
389500 }
390501
391502 // extract
@@ -403,15 +514,14 @@ func main() {
403514 log .Fatalf ("Unable to determine current directory: %s\n " , err .Error ())
404515 }
405516
406- var cmd * exec.Cmd
407- // check for `vendor/modules.txt` and not just `vendor` in order to distinguish non-go vendor dirs
408- if depMode == GoGetWithModules && hasVendor {
409- log .Printf ("Running extractor command '%s -mod=vendor ./...' from directory '%s'.\n " , extractor , cwd )
410- cmd = exec .Command (extractor , "-mod=vendor" , "./..." )
411- } else {
412- log .Printf ("Running extractor command '%s ./...' from directory '%s'.\n " , extractor , cwd )
413- cmd = exec .Command (extractor , "./..." )
517+ extractorArgs := []string {}
518+ if depMode == GoGetWithModules {
519+ extractorArgs = append (extractorArgs , modMode .argsForGoVersion (getEnvGoSemVer ())... )
414520 }
521+ extractorArgs = append (extractorArgs , "./..." )
522+
523+ log .Printf ("Running extractor command '%s %v' from directory '%s'.\n " , extractor , extractorArgs , cwd )
524+ cmd := exec .Command (extractor , extractorArgs ... )
415525 cmd .Stdout = os .Stdout
416526 cmd .Stderr = os .Stderr
417527 err = cmd .Run ()
0 commit comments