Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit 1743dae

Browse files
author
Sauyon Lee
authored
Merge pull request #312 from smowton/smowton/autobuilder-fixes-fixed-further
Autobuilder: always check the vendor directory works and if go.mod exists
2 parents 34d5e97 + 4d08437 commit 1743dae

File tree

2 files changed

+184
-65
lines changed

2 files changed

+184
-65
lines changed

extractor/cli/go-autobuilder/go-autobuilder.go

Lines changed: 175 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
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
4751
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?\nError: %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?\nError: %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

5571
func 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+
144209
func 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()

extractor/util/util.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,12 @@ func FileExists(filename string) bool {
9191
}
9292
return err == nil && !info.IsDir()
9393
}
94+
95+
// DirExists tests whether `filename` exists and is a directory.
96+
func DirExists(filename string) bool {
97+
info, err := os.Stat(filename)
98+
if err != nil && !os.IsNotExist(err) {
99+
log.Printf("Unable to stat %s: %s\n", filename, err.Error())
100+
}
101+
return err == nil && info.IsDir()
102+
}

0 commit comments

Comments
 (0)