@@ -2,6 +2,7 @@ package main
2
2
3
3
import (
4
4
"fmt"
5
+ "golang.org/x/mod/semver"
5
6
"io/ioutil"
6
7
"log"
7
8
"net/url"
@@ -44,12 +45,27 @@ variable is 32.
44
45
fmt .Fprintf (os .Stderr , "Usage:\n \n %s\n " , os .Args [0 ])
45
46
}
46
47
48
+ var goVersion = ""
49
+
50
+ // Returns the current Go version as returned by 'go version', e.g. go1.14.4
47
51
func 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 ]
51
58
}
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 :]
53
69
}
54
70
55
71
func run (cmd * exec.Cmd ) bool {
@@ -141,6 +157,55 @@ const (
141
157
Glide
142
158
)
143
159
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
+
144
209
func main () {
145
210
if len (os .Args ) > 1 {
146
211
usage ()
@@ -168,6 +233,7 @@ func main() {
168
233
// determine how to install dependencies and whether a GOPATH needs to be set up before
169
234
// extraction
170
235
depMode := GoGetNoModules
236
+ modMode := ModUnset
171
237
needGopath := true
172
238
if util .FileExists ("go.mod" ) {
173
239
depMode = GoGetWithModules
@@ -183,7 +249,40 @@ func main() {
183
249
184
250
// if a vendor/modules.txt file exists, we assume that there are vendored Go dependencies, and
185
251
// 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
+ }
187
286
188
287
// if `LGTM_INDEX_NEED_GOPATH` is set, it overrides the value for `needGopath` inferred above
189
288
if needGopathOverride := os .Getenv ("LGTM_INDEX_NEED_GOPATH" ); needGopathOverride != "" {
@@ -291,7 +390,7 @@ func main() {
291
390
292
391
// check whether an explicit dependency installation command was provided
293
392
inst := util .Getenv ("CODEQL_EXTRACTOR_GO_BUILD_COMMAND" , "LGTM_INDEX_BUILD_COMMAND" )
294
- var install * exec. Cmd
393
+ shouldInstallDependencies := false
295
394
if inst == "" {
296
395
// if there is a build file, run the corresponding build tool
297
396
buildSucceeded := tryBuild ("Makefile" , "make" ) ||
@@ -302,54 +401,8 @@ func main() {
302
401
tryBuild ("build.sh" , "./build.sh" )
303
402
304
403
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
353
406
}
354
407
} else {
355
408
// write custom build commands into a script, then run it
@@ -380,12 +433,70 @@ func main() {
380
433
log .Fatalf ("Unable to close temporary script holding custom build commands: %s\n " , err .Error ())
381
434
}
382
435
os .Chmod (script .Name (), 0700 )
383
- install = exec .Command (script .Name ())
384
436
log .Println ("Installing dependencies using custom build command." )
437
+ run (exec .Command (script .Name ()))
385
438
}
386
439
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
+ }
389
500
}
390
501
391
502
// extract
@@ -403,15 +514,14 @@ func main() {
403
514
log .Fatalf ("Unable to determine current directory: %s\n " , err .Error ())
404
515
}
405
516
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 ())... )
414
520
}
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 ... )
415
525
cmd .Stdout = os .Stdout
416
526
cmd .Stderr = os .Stderr
417
527
err = cmd .Run ()
0 commit comments