Fix behaviour for single non-root go.mod

Also add telemetry so we can prioritise future work on the autobuilder.
This commit is contained in:
Owen Mansel-Chan
2023-06-27 15:45:30 +01:00
parent e43fd72fc1
commit a852173e22
12 changed files with 369 additions and 42 deletions

View File

@@ -10,6 +10,7 @@ import (
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"
"golang.org/x/mod/semver"
@@ -240,28 +241,109 @@ func getSourceDir() string {
return srcdir
}
func getDirs(paths []string) []string {
dirs := make([]string, len(paths))
for i, path := range paths {
dirs[i] = filepath.Dir(path)
}
return dirs
}
// Note this has the side effect of sorting `dirs`
func checkDirsNested(dirs []string) bool {
// the paths were generated by a depth-first search so I think they might
// be sorted, but we sort them just in case
sort.Strings(dirs)
for _, dir := range dirs {
if !strings.HasPrefix(dir, dirs[0]) {
return false
}
}
return true
}
func findGoModFiles(emitDiagnostics bool) (string, bool) {
goModPaths := util.FindAllFilesWithName(".", "go.mod", "vendor")
if len(goModPaths) == 0 {
// preserve current behaviour
return ".", false
}
goModDirs := getDirs(goModPaths)
if util.AnyGoFilesOutsideDirs(".", goModDirs...) {
if emitDiagnostics {
diagnostics.EmitGoFilesOutsideGoModules(goModPaths)
}
// preserve current behaviour
return ".", true
}
if len(goModPaths) > 1 {
// currently not supported
if emitDiagnostics {
if checkDirsNested(goModDirs) {
diagnostics.EmitMultipleGoModFoundNested(goModPaths)
} else {
diagnostics.EmitMultipleGoModFoundNotNested(goModPaths)
}
}
// preserve current behaviour
return ".", true
}
if emitDiagnostics {
if goModDirs[0] == "." {
diagnostics.EmitSingleRootGoModFound(goModPaths[0])
} else {
diagnostics.EmitSingleNonRootGoModFound(goModPaths[0])
}
}
return goModDirs[0], true
}
// Returns the appropriate DependencyInstallerMode for the current project
func getDepMode() DependencyInstallerMode {
if util.FileExists("go.mod") {
func getDepMode(emitDiagnostics bool) (DependencyInstallerMode, string) {
if util.FileExists("BUILD") {
// currently not supported
if emitDiagnostics {
diagnostics.EmitBazelBuildFileFound()
}
}
goWorkPaths := util.FindAllFilesWithName(".", "go.work", "vendor")
if len(goWorkPaths) > 0 {
// currently not supported
if emitDiagnostics {
diagnostics.EmitGoWorkFound(goWorkPaths)
}
}
baseDir, goModFound := findGoModFiles(emitDiagnostics)
if goModFound {
log.Println("Found go.mod, enabling go modules")
return GoGetWithModules
return GoGetWithModules, baseDir
}
if util.FileExists("Gopkg.toml") {
if emitDiagnostics {
diagnostics.EmitGopkgTomlFound()
}
log.Println("Found Gopkg.toml, using dep instead of go get")
return Dep
return Dep, "."
}
if util.FileExists("glide.yaml") {
if emitDiagnostics {
diagnostics.EmitGlideYamlFound()
}
log.Println("Found glide.yaml, using Glide instead of go get")
return Glide
return Glide, "."
}
return GoGetNoModules
return GoGetNoModules, "."
}
// Tries to open `go.mod` and read a go directive, returning the version and whether it was found.
func tryReadGoDirective(depMode DependencyInstallerMode) (string, bool) {
func tryReadGoDirective(depMode DependencyInstallerMode, baseDir string) (string, bool) {
if depMode == GoGetWithModules {
versionRe := regexp.MustCompile(`(?m)^go[ \t\r]+([0-9]+\.[0-9]+)$`)
goMod, err := os.ReadFile("go.mod")
goMod, err := os.ReadFile(filepath.Join(baseDir, "go.mod"))
if err != nil {
log.Println("Failed to read go.mod to check for missing Go version")
} else {
@@ -277,13 +359,13 @@ func tryReadGoDirective(depMode DependencyInstallerMode) (string, bool) {
}
// Returns the appropriate ModMode for the current project
func getModMode(depMode DependencyInstallerMode) ModMode {
func getModMode(depMode DependencyInstallerMode, baseDir string) ModMode {
if depMode == GoGetWithModules {
// if a vendor/modules.txt file exists, we assume that there are vendored Go dependencies, and
// skip the dependency installation step and run the extractor with `-mod=vendor`
if util.FileExists("vendor/modules.txt") {
if util.FileExists(baseDir + "/vendor/modules.txt") {
return ModVendor
} else if util.DirExists("vendor") {
} else if util.DirExists(baseDir + "/vendor") {
return ModMod
}
}
@@ -344,25 +426,29 @@ func getNeedGopath(depMode DependencyInstallerMode, importpath string) bool {
}
// Try to update `go.mod` and `go.sum` if the go version is >= 1.16.
func tryUpdateGoModAndGoSum(modMode ModMode, depMode DependencyInstallerMode) {
func tryUpdateGoModAndGoSum(modMode ModMode, depMode DependencyInstallerMode, baseDir string) {
// Go 1.16 and later won't automatically attempt to update go.mod / go.sum during package loading, so try to update them here:
if modMode != ModVendor && depMode == GoGetWithModules && semver.Compare(getEnvGoSemVer(), "v1.16") >= 0 {
// stat go.mod and go.sum
beforeGoModFileInfo, beforeGoModErr := os.Stat("go.mod")
goModPath := filepath.Join(baseDir, "go.mod")
beforeGoModFileInfo, beforeGoModErr := os.Stat(goModPath)
if beforeGoModErr != nil {
log.Println("Failed to stat go.mod before running `go mod tidy -e`")
}
beforeGoSumFileInfo, beforeGoSumErr := os.Stat("go.sum")
goSumPath := filepath.Join(baseDir, "go.sum")
beforeGoSumFileInfo, beforeGoSumErr := os.Stat(goSumPath)
// run `go mod tidy -e`
res := util.RunCmd(exec.Command("go", "mod", "tidy", "-e"))
cmd := exec.Command("go", "mod", "tidy", "-e")
cmd.Dir = baseDir
res := util.RunCmd(cmd)
if !res {
log.Println("Failed to run `go mod tidy -e`")
} else {
if beforeGoModFileInfo != nil {
afterGoModFileInfo, afterGoModErr := os.Stat("go.mod")
afterGoModFileInfo, afterGoModErr := os.Stat(goModPath)
if afterGoModErr != nil {
log.Println("Failed to stat go.mod after running `go mod tidy -e`")
} else if afterGoModFileInfo.ModTime().After(beforeGoModFileInfo.ModTime()) {
@@ -371,7 +457,7 @@ func tryUpdateGoModAndGoSum(modMode ModMode, depMode DependencyInstallerMode) {
}
}
afterGoSumFileInfo, afterGoSumErr := os.Stat("go.sum")
afterGoSumFileInfo, afterGoSumErr := os.Stat(goSumPath)
if afterGoSumErr != nil {
log.Println("Failed to stat go.sum after running `go mod tidy -e`")
} else {
@@ -560,7 +646,7 @@ func buildWithCustomCommands(inst string) {
}
// Install dependencies using the given dependency installer mode.
func installDependencies(depMode DependencyInstallerMode) {
func installDependencies(depMode DependencyInstallerMode, baseDir string) {
// automatically determine command to install dependencies
var install *exec.Cmd
if depMode == Dep {
@@ -606,31 +692,28 @@ func installDependencies(depMode DependencyInstallerMode) {
// get dependencies
install = exec.Command("go", "get", "-v", "./...")
log.Println("Installing dependencies using `go get -v ./...`.")
install.Dir = baseDir
log.Printf("Installing dependencies using `go get -v ./...` in `%s`.\n", baseDir)
}
util.RunCmd(install)
}
// Run the extractor.
func extract(depMode DependencyInstallerMode, modMode ModMode) {
func extract(depMode DependencyInstallerMode, modMode ModMode, baseDir string) {
extractor, err := util.GetExtractorPath()
if err != nil {
log.Fatalf("Could not determine path of extractor: %v.\n", err)
}
cwd, err := os.Getwd()
if err != nil {
log.Fatalf("Unable to determine current directory: %s\n", err.Error())
}
extractorArgs := []string{}
if depMode == GoGetWithModules {
extractorArgs = append(extractorArgs, modMode.argsForGoVersion(getEnvGoSemVer())...)
}
extractorArgs = append(extractorArgs, "./...")
log.Printf("Running extractor command '%s %v' from directory '%s'.\n", extractor, extractorArgs, cwd)
log.Printf("Running extractor command '%s %v' from directory '%s'.\n", extractor, extractorArgs, baseDir)
cmd := exec.Command(extractor, extractorArgs...)
cmd.Dir = baseDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
@@ -650,21 +733,21 @@ func installDependenciesAndBuild() {
// determine how to install dependencies and whether a GOPATH needs to be set up before
// extraction
depMode := getDepMode()
depMode, baseDir := getDepMode(true)
if _, present := os.LookupEnv("GO111MODULE"); !present {
os.Setenv("GO111MODULE", "auto")
}
goModVersion, goModVersionFound := tryReadGoDirective(depMode)
goModVersion, goModVersionFound := tryReadGoDirective(depMode, baseDir)
if semver.Compare("v"+goModVersion, getEnvGoSemVer()) >= 0 {
diagnostics.EmitNewerGoVersionNeeded()
}
modMode := getModMode(depMode)
modMode := getModMode(depMode, baseDir)
modMode = fixGoVendorIssues(modMode, depMode, goModVersionFound)
tryUpdateGoModAndGoSum(modMode, depMode)
tryUpdateGoModAndGoSum(modMode, depMode, baseDir)
importpath := getImportPath()
needGopath := getNeedGopath(depMode, importpath)
@@ -707,11 +790,11 @@ func installDependenciesAndBuild() {
if modMode == ModVendor {
log.Printf("Skipping dependency installation because a Go vendor directory was found.")
} else {
installDependencies(depMode)
installDependencies(depMode, baseDir)
}
}
extract(depMode, modMode)
extract(depMode, modMode, baseDir)
}
const minGoVersion = "1.11"
@@ -976,8 +1059,8 @@ func isGoInstalled() bool {
// Get the version of Go to install and output it to stdout as json.
func identifyEnvironment() {
var v versionInfo
depMode := getDepMode()
v.goModVersion, v.goModVersionFound = tryReadGoDirective(depMode)
depMode, baseDir := getDepMode(false)
v.goModVersion, v.goModVersionFound = tryReadGoDirective(depMode, baseDir)
v.goEnvVersionFound = isGoInstalled()
if v.goEnvVersionFound {