Merge pull request #15361 from github/mbg/go/legacy-gopath-mode-deprecated

Go: Update autobuilder to deal with the upcoming deprecation of the legacy GOPATH mode
This commit is contained in:
Michael B. Gale
2024-03-04 10:23:37 +00:00
committed by GitHub
51 changed files with 1059 additions and 332 deletions

View File

@@ -70,12 +70,33 @@ func tryBuild(cmd string, args ...string) bool {
return res && (!CheckExtracted || checkExtractorRun())
}
// Autobuild attempts to detect build system and run the corresponding command.
func Autobuild() bool {
return tryBuildIfExists("Makefile", "make") ||
tryBuildIfExists("makefile", "make") ||
tryBuildIfExists("GNUmakefile", "make") ||
tryBuildIfExists("build.ninja", "ninja") ||
tryBuildIfExists("build", "./build") ||
tryBuildIfExists("build.sh", "./build.sh")
// If a project is accompanied by a build script (such as a makefile), then we try executing such
// build scripts to build the project. This type represents pairs of script names to check for
// and the names of corresponding build tools to invoke if those scripts exist.
type BuildScript struct {
Tool string // The name of the command to execute if the build script exists
Filename string // The name of the build script to check for
}
// An array of build scripts to check for and corresponding commands that we can execute
// if they exist.
var BuildScripts = []BuildScript{
{Tool: "make", Filename: "Makefile"},
{Tool: "make", Filename: "makefile"},
{Tool: "make", Filename: "GNUmakefile"},
{Tool: "ninja", Filename: "build.ninja"},
{Tool: "./build", Filename: "build"},
{Tool: "./build.sh", Filename: "build.sh"},
}
// Autobuild attempts to detect build systems based on the presence of build scripts from the
// list in `BuildScripts` and run the corresponding command. This may invoke zero or more
// build scripts in the order given by `BuildScripts`.
func Autobuild() bool {
for _, script := range BuildScripts {
if tryBuildIfExists(script.Filename, script.Tool) {
return true
}
}
return false
}

View File

@@ -267,15 +267,22 @@ func outputEnvironmentJson(version string) {
// Get the version of Go to install and output it to stdout as json.
func IdentifyEnvironment() {
var v versionInfo
buildInfo := project.GetBuildInfo(false)
goVersionInfo := project.TryReadGoDirective(buildInfo)
v.goModVersion, v.goModVersionFound = goVersionInfo.Version, goVersionInfo.Found
workspaces := project.GetWorkspaceInfo(false)
// Remove temporary extractor files (e.g. auto-generated go.mod files) when we are done
defer project.RemoveTemporaryExtractorFiles()
// Find the greatest Go version required by any of the workspaces.
greatestGoVersion := project.RequiredGoVersion(&workspaces)
v.goModVersion, v.goModVersionFound = greatestGoVersion.Version, greatestGoVersion.Found
// Find which, if any, version of Go is installed on the system already.
v.goEnvVersionFound = toolchain.IsInstalled()
if v.goEnvVersionFound {
v.goEnvVersion = toolchain.GetEnvGoVersion()[2:]
}
// Determine which version of Go we should recommend to install.
msg, versionToInstall := getVersionToInstall(v)
log.Println(msg)

View File

@@ -56,23 +56,6 @@ Build behavior:
fmt.Fprintf(os.Stderr, "Usage:\n\n %s\n", os.Args[0])
}
// Returns the current Go version in semver format, e.g. v1.14.4
func getEnvGoSemVer() string {
goVersion := toolchain.GetEnvGoVersion()
if !strings.HasPrefix(goVersion, "go") {
log.Fatalf("Expected 'go version' output of the form 'go1.2.3'; got '%s'", goVersion)
}
// Go versions don't follow the SemVer format, but the only exception we normally care about
// is release candidates; so this is a horrible hack to convert e.g. `go1.22rc1` into `go1.22-rc1`
// which is compatible with the SemVer specification
rcIndex := strings.Index(goVersion, "rc")
if rcIndex != -1 {
return semver.Canonical("v"+goVersion[2:rcIndex]) + "-" + goVersion[rcIndex:]
} else {
return semver.Canonical("v" + goVersion[2:])
}
}
// Returns the import path of the package being built, or "" if it cannot be determined.
func getImportPath() (importpath string) {
importpath = os.Getenv("LGTM_INDEX_IMPORT_PATH")
@@ -177,8 +160,8 @@ func getSourceDir() string {
}
// fixGoVendorIssues fixes issues with go vendor for go version >= 1.14
func fixGoVendorIssues(buildInfo *project.BuildInfo, goModVersionFound bool) {
if buildInfo.ModMode == project.ModVendor {
func fixGoVendorIssues(workspace *project.GoWorkspace, goModVersionFound bool) {
if workspace.ModMode == project.ModVendor {
// fix go vendor issues with go versions >= 1.14 when no go version is specified in the go.mod
// if this is the case, and dependencies were vendored with an old go version (and therefore
// do not contain a '## explicit' annotation, the go command will fail and refuse to do any
@@ -186,7 +169,7 @@ func fixGoVendorIssues(buildInfo *project.BuildInfo, goModVersionFound bool) {
//
// we work around this by adding an explicit go version of 1.13, which is the last version
// where this is not an issue
if buildInfo.DepMode == project.GoGetWithModules {
if workspace.DepMode == project.GoGetWithModules {
if !goModVersionFound {
// if the go.mod does not contain a version line
modulesTxt, err := os.ReadFile("vendor/modules.txt")
@@ -197,7 +180,7 @@ func fixGoVendorIssues(buildInfo *project.BuildInfo, goModVersionFound bool) {
log.Println("Adding a version directive to the go.mod file as the modules.txt does not have explicit annotations")
if !addVersionToMod("1.13") {
log.Println("Failed to add a version to the go.mod file to fix explicitly required package bug; not using vendored dependencies")
buildInfo.ModMode = project.ModMod
workspace.ModMode = project.ModMod
}
}
}
@@ -206,9 +189,9 @@ func fixGoVendorIssues(buildInfo *project.BuildInfo, goModVersionFound bool) {
}
// Determines whether the project needs a GOPATH set up
func getNeedGopath(buildInfo project.BuildInfo, importpath string) bool {
func getNeedGopath(workspace project.GoWorkspace, importpath string) bool {
needGopath := true
if buildInfo.DepMode == project.GoGetWithModules {
if workspace.DepMode == project.GoGetWithModules {
needGopath = false
}
// if `LGTM_INDEX_NEED_GOPATH` is set, it overrides the value for `needGopath` inferred above
@@ -229,44 +212,46 @@ func getNeedGopath(buildInfo project.BuildInfo, importpath string) bool {
}
// Try to update `go.mod` and `go.sum` if the go version is >= 1.16.
func tryUpdateGoModAndGoSum(buildInfo project.BuildInfo) {
func tryUpdateGoModAndGoSum(workspace project.GoWorkspace) {
// 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 buildInfo.ModMode != project.ModVendor && buildInfo.DepMode == project.GoGetWithModules && semver.Compare(getEnvGoSemVer(), "v1.16") >= 0 {
// stat go.mod and go.sum
goModPath := filepath.Join(buildInfo.BaseDir, "go.mod")
beforeGoModFileInfo, beforeGoModErr := os.Stat(goModPath)
if beforeGoModErr != nil {
log.Println("Failed to stat go.mod before running `go mod tidy -e`")
}
goSumPath := filepath.Join(buildInfo.BaseDir, "go.sum")
beforeGoSumFileInfo, beforeGoSumErr := os.Stat(goSumPath)
// run `go mod tidy -e`
cmd := exec.Command("go", "mod", "tidy", "-e")
cmd.Dir = buildInfo.BaseDir
res := util.RunCmd(cmd)
if !res {
log.Println("Failed to run `go mod tidy -e`")
} else {
if beforeGoModFileInfo != nil {
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()) {
// if go.mod has been changed then notify the user
log.Println("We have run `go mod tidy -e` and it altered go.mod. You may wish to check these changes into version control. ")
}
if workspace.ModMode != project.ModVendor && workspace.DepMode == project.GoGetWithModules && semver.Compare(toolchain.GetEnvGoSemVer(), "v1.16") >= 0 {
for _, goMod := range workspace.Modules {
// stat go.mod and go.sum
goModPath := goMod.Path
goModDir := filepath.Dir(goModPath)
beforeGoModFileInfo, beforeGoModErr := os.Stat(goModPath)
if beforeGoModErr != nil {
log.Printf("Failed to stat %s before running `go mod tidy -e`\n", goModPath)
}
afterGoSumFileInfo, afterGoSumErr := os.Stat(goSumPath)
if afterGoSumErr != nil {
log.Println("Failed to stat go.sum after running `go mod tidy -e`")
goSumPath := filepath.Join(goModDir, "go.sum")
beforeGoSumFileInfo, beforeGoSumErr := os.Stat(goSumPath)
// run `go mod tidy -e`
cmd := toolchain.TidyModule(goModDir)
res := util.RunCmd(cmd)
if !res {
log.Printf("Failed to run `go mod tidy -e` in %s\n", goModDir)
} else {
if beforeGoSumErr != nil || afterGoSumFileInfo.ModTime().After(beforeGoSumFileInfo.ModTime()) {
// if go.sum has been changed then notify the user
log.Println("We have run `go mod tidy -e` and it altered go.sum. You may wish to check these changes into version control. ")
if beforeGoModFileInfo != nil {
afterGoModFileInfo, afterGoModErr := os.Stat(goModPath)
if afterGoModErr != nil {
log.Printf("Failed to stat %s after running `go mod tidy -e`: %s\n", goModPath, afterGoModErr.Error())
} else if afterGoModFileInfo.ModTime().After(beforeGoModFileInfo.ModTime()) {
// if go.mod has been changed then notify the user
log.Println("We have run `go mod tidy -e` and it altered go.mod. You may wish to check these changes into version control. ")
}
}
afterGoSumFileInfo, afterGoSumErr := os.Stat(goSumPath)
if afterGoSumErr != nil {
log.Printf("Failed to stat %s after running `go mod tidy -e`: %s\n", goSumPath, afterGoSumErr.Error())
} else {
if beforeGoSumErr != nil || afterGoSumFileInfo.ModTime().After(beforeGoSumFileInfo.ModTime()) {
// if go.sum has been changed then notify the user
log.Println("We have run `go mod tidy -e` and it altered go.sum. You may wish to check these changes into version control. ")
}
}
}
}
@@ -406,7 +391,7 @@ func buildWithoutCustomCommands(modMode project.ModMode) bool {
log.Println("Build failed, continuing to install dependencies.")
shouldInstallDependencies = true
} else if util.DepErrors("./...", modMode.ArgsForGoVersion(getEnvGoSemVer())...) {
} else if util.DepErrors("./...", modMode.ArgsForGoVersion(toolchain.GetEnvGoSemVer())...) {
log.Println("Dependencies are still not resolving after the build, continuing to install dependencies.")
shouldInstallDependencies = true
@@ -449,10 +434,10 @@ func buildWithCustomCommands(inst string) {
}
// Install dependencies using the given dependency installer mode.
func installDependencies(buildInfo project.BuildInfo) {
func installDependencies(workspace project.GoWorkspace) {
// automatically determine command to install dependencies
var install *exec.Cmd
if buildInfo.DepMode == project.Dep {
if workspace.DepMode == project.Dep {
// set up the dep cache if SEMMLE_CACHE is set
cacheDir := os.Getenv("SEMMLE_CACHE")
if cacheDir != "" {
@@ -482,47 +467,80 @@ func installDependencies(buildInfo project.BuildInfo) {
install = exec.Command("dep", "ensure", "-v")
}
log.Println("Installing dependencies using `dep ensure`.")
} else if buildInfo.DepMode == project.Glide {
util.RunCmd(install)
} else if workspace.DepMode == project.Glide {
install = exec.Command("glide", "install")
log.Println("Installing dependencies using `glide install`")
util.RunCmd(install)
} else {
// explicitly set go module support
if buildInfo.DepMode == project.GoGetWithModules {
os.Setenv("GO111MODULE", "on")
} else if buildInfo.DepMode == project.GoGetNoModules {
os.Setenv("GO111MODULE", "off")
if workspace.Modules == nil {
project.InitGoModForLegacyProject(workspace.BaseDir)
workspace.Modules = project.LoadGoModules([]string{filepath.Join(workspace.BaseDir, "go.mod")})
}
// get dependencies
install = exec.Command("go", "get", "-v", "./...")
install.Dir = buildInfo.BaseDir
log.Printf("Installing dependencies using `go get -v ./...` in `%s`.\n", buildInfo.BaseDir)
// get dependencies for all modules
for _, module := range workspace.Modules {
path := filepath.Dir(module.Path)
if util.DirExists(filepath.Join(path, "vendor")) {
vendor := toolchain.VendorModule(path)
log.Printf("Synchronizing vendor file using `go mod vendor` in %s.\n", path)
util.RunCmd(vendor)
}
install = exec.Command("go", "get", "-v", "./...")
install.Dir = path
log.Printf("Installing dependencies using `go get -v ./...` in `%s`.\n", path)
util.RunCmd(install)
}
}
util.RunCmd(install)
}
// Run the extractor.
func extract(buildInfo project.BuildInfo) {
func extract(workspace project.GoWorkspace) bool {
extractor, err := util.GetExtractorPath()
if err != nil {
log.Fatalf("Could not determine path of extractor: %v.\n", err)
}
extractorArgs := []string{}
if buildInfo.DepMode == project.GoGetWithModules {
extractorArgs = append(extractorArgs, buildInfo.ModMode.ArgsForGoVersion(getEnvGoSemVer())...)
if workspace.DepMode == project.GoGetWithModules {
extractorArgs = append(extractorArgs, workspace.ModMode.ArgsForGoVersion(toolchain.GetEnvGoSemVer())...)
}
extractorArgs = append(extractorArgs, "./...")
log.Printf("Running extractor command '%s %v' from directory '%s'.\n", extractor, extractorArgs, buildInfo.BaseDir)
if len(workspace.Modules) == 0 {
// There may be no modules if we are using e.g. Dep or Glide
extractorArgs = append(extractorArgs, "./...")
} else {
for _, module := range workspace.Modules {
relModPath, relErr := filepath.Rel(workspace.BaseDir, filepath.Dir(module.Path))
if relErr != nil {
log.Printf(
"Unable to make module path %s relative to workspace base dir %s: %s\n",
filepath.Dir(module.Path), workspace.BaseDir, relErr.Error())
} else {
if relModPath != "." {
extractorArgs = append(extractorArgs, "."+string(os.PathSeparator)+relModPath+"/...")
} else {
extractorArgs = append(extractorArgs, relModPath+"/...")
}
}
}
}
log.Printf("Running extractor command '%s %v' from directory '%s'.\n", extractor, extractorArgs, workspace.BaseDir)
cmd := exec.Command(extractor, extractorArgs...)
cmd.Dir = buildInfo.BaseDir
cmd.Dir = workspace.BaseDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
log.Fatalf("Extraction failed: %s\n", err.Error())
log.Printf("Extraction failed for %s: %s\n", workspace.BaseDir, err.Error())
return false
}
return true
}
// Build the project and run the extractor.
@@ -536,75 +554,120 @@ func installDependenciesAndBuild() {
// determine how to install dependencies and whether a GOPATH needs to be set up before
// extraction
buildInfo := project.GetBuildInfo(true)
workspaces := project.GetWorkspaceInfo(true)
if _, present := os.LookupEnv("GO111MODULE"); !present {
os.Setenv("GO111MODULE", "auto")
}
goVersionInfo := project.TryReadGoDirective(buildInfo)
// Remove temporary extractor files (e.g. auto-generated go.mod files) when we are done
defer project.RemoveTemporaryExtractorFiles()
// If there is only one workspace and it needs a GOPATH set up, which may be the case if
// we don't use Go modules, then we move the repository to a temporary directory and set
// the GOPATH to it.
if len(workspaces) == 1 {
workspace := workspaces[0]
importpath := getImportPath()
needGopath := getNeedGopath(workspace, importpath)
inLGTM := os.Getenv("LGTM_SRC") != "" || os.Getenv("LGTM_INDEX_NEED_GOPATH") != ""
if inLGTM && needGopath {
paths := moveToTemporaryGopath(srcdir, importpath)
// schedule restoring the contents of newdir to their original location after this function completes:
defer restoreRepoLayout(paths.newdir, paths.files, filepath.Base(paths.scratch), srcdir)
pt := createPathTransformerFile(paths.newdir)
defer os.Remove(pt.Name())
writePathTransformerFile(pt, paths.realSrc, paths.root, paths.newdir)
setGopath(paths.root)
}
}
// Find the greatest version of Go that is required by the workspaces to check it against the version
// of Go that is installed on the system.
greatestGoVersion := project.RequiredGoVersion(&workspaces)
// This diagnostic is not required if the system Go version is 1.21 or greater, since the
// Go tooling should install required Go versions as needed.
if semver.Compare(getEnvGoSemVer(), "v1.21.0") < 0 && goVersionInfo.Found && semver.Compare("v"+goVersionInfo.Version, getEnvGoSemVer()) > 0 {
diagnostics.EmitNewerGoVersionNeeded(getEnvGoSemVer(), "v"+goVersionInfo.Version)
if semver.Compare(toolchain.GetEnvGoSemVer(), "v1.21.0") < 0 && greatestGoVersion.Found && semver.Compare("v"+greatestGoVersion.Version, toolchain.GetEnvGoSemVer()) > 0 {
diagnostics.EmitNewerGoVersionNeeded(toolchain.GetEnvGoSemVer(), "v"+greatestGoVersion.Version)
if val, _ := os.LookupEnv("GITHUB_ACTIONS"); val == "true" {
log.Printf(
"The go.mod file requires version %s of Go, but version %s is installed. Consider adding an actions/setup-go step to your workflow.\n",
"v"+goVersionInfo.Version,
getEnvGoSemVer())
"A go.mod file requires version %s of Go, but version %s is installed. Consider adding an actions/setup-go step to your workflow.\n",
"v"+greatestGoVersion.Version,
toolchain.GetEnvGoSemVer())
}
}
fixGoVendorIssues(&buildInfo, goVersionInfo.Found)
// Attempt to extract all workspaces; we will tolerate individual extraction failures here
for i, workspace := range workspaces {
goVersionInfo := workspace.RequiredGoVersion()
tryUpdateGoModAndGoSum(buildInfo)
fixGoVendorIssues(&workspace, goVersionInfo.Found)
importpath := getImportPath()
needGopath := getNeedGopath(buildInfo, importpath)
tryUpdateGoModAndGoSum(workspace)
inLGTM := os.Getenv("LGTM_SRC") != "" || os.Getenv("LGTM_INDEX_NEED_GOPATH") != ""
if inLGTM && needGopath {
paths := moveToTemporaryGopath(srcdir, importpath)
// schedule restoring the contents of newdir to their original location after this function completes:
defer restoreRepoLayout(paths.newdir, paths.files, filepath.Base(paths.scratch), srcdir)
pt := createPathTransformerFile(paths.newdir)
defer os.Remove(pt.Name())
writePathTransformerFile(pt, paths.realSrc, paths.root, paths.newdir)
setGopath(paths.root)
}
// check whether an explicit dependency installation command was provided
inst := util.Getenv("CODEQL_EXTRACTOR_GO_BUILD_COMMAND", "LGTM_INDEX_BUILD_COMMAND")
shouldInstallDependencies := false
if inst == "" {
shouldInstallDependencies = buildWithoutCustomCommands(buildInfo.ModMode)
} else {
buildWithCustomCommands(inst)
}
if buildInfo.ModMode == project.ModVendor {
// test if running `go` with -mod=vendor works, and if it doesn't, try to fallback to -mod=mod
// or not set if the go version < 1.14. Note we check this post-build in case the build brings
// the vendor directory up to date.
if !checkVendor() {
buildInfo.ModMode = project.ModMod
log.Println("The vendor directory is not consistent with the go.mod; not using vendored dependencies.")
}
}
if shouldInstallDependencies {
if buildInfo.ModMode == project.ModVendor {
log.Printf("Skipping dependency installation because a Go vendor directory was found.")
// check whether an explicit dependency installation command was provided
inst := util.Getenv("CODEQL_EXTRACTOR_GO_BUILD_COMMAND", "LGTM_INDEX_BUILD_COMMAND")
shouldInstallDependencies := false
if inst == "" {
shouldInstallDependencies = buildWithoutCustomCommands(workspace.ModMode)
} else {
installDependencies(buildInfo)
buildWithCustomCommands(inst)
}
if workspace.ModMode == project.ModVendor {
// test if running `go` with -mod=vendor works, and if it doesn't, try to fallback to -mod=mod
// or not set if the go version < 1.14. Note we check this post-build in case the build brings
// the vendor directory up to date.
if !checkVendor() {
workspace.ModMode = project.ModMod
log.Println("The vendor directory is not consistent with the go.mod; not using vendored dependencies.")
}
}
if shouldInstallDependencies {
if workspace.ModMode == project.ModVendor {
log.Printf("Skipping dependency installation because a Go vendor directory was found.")
} else {
installDependencies(workspace)
}
}
workspaces[i].Extracted = extract(workspace)
}
// Find all projects which could not be extracted successfully
var unsuccessfulProjects = []string{}
for _, workspace := range workspaces {
if !workspace.Extracted {
unsuccessfulProjects = append(unsuccessfulProjects, workspace.BaseDir)
}
}
extract(buildInfo)
// If all projects could not be extracted successfully, we fail the overall extraction.
if len(unsuccessfulProjects) == len(workspaces) {
log.Fatalln("Extraction failed for all discovered Go projects.")
}
// If there is at least one project that could not be extracted successfully,
// emit a diagnostic that reports which projects we could not extract successfully.
// We only consider this a warning, since there may be test projects etc. which
// do not matter if they cannot be extracted successfully.
if len(unsuccessfulProjects) > 0 {
log.Printf(
"Warning: extraction failed for %d project(s): %s\n",
len(unsuccessfulProjects),
strings.Join(unsuccessfulProjects, ", "))
diagnostics.EmitExtractionFailedForProjects(unsuccessfulProjects)
} else {
log.Println("Success: extraction succeeded for all discovered projects.")
}
}
func main() {

View File

@@ -493,3 +493,18 @@ func EmitNewerSystemGoRequired(requiredVersion string) {
noLocation,
)
}
func EmitExtractionFailedForProjects(path []string) {
emitDiagnostic(
"go/autobuilder/extraction-failed-for-project",
fmt.Sprintf("Unable to extract %d Go projects", len(path)),
fmt.Sprintf(
"The following %d Go project%s could not be extracted successfully:\n\n`%s`\n",
len(path),
plural(len(path), "", "s"),
strings.Join(path, "`, `")),
severityWarning,
fullVisibility,
noLocation,
)
}

View File

@@ -5,22 +5,104 @@ import (
"os"
"path/filepath"
"regexp"
"slices"
"sort"
"strings"
"github.com/github/codeql-go/extractor/diagnostics"
"github.com/github/codeql-go/extractor/toolchain"
"github.com/github/codeql-go/extractor/util"
"golang.org/x/mod/modfile"
"golang.org/x/mod/semver"
)
func getDirs(paths []string) []string {
dirs := make([]string, len(paths))
for i, path := range paths {
dirs[i] = filepath.Dir(path)
}
return dirs
// DependencyInstallerMode is an enum describing how dependencies should be installed
type DependencyInstallerMode int
const (
// GoGetNoModules represents dependency installation using `go get` without modules
GoGetNoModules DependencyInstallerMode = iota
// GoGetWithModules represents dependency installation using `go get` with modules
GoGetWithModules
// Dep represent dependency installation using `dep ensure`
Dep
// Glide represents dependency installation using `glide install`
Glide
)
// Represents information about a `go.mod` file: this is at least the path to the `go.mod` file,
// plus the parsed contents of the file, if available.
type GoModule struct {
Path string // The path to the `go.mod` file
Module *modfile.File // The parsed contents of the `go.mod` file
}
// Represents information about a Go project workspace: this may either be a folder containing
// a `go.work` file or a collection of `go.mod` files.
type GoWorkspace struct {
BaseDir string // The base directory for this workspace
WorkspaceFile *modfile.WorkFile // The `go.work` file for this workspace
Modules []*GoModule // A list of `go.mod` files
DepMode DependencyInstallerMode // A value indicating how to install dependencies for this workspace
ModMode ModMode // A value indicating which module mode to use for this workspace
Extracted bool // A value indicating whether this workspace was extracted successfully
}
// Represents a nullable version string.
type GoVersionInfo struct {
// The version string, if any
Version string
// A value indicating whether a version string was found
Found bool
}
// Determines the version of Go that is required by this workspace. This is, in order of preference:
// 1. The Go version specified in the `go.work` file, if any.
// 2. The greatest Go version specified in any `go.mod` file, if any.
func (workspace *GoWorkspace) RequiredGoVersion() GoVersionInfo {
if workspace.WorkspaceFile != nil && workspace.WorkspaceFile.Go != nil {
// If we have parsed a `go.work` file, return the version number from it.
return GoVersionInfo{Version: workspace.WorkspaceFile.Go.Version, Found: true}
} else if workspace.Modules != nil && len(workspace.Modules) > 0 {
// Otherwise, if we have `go.work` files, find the greatest Go version in those.
var greatestVersion string = ""
for _, module := range workspace.Modules {
if module.Module != nil && module.Module.Go != nil {
// If we have parsed the file, retrieve the version number we have already obtained.
if greatestVersion == "" || semver.Compare("v"+module.Module.Go.Version, "v"+greatestVersion) > 0 {
greatestVersion = module.Module.Go.Version
}
} else {
modVersion := tryReadGoDirective(module.Path)
if modVersion.Found && (greatestVersion == "" || semver.Compare("v"+modVersion.Version, "v"+greatestVersion) > 0) {
greatestVersion = modVersion.Version
}
}
}
// If we have found some version, return it.
if greatestVersion != "" {
return GoVersionInfo{Version: greatestVersion, Found: true}
}
}
return GoVersionInfo{Version: "", Found: false}
}
// Finds the greatest Go version required by any of the given `workspaces`.
// Returns a `GoVersionInfo` value with `Found: false` if no version information is available.
func RequiredGoVersion(workspaces *[]GoWorkspace) GoVersionInfo {
greatestGoVersion := GoVersionInfo{Version: "", Found: false}
for _, workspace := range *workspaces {
goVersionInfo := workspace.RequiredGoVersion()
if goVersionInfo.Found && (!greatestGoVersion.Found || semver.Compare("v"+goVersionInfo.Version, "v"+greatestGoVersion.Version) > 0) {
greatestGoVersion = goVersionInfo
}
}
return greatestGoVersion
}
// Determines whether any of the directory paths in the input are nested.
func checkDirsNested(inputDirs []string) (string, bool) {
// replace "." with "" so that we can check if all the paths are nested
dirs := make([]string, len(inputDirs))
@@ -42,70 +124,353 @@ func checkDirsNested(inputDirs []string) (string, bool) {
return dirs[0], true
}
// Returns the directory to run the go build in and whether to use a go.mod
// file.
func findGoModFiles(emitDiagnostics bool) (baseDir string, useGoMod bool) {
goModPaths := util.FindAllFilesWithName(".", "go.mod", "vendor")
if len(goModPaths) == 0 {
baseDir = "."
useGoMod = false
// A list of files we created that should be removed after we are done.
var filesToRemove []string = []string{}
// Try to initialize a go.mod file for projects that do not already have one.
func InitGoModForLegacyProject(path string) {
log.Printf("Project appears to be a legacy Go project, attempting to initialize go.mod in %s\n", path)
modInit := toolchain.InitModule(path)
if !util.RunCmd(modInit) {
log.Printf("Failed to initialize go.mod file for this project.")
return
}
goModDirs := getDirs(goModPaths)
if util.AnyGoFilesOutsideDirs(".", goModDirs...) {
// Add the go.mod file to a list of files we should remove later.
filesToRemove = append(filesToRemove, filepath.Join(path, "go.mod"))
modTidy := toolchain.TidyModule(path)
out, err := modTidy.CombinedOutput()
log.Println(string(out))
if err != nil {
log.Printf("Failed to determine module requirements for this project.")
}
if strings.Contains(string(out), "is relative, but relative import paths are not supported in module mode") {
diagnostics.EmitRelativeImportPaths()
}
}
// Attempts to remove all files that we created.
func RemoveTemporaryExtractorFiles() {
for _, path := range filesToRemove {
err := os.Remove(path)
if err != nil {
log.Printf("Unable to remove file we created at %s: %s\n", path, err.Error())
}
}
filesToRemove = []string{}
}
// Find all go.work files in the working directory and its subdirectories
func findGoWorkFiles() []string {
return util.FindAllFilesWithName(".", "go.work", "vendor")
}
// Find all go.mod files in the specified directory and its subdirectories
func findGoModFiles(root string) []string {
return util.FindAllFilesWithName(root, "go.mod", "vendor")
}
// Given a list of `go.mod` file paths, try to parse them all. The resulting array of `GoModule` objects
// will be the same length as the input array and the objects will contain at least the `go.mod` path.
// If parsing the corresponding file is successful, then the parsed contents will also be available.
func LoadGoModules(goModFilePaths []string) []*GoModule {
results := make([]*GoModule, len(goModFilePaths))
for i, goModFilePath := range goModFilePaths {
results[i] = new(GoModule)
results[i].Path = goModFilePath
modFileSrc, err := os.ReadFile(goModFilePath)
if err != nil {
log.Printf("Unable to read %s: %s.\n", goModFilePath, err.Error())
continue
}
modFile, err := modfile.ParseLax(goModFilePath, modFileSrc, nil)
if err != nil {
log.Printf("Unable to parse %s: %s.\n", goModFilePath, err.Error())
continue
}
results[i].Module = modFile
}
return results
}
// Given a path to a `go.work` file, this function attempts to parse the `go.work` file. If unsuccessful,
// we attempt to discover `go.mod` files within subdirectories of the directory containing the `go.work`
// file ourselves.
func discoverWorkspace(workFilePath string) GoWorkspace {
log.Printf("Loading %s...\n", workFilePath)
baseDir := filepath.Dir(workFilePath)
workFileSrc, err := os.ReadFile(workFilePath)
if err != nil {
// We couldn't read the `go.work` file for some reason; let's try to find `go.mod` files ourselves
log.Printf("Unable to read %s, falling back to finding `go.mod` files manually:\n%s\n", workFilePath, err.Error())
return GoWorkspace{
BaseDir: baseDir,
Modules: LoadGoModules(findGoModFiles(baseDir)),
DepMode: GoGetWithModules,
ModMode: getModMode(GoGetWithModules, baseDir),
}
}
workFile, err := modfile.ParseWork(workFilePath, workFileSrc, nil)
if err != nil {
// The `go.work` file couldn't be parsed for some reason; let's try to find `go.mod` files ourselves
log.Printf("Unable to parse %s, falling back to finding `go.mod` files manually:\n%s\n", workFilePath, err.Error())
return GoWorkspace{
BaseDir: baseDir,
Modules: LoadGoModules(findGoModFiles(baseDir)),
DepMode: GoGetWithModules,
ModMode: getModMode(GoGetWithModules, baseDir),
}
}
// Get the paths of all of the `go.mod` files that we read from the `go.work` file.
goModFilePaths := make([]string, len(workFile.Use))
for i, use := range workFile.Use {
if filepath.IsAbs(use.Path) {
// TODO: This case might be problematic for some other logic (e.g. stray file detection)
goModFilePaths[i] = filepath.Join(use.Path, "go.mod")
} else {
goModFilePaths[i] = filepath.Join(filepath.Dir(workFilePath), use.Path, "go.mod")
}
}
log.Printf("%s uses the following Go modules:\n%s\n", workFilePath, strings.Join(goModFilePaths, "\n"))
return GoWorkspace{
BaseDir: baseDir,
WorkspaceFile: workFile,
Modules: LoadGoModules(goModFilePaths),
DepMode: GoGetWithModules,
ModMode: ModReadonly, // Workspaces only support "readonly"
}
}
// Analyse the working directory to discover workspaces.
func discoverWorkspaces(emitDiagnostics bool) []GoWorkspace {
// Try to find any `go.work` files which may exist in the working directory.
goWorkFiles := findGoWorkFiles()
if len(goWorkFiles) == 0 {
// There is no `go.work` file. Find all `go.mod` files in the working directory.
log.Println("Found no go.work files in the workspace; looking for go.mod files...")
goModFiles := findGoModFiles(".")
// Return a separate workspace for each `go.mod` file that we found.
results := make([]GoWorkspace, len(goModFiles))
for i, goModFile := range goModFiles {
results[i] = GoWorkspace{
BaseDir: filepath.Dir(goModFile),
Modules: LoadGoModules([]string{goModFile}),
DepMode: GoGetWithModules,
ModMode: getModMode(GoGetWithModules, filepath.Dir(goModFile)),
}
}
return results
} else {
// We have found `go.work` files, try to load them all.
log.Printf("Found go.work file(s) in: %s.\n", strings.Join(goWorkFiles, ", "))
if emitDiagnostics {
diagnostics.EmitGoWorkFound(goWorkFiles)
}
results := make([]GoWorkspace, len(goWorkFiles))
for i, workFilePath := range goWorkFiles {
results[i] = discoverWorkspace(workFilePath)
}
// Add all stray `go.mod` files (i.e. those not referenced by `go.work` files)
// as separate workspaces.
goModFiles := findGoModFiles(".")
for _, goModFile := range goModFiles {
// Check to see whether we already have this module file under an existing workspace.
found := false
for _, workspace := range results {
if workspace.Modules == nil {
break
}
for _, module := range workspace.Modules {
if module.Path == goModFile {
found = true
break
}
}
if found {
break
}
}
// If not, add it to the array.
if !found {
log.Printf("Module %s is not referenced by any go.work file; adding it separately.\n", goModFile)
results = append(results, GoWorkspace{
BaseDir: filepath.Dir(goModFile),
Modules: LoadGoModules([]string{goModFile}),
DepMode: GoGetWithModules,
ModMode: getModMode(GoGetWithModules, filepath.Dir(goModFile)),
})
}
}
return results
}
}
// Discovers Go workspaces in the current working directory.
// Returns an array of Go workspaces and the total number of module files which we discovered.
func getBuildRoots(emitDiagnostics bool) (goWorkspaces []GoWorkspace, totalModuleFiles int) {
goWorkspaces = discoverWorkspaces(emitDiagnostics)
// Determine the total number of `go.mod` files that we discovered.
totalModuleFiles = 0
for _, goWorkspace := range goWorkspaces {
totalModuleFiles += len(goWorkspace.Modules)
}
// If there are no `go.mod` files at all, create one in line with https://go.dev/blog/migrating-to-go-modules
if totalModuleFiles == 0 {
// Check for other, legacy package managers
if util.FileExists("Gopkg.toml") {
if emitDiagnostics {
diagnostics.EmitGopkgTomlFound()
}
log.Println("Found Gopkg.toml, using dep instead of go get")
goWorkspaces = []GoWorkspace{{
BaseDir: ".",
DepMode: Dep,
ModMode: ModUnset,
}}
totalModuleFiles = 0
return
}
if util.FileExists("glide.yaml") {
if emitDiagnostics {
diagnostics.EmitGlideYamlFound()
}
log.Println("Found glide.yaml, using Glide instead of go get")
goWorkspaces = []GoWorkspace{{
BaseDir: ".",
DepMode: Glide,
ModMode: ModUnset,
}}
totalModuleFiles = 0
return
}
// If we have no `go.mod` files, then the project appears to be a legacy project without
// a `go.mod` file. Check that there are actually Go source files before initializing a module
// so that we correctly fail the extraction later.
if !util.FindGoFiles(".") {
goWorkspaces = []GoWorkspace{{
BaseDir: ".",
DepMode: GoGetNoModules,
ModMode: ModUnset,
}}
totalModuleFiles = 0
return
}
goWorkspaces = []GoWorkspace{{
BaseDir: ".",
DepMode: GoGetNoModules,
ModMode: getModMode(GoGetWithModules, "."),
}}
totalModuleFiles = 0
return
}
// Get the paths to all `go.mod` files
i := 0
goModPaths := make([]string, totalModuleFiles)
for _, goWorkspace := range goWorkspaces {
for _, goModule := range goWorkspace.Modules {
goModPaths[i] = goModule.Path
i++
}
}
goModDirs := util.GetParentDirs(goModPaths)
straySourceFiles := util.GoFilesOutsideDirs(".", goModDirs...)
if len(straySourceFiles) > 0 {
if emitDiagnostics {
diagnostics.EmitGoFilesOutsideGoModules(goModPaths)
}
baseDir = "."
useGoMod = false
// We need to initialise Go modules for the stray source files. Our goal is to initialise
// as few Go modules as possible, in locations which do not overlap with existing Go
// modules.
for _, straySourceFile := range straySourceFiles {
path := "."
components := strings.Split(filepath.Dir(straySourceFile), string(os.PathSeparator))
for _, component := range components {
path = filepath.Join(path, component)
// Try to initialize a `go.mod` file automatically for the stray source files.
if !slices.Contains(goModDirs, path) {
goWorkspaces = append(goWorkspaces, GoWorkspace{
BaseDir: path,
DepMode: GoGetNoModules,
ModMode: ModUnset,
})
goModDirs = append(goModDirs, path)
break
}
}
}
return
}
if len(goModPaths) > 1 {
// currently not supported
baseDir = "."
commonRoot, nested := checkDirsNested(goModDirs)
if nested && commonRoot == "" {
useGoMod = true
} else {
useGoMod = false
}
if emitDiagnostics {
// If we are emitted diagnostics, report some details about the workspace structure.
if emitDiagnostics {
if totalModuleFiles > 1 {
_, nested := checkDirsNested(goModDirs)
if nested {
diagnostics.EmitMultipleGoModFoundNested(goModPaths)
} else {
diagnostics.EmitMultipleGoModFoundNotNested(goModPaths)
}
}
return
}
if emitDiagnostics {
if goModDirs[0] == "." {
diagnostics.EmitSingleRootGoModFound(goModPaths[0])
} else {
diagnostics.EmitSingleNonRootGoModFound(goModPaths[0])
} else if totalModuleFiles == 1 {
if goModDirs[0] == "." {
diagnostics.EmitSingleRootGoModFound(goModPaths[0])
} else {
diagnostics.EmitSingleNonRootGoModFound(goModPaths[0])
}
}
}
baseDir = goModDirs[0]
useGoMod = true
return
}
// DependencyInstallerMode is an enum describing how dependencies should be installed
type DependencyInstallerMode int
const (
// GoGetNoModules represents dependency installation using `go get` without modules
GoGetNoModules DependencyInstallerMode = iota
// GoGetWithModules represents dependency installation using `go get` with modules
GoGetWithModules
// Dep represent dependency installation using `dep ensure`
Dep
// Glide represents dependency installation using `glide install`
Glide
)
// Returns the appropriate DependencyInstallerMode for the current project
func getDepMode(emitDiagnostics bool) (DependencyInstallerMode, string) {
// Finds Go workspaces in the current working directory.
func GetWorkspaceInfo(emitDiagnostics bool) []GoWorkspace {
bazelPaths := util.FindAllFilesWithName(".", "BUILD", "vendor")
bazelPaths = append(bazelPaths, util.FindAllFilesWithName(".", "BUILD.bazel", "vendor")...)
if len(bazelPaths) > 0 {
@@ -115,36 +480,10 @@ func getDepMode(emitDiagnostics bool) (DependencyInstallerMode, string) {
}
}
goWorkPaths := util.FindAllFilesWithName(".", "go.work", "vendor")
if len(goWorkPaths) > 0 {
// currently not supported
if emitDiagnostics {
diagnostics.EmitGoWorkFound(goWorkPaths)
}
}
goWorkspaces, totalModuleFiles := getBuildRoots(emitDiagnostics)
log.Printf("Found %d go.mod file(s).\n", totalModuleFiles)
baseDir, useGoMod := findGoModFiles(emitDiagnostics)
if useGoMod {
log.Println("Found go.mod, enabling go modules")
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, "."
}
if util.FileExists("glide.yaml") {
if emitDiagnostics {
diagnostics.EmitGlideYamlFound()
}
log.Println("Found glide.yaml, using Glide instead of go get")
return Glide, "."
}
return GoGetNoModules, "."
return goWorkspaces
}
// ModMode corresponds to the possible values of the -mod flag for the Go compiler
@@ -194,38 +533,18 @@ func getModMode(depMode DependencyInstallerMode, baseDir string) ModMode {
return ModUnset
}
type BuildInfo struct {
DepMode DependencyInstallerMode
ModMode ModMode
BaseDir string
}
func GetBuildInfo(emitDiagnostics bool) BuildInfo {
depMode, baseDir := getDepMode(true)
modMode := getModMode(depMode, baseDir)
return BuildInfo{depMode, modMode, baseDir}
}
type GoVersionInfo struct {
// The version string, if any
Version string
// A value indicating whether a version string was found
Found bool
}
// Tries to open `go.mod` and read a go directive, returning the version and whether it was found.
func TryReadGoDirective(buildInfo BuildInfo) GoVersionInfo {
if buildInfo.DepMode == GoGetWithModules {
versionRe := regexp.MustCompile(`(?m)^go[ \t\r]+([0-9]+\.[0-9]+(\.[0-9]+)?)$`)
goMod, err := os.ReadFile(filepath.Join(buildInfo.BaseDir, "go.mod"))
if err != nil {
log.Println("Failed to read go.mod to check for missing Go version")
} else {
matches := versionRe.FindSubmatch(goMod)
if matches != nil {
if len(matches) > 1 {
return GoVersionInfo{string(matches[1]), true}
}
// The version string is returned in the "1.2.3" format.
func tryReadGoDirective(path string) GoVersionInfo {
versionRe := regexp.MustCompile(`(?m)^go[ \t\r]+([0-9]+\.[0-9]+(\.[0-9]+)?)$`)
goMod, err := os.ReadFile(path)
if err != nil {
log.Println("Failed to read go.mod to check for missing Go version")
} else {
matches := versionRe.FindSubmatch(goMod)
if matches != nil {
if len(matches) > 1 {
return GoVersionInfo{string(matches[1]), true}
}
}
}

View File

@@ -6,6 +6,8 @@ import (
"os"
"os/exec"
"strings"
"golang.org/x/mod/semver"
)
// Check if Go is installed in the environment.
@@ -36,6 +38,23 @@ func GetEnvGoVersion() string {
return goVersion
}
// Returns the current Go version in semver format, e.g. v1.14.4
func GetEnvGoSemVer() string {
goVersion := GetEnvGoVersion()
if !strings.HasPrefix(goVersion, "go") {
log.Fatalf("Expected 'go version' output of the form 'go1.2.3'; got '%s'", goVersion)
}
// Go versions don't follow the SemVer format, but the only exception we normally care about
// is release candidates; so this is a horrible hack to convert e.g. `go1.22rc1` into `go1.22-rc1`
// which is compatible with the SemVer specification
rcIndex := strings.Index(goVersion, "rc")
if rcIndex != -1 {
return semver.Canonical("v"+goVersion[2:rcIndex]) + "-" + goVersion[rcIndex:]
} else {
return semver.Canonical("v" + goVersion[2:])
}
}
// The 'go version' command may output warnings on separate lines before
// the actual version string is printed. This function parses the output
// to retrieve just the version string.
@@ -47,3 +66,29 @@ func parseGoVersion(data string) string {
}
return strings.Fields(lastLine)[2]
}
// Returns a value indicating whether the system Go toolchain supports workspaces.
func SupportsWorkspaces() bool {
return semver.Compare(GetEnvGoSemVer(), "v1.18.0") >= 0
}
// Run `go mod tidy -e` in the directory given by `path`.
func TidyModule(path string) *exec.Cmd {
cmd := exec.Command("go", "mod", "tidy", "-e")
cmd.Dir = path
return cmd
}
// Run `go mod init` in the directory given by `path`.
func InitModule(path string) *exec.Cmd {
modInit := exec.Command("go", "mod", "init", "codeql/auto-project")
modInit.Dir = path
return modInit
}
// Constructs a command to run `go mod vendor -e` in the directory given by `path`.
func VendorModule(path string) *exec.Cmd {
modVendor := exec.Command("go", "mod", "vendor", "-e")
modVendor.Dir = path
return modVendor
}

View File

@@ -10,6 +10,7 @@ import (
"os/exec"
"path/filepath"
"runtime"
"slices"
"strings"
)
@@ -204,13 +205,13 @@ func RunCmd(cmd *exec.Cmd) bool {
in, _ := cmd.StdinPipe()
err := cmd.Start()
if err != nil {
log.Printf("Running %s failed, continuing anyway: %s\n", cmd.Path, err.Error())
log.Printf("Running %s %v failed, continuing anyway: %s\n", cmd.Path, cmd.Args, err.Error())
return false
}
in.Close()
err = cmd.Wait()
if err != nil {
log.Printf("Running %s failed, continuing anyway: %s\n", cmd.Path, err.Error())
log.Printf("Running %s %v failed, continuing anyway: %s\n", cmd.Path, cmd.Args, err.Error())
return false
}
@@ -319,18 +320,16 @@ func FindAllFilesWithName(root string, name string, dirsToSkip ...string) []stri
return paths
}
// Determines whether there are any Go source files in locations which do not have a Go.mod
// file in the same directory or higher up in the file hierarchy, relative to the `root`.
func AnyGoFilesOutsideDirs(root string, dirsToSkip ...string) bool {
found := false
filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
for _, dirToSkip := range dirsToSkip {
if path == dirToSkip {
return filepath.SkipDir
}
}
if d.IsDir() && slices.Contains(dirsToSkip, path) {
return filepath.SkipDir
}
if filepath.Ext(d.Name()) == ".go" {
found = true
@@ -340,3 +339,34 @@ func AnyGoFilesOutsideDirs(root string, dirsToSkip ...string) bool {
})
return found
}
// Returns an array of any Go source files in locations which do not have a Go.mod
// file in the same directory or higher up in the file hierarchy, relative to the `root`.
func GoFilesOutsideDirs(root string, dirsToSkip ...string) []string {
result := []string{}
filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() && slices.Contains(dirsToSkip, path) {
return filepath.SkipDir
}
if filepath.Ext(d.Name()) == ".go" {
log.Printf("Found stray Go source file in %s.\n", path)
result = append(result, path)
}
return nil
})
return result
}
// For every file path in the input array, return the parent directory.
func GetParentDirs(paths []string) []string {
dirs := make([]string, len(paths))
for i, path := range paths {
dirs[i] = filepath.Dir(path)
}
return dirs
}

View File

@@ -0,0 +1,4 @@
extractedFiles
| work/subdir/go.mod:0:0:0:0 | work/subdir/go.mod |
| work/subdir/test.go:0:0:0:0 | work/subdir/test.go |
#select

View File

@@ -10,7 +10,7 @@ from diagnostics_test_utils import *
goPath = os.path.join(os.path.abspath(os.getcwd()), ".go")
os.environ['GOPATH'] = goPath
os.environ['LGTM_INDEX_IMPORT_PATH'] = "test"
run_codeql_database_create([], lang="go", source="work", db=None, runFunction=runUnsuccessfully)
run_codeql_database_create([], lang="go", source="work")
check_diagnostics()

View File

@@ -0,0 +1,8 @@
import go
import semmle.go.DiagnosticsReporting
query predicate extractedFiles(File f) { any() }
from string msg, int sev
where reportableDiagnostics(_, msg, sev)
select msg, sev

View File

@@ -10,7 +10,7 @@ from diagnostics_test_utils import *
goPath = os.path.join(os.path.abspath(os.getcwd()), ".go")
os.environ['GOPATH'] = goPath
os.environ['GITHUB_REPOSITORY'] = "a/b"
run_codeql_database_create([], lang="go", source="work", db=None, runFunction=runUnsuccessfully)
run_codeql_database_create([], lang="go", source="work", db=None)
check_diagnostics()

View File

@@ -0,0 +1,14 @@
{
"markdownMessage": "A single `go.mod` file was found.\n\n`go.mod`",
"severity": "note",
"source": {
"extractorName": "go",
"id": "go/autobuilder/single-root-go-mod-found",
"name": "A single `go.mod` file was found in the root"
},
"visibility": {
"cliSummaryTable": false,
"statusPage": false,
"telemetry": true
}
}

View File

@@ -0,0 +1,2 @@
# go get has been observed to sometimes fail when multiple tests try to simultaneously fetch the same package.
goget

View File

@@ -0,0 +1,3 @@
require golang.org/x/net v0.0.0-20200505041828-1ed23360d12c
module test

View File

@@ -0,0 +1,7 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M=
golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -0,0 +1,5 @@
package subdir
func Add(a, b int) int {
return a + b
}

View File

@@ -0,0 +1,14 @@
package test
import (
"test/subdir"
"golang.org/x/net/ipv4"
)
func test() {
header := ipv4.Header{}
header.Version = subdir.Add(2, 2)
}

View File

@@ -0,0 +1,5 @@
extractedFiles
| src/go.mod:0:0:0:0 | src/go.mod |
| src/subdir/add.go:0:0:0:0 | src/subdir/add.go |
| src/test.go:0:0:0:0 | src/test.go |
#select

View File

@@ -0,0 +1,18 @@
import os
import subprocess
from create_database_utils import *
from diagnostics_test_utils import *
# Set up a GOPATH relative to this test's root directory;
# we set os.environ instead of using extra_env because we
# need it to be set for the call to "go clean -modcache" later
goPath = os.path.join(os.path.abspath(os.getcwd()), ".go")
os.environ['GOPATH'] = goPath
run_codeql_database_create([], lang="go", source="src")
check_diagnostics()
# Clean up the temporary GOPATH to prevent Bazel failures next
# time the tests are run; see https://github.com/golang/go/issues/27161
subprocess.call(["go", "clean", "-modcache"])

View File

@@ -0,0 +1,8 @@
import go
import semmle.go.DiagnosticsReporting
query predicate extractedFiles(File f) { any() }
from string msg, int sev
where reportableDiagnostics(_, msg, sev)
select msg, sev

View File

@@ -1,17 +1,3 @@
{
"markdownMessage": "1 package could not be found:\n\n`subdir/subsubdir`.\n\nDefinitions in those packages may not be recognized by CodeQL, and files that use them may only be partially analyzed.\n\nCheck that the paths are correct and make sure any private packages can be accessed. If any of the packages are present in the repository then you may need a [custom build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).",
"severity": "warning",
"source": {
"extractorName": "go",
"id": "go/autobuilder/package-not-found",
"name": "Some packages could not be found"
},
"visibility": {
"cliSummaryTable": true,
"statusPage": true,
"telemetry": true
}
}
{
"markdownMessage": "Go files were found outside of the Go modules corresponding to these `go.mod` files.\n\n`subdir/go.mod`",
"severity": "note",

View File

@@ -1,2 +1,7 @@
| Extraction failed in subdir/test.go with error cannot find package "subdir/subsubdir" in any of:\n\t(absolute path) (from $GOROOT)\n\t(absolute path) (from $GOPATH) | 2 |
| Extraction failed in subdir/test.go with error could not import subdir/subsubdir (invalid package name: "") | 2 |
extractedFiles
| src/go.mod:0:0:0:0 | src/go.mod |
| src/main.go:0:0:0:0 | src/main.go |
| src/subdir/go.mod:0:0:0:0 | src/subdir/go.mod |
| src/subdir/subsubdir/add.go:0:0:0:0 | src/subdir/subsubdir/add.go |
| src/subdir/test.go:0:0:0:0 | src/subdir/test.go |
#select

View File

@@ -1,6 +1,8 @@
import go
import semmle.go.DiagnosticsReporting
query predicate extractedFiles(File f) { any() }
from string msg, int sev
where reportableDiagnostics(_, msg, sev)
select msg, sev

View File

@@ -0,0 +1,5 @@
extractedFiles
| src/go.mod:0:0:0:0 | src/go.mod |
| src/subdir/add.go:0:0:0:0 | src/subdir/add.go |
| src/test.go:0:0:0:0 | src/test.go |
#select

View File

@@ -1,6 +1,8 @@
import go
import semmle.go.DiagnosticsReporting
query predicate extractedFiles(File f) { any() }
from string msg, int sev
where reportableDiagnostics(_, msg, sev)
select msg, sev

View File

@@ -0,0 +1,5 @@
extractedFiles
| src/subdir/go.mod:0:0:0:0 | src/subdir/go.mod |
| src/subdir/subsubdir/add.go:0:0:0:0 | src/subdir/subsubdir/add.go |
| src/subdir/test.go:0:0:0:0 | src/subdir/test.go |
#select

View File

@@ -1,6 +1,8 @@
import go
import semmle.go.DiagnosticsReporting
query predicate extractedFiles(File f) { any() }
from string msg, int sev
where reportableDiagnostics(_, msg, sev)
select msg, sev

View File

@@ -26,17 +26,3 @@
"telemetry": true
}
}
{
"markdownMessage": "2 packages could not be found:\n\n`subdir1/subsubdir1`, `subdir2/subsubdir2`.\n\nDefinitions in those packages may not be recognized by CodeQL, and files that use them may only be partially analyzed.\n\nCheck that the paths are correct and make sure any private packages can be accessed. If any of the packages are present in the repository then you may need a [custom build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).",
"severity": "warning",
"source": {
"extractorName": "go",
"id": "go/autobuilder/package-not-found",
"name": "Some packages could not be found"
},
"visibility": {
"cliSummaryTable": true,
"statusPage": true,
"telemetry": true
}
}

View File

@@ -1,4 +1,8 @@
| Extraction failed in modules/subdir1/test.go with error cannot find package "subdir1/subsubdir1" in any of:\n\t(absolute path) (from $GOROOT)\n\t(absolute path) (from $GOPATH) | 2 |
| Extraction failed in modules/subdir1/test.go with error could not import subdir1/subsubdir1 (invalid package name: "") | 2 |
| Extraction failed in modules/subdir2/test.go with error cannot find package "subdir2/subsubdir2" in any of:\n\t(absolute path) (from $GOROOT)\n\t(absolute path) (from $GOPATH) | 2 |
| Extraction failed in modules/subdir2/test.go with error could not import subdir2/subsubdir2 (invalid package name: "") | 2 |
extractedFiles
| src/modules/subdir1/go.mod:0:0:0:0 | src/modules/subdir1/go.mod |
| src/modules/subdir1/subsubdir1/add.go:0:0:0:0 | src/modules/subdir1/subsubdir1/add.go |
| src/modules/subdir1/test.go:0:0:0:0 | src/modules/subdir1/test.go |
| src/modules/subdir2/go.mod:0:0:0:0 | src/modules/subdir2/go.mod |
| src/modules/subdir2/subsubdir2/add.go:0:0:0:0 | src/modules/subdir2/subsubdir2/add.go |
| src/modules/subdir2/test.go:0:0:0:0 | src/modules/subdir2/test.go |
#select

View File

@@ -1,6 +1,8 @@
import go
import semmle.go.DiagnosticsReporting
query predicate extractedFiles(File f) { any() }
from string msg, int sev
where reportableDiagnostics(_, msg, sev)
select msg, sev

View File

@@ -12,17 +12,3 @@
"telemetry": true
}
}
{
"markdownMessage": "2 packages could not be found:\n\n`test/subdir2`, `subdir1/subsubdir1`.\n\nDefinitions in those packages may not be recognized by CodeQL, and files that use them may only be partially analyzed.\n\nCheck that the paths are correct and make sure any private packages can be accessed. If any of the packages are present in the repository then you may need a [custom build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).",
"severity": "warning",
"source": {
"extractorName": "go",
"id": "go/autobuilder/package-not-found",
"name": "Some packages could not be found"
},
"visibility": {
"cliSummaryTable": true,
"statusPage": true,
"telemetry": true
}
}

View File

@@ -1,4 +1,8 @@
| Extraction failed in subdir0/subdir1/test.go with error cannot find package "subdir1/subsubdir1" in any of:\n\t(absolute path) (from $GOROOT)\n\t(absolute path) (from $GOPATH) | 2 |
| Extraction failed in subdir0/subdir1/test.go with error could not import subdir1/subsubdir1 (invalid package name: "") | 2 |
| Extraction failed in subdir0/test.go with error cannot find package "test/subdir2" in any of:\n\t(absolute path) (from $GOROOT)\n\t(absolute path) (from $GOPATH) | 2 |
| Extraction failed in subdir0/test.go with error could not import test/subdir2 (invalid package name: "") | 2 |
extractedFiles
| src/subdir0/go.mod:0:0:0:0 | src/subdir0/go.mod |
| src/subdir0/subdir1/go.mod:0:0:0:0 | src/subdir0/subdir1/go.mod |
| src/subdir0/subdir1/subsubdir1/add.go:0:0:0:0 | src/subdir0/subdir1/subsubdir1/add.go |
| src/subdir0/subdir1/test.go:0:0:0:0 | src/subdir0/subdir1/test.go |
| src/subdir0/subdir2/add.go:0:0:0:0 | src/subdir0/subdir2/add.go |
| src/subdir0/test.go:0:0:0:0 | src/subdir0/test.go |
#select

View File

@@ -1,6 +1,8 @@
import go
import semmle.go.DiagnosticsReporting
query predicate extractedFiles(File f) { any() }
from string msg, int sev
where reportableDiagnostics(_, msg, sev)
select msg, sev

View File

@@ -0,0 +1,8 @@
extractedFiles
| src/go.mod:0:0:0:0 | src/go.mod |
| src/subdir1/go.mod:0:0:0:0 | src/subdir1/go.mod |
| src/subdir1/subsubdir1/add.go:0:0:0:0 | src/subdir1/subsubdir1/add.go |
| src/subdir1/test.go:0:0:0:0 | src/subdir1/test.go |
| src/subdir2/add.go:0:0:0:0 | src/subdir2/add.go |
| src/test.go:0:0:0:0 | src/test.go |
#select

View File

@@ -1,6 +1,8 @@
import go
import semmle.go.DiagnosticsReporting
query predicate extractedFiles(File f) { any() }
from string msg, int sev
where reportableDiagnostics(_, msg, sev)
select msg, sev

View File

@@ -12,17 +12,3 @@
"telemetry": true
}
}
{
"markdownMessage": "2 packages could not be found:\n\n`subdir1/subsubdir1`, `subdir2/subsubdir2`.\n\nDefinitions in those packages may not be recognized by CodeQL, and files that use them may only be partially analyzed.\n\nCheck that the paths are correct and make sure any private packages can be accessed. If any of the packages are present in the repository then you may need a [custom build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).",
"severity": "warning",
"source": {
"extractorName": "go",
"id": "go/autobuilder/package-not-found",
"name": "Some packages could not be found"
},
"visibility": {
"cliSummaryTable": true,
"statusPage": true,
"telemetry": true
}
}

View File

@@ -1,4 +1,8 @@
| Extraction failed in subdir1/test.go with error cannot find package "subdir1/subsubdir1" in any of:\n\t(absolute path) (from $GOROOT)\n\t(absolute path) (from $GOPATH) | 2 |
| Extraction failed in subdir1/test.go with error could not import subdir1/subsubdir1 (invalid package name: "") | 2 |
| Extraction failed in subdir2/test.go with error cannot find package "subdir2/subsubdir2" in any of:\n\t(absolute path) (from $GOROOT)\n\t(absolute path) (from $GOPATH) | 2 |
| Extraction failed in subdir2/test.go with error could not import subdir2/subsubdir2 (invalid package name: "") | 2 |
extractedFiles
| src/subdir1/go.mod:0:0:0:0 | src/subdir1/go.mod |
| src/subdir1/subsubdir1/add.go:0:0:0:0 | src/subdir1/subsubdir1/add.go |
| src/subdir1/test.go:0:0:0:0 | src/subdir1/test.go |
| src/subdir2/go.mod:0:0:0:0 | src/subdir2/go.mod |
| src/subdir2/subsubdir2/add.go:0:0:0:0 | src/subdir2/subsubdir2/add.go |
| src/subdir2/test.go:0:0:0:0 | src/subdir2/test.go |
#select

View File

@@ -1,6 +1,8 @@
import go
import semmle.go.DiagnosticsReporting
query predicate extractedFiles(File f) { any() }
from string msg, int sev
where reportableDiagnostics(_, msg, sev)
select msg, sev

View File

@@ -0,0 +1,28 @@
{
"markdownMessage": "2 `go.mod` files were found:\n\n`subdir1/go.mod`, `subdir2/go.mod`",
"severity": "note",
"source": {
"extractorName": "go",
"id": "go/autobuilder/multiple-go-mod-found-not-nested",
"name": "Multiple `go.mod` files found, not all nested under one root `go.mod` file"
},
"visibility": {
"cliSummaryTable": false,
"statusPage": false,
"telemetry": true
}
}
{
"markdownMessage": "The following 1 Go project could not be extracted successfully:\n\n`subdir2`\n",
"severity": "warning",
"source": {
"extractorName": "go",
"id": "go/autobuilder/extraction-failed-for-project",
"name": "Unable to extract 1 Go projects"
},
"visibility": {
"cliSummaryTable": true,
"statusPage": true,
"telemetry": true
}
}

View File

@@ -0,0 +1,2 @@
# go get has been observed to sometimes fail when multiple tests try to simultaneously fetch the same package.
goget

View File

@@ -0,0 +1,5 @@
go 1.14
require golang.org/x/net v0.0.0-20200505041828-1ed23360d12c
module subdir1

View File

@@ -0,0 +1,7 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M=
golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -0,0 +1,5 @@
package subsubdir1
func Add(a, b int) int {
return a + b
}

View File

@@ -0,0 +1,14 @@
package subdir
import (
"subdir1/subsubdir1"
"golang.org/x/net/ipv4"
)
func test() {
header := ipv4.Header{}
header.Version = subsubdir1.Add(2, 2)
}

View File

@@ -0,0 +1,7 @@
go 1.14
require (
github.com/microsoft/go-mssqldb v0.12.0
)
module subdir2

View File

@@ -0,0 +1,30 @@
github.com/Azure/go-autorest v13.3.2+incompatible h1:VxzPyuhtnlBOzc4IWCZHqpyH2d+QMLQEuy3wREyY4oc=
github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs=
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
github.com/Azure/go-autorest/autorest v0.9.4 h1:1cM+NmKw91+8h5vfjgzK4ZGLuN72k87XVZBWyGwNjUM=
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
github.com/Azure/go-autorest/autorest/adal v0.8.1 h1:pZdL8o72rK+avFWl+p9nE8RWi1JInZrWJYlnpfXJwHk=
github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM=
github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/microsoft/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 h1:OGNva6WhsKst5OZf7eZOklDztV3hwtTHovdrLHV+MsA=
github.com/microsoft/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -0,0 +1,5 @@
package subsubdir2
func Add(a, b int) int {
return a + b
}

View File

@@ -0,0 +1,12 @@
package subdir
import (
mssql "github.com/microsoft/go-mssqldb"
)
func test() {
connString := "hello"
connector, err := mssql.NewAccessTokenConnector(
connString, nil)
}

View File

@@ -0,0 +1,5 @@
extractedFiles
| src/subdir1/go.mod:0:0:0:0 | src/subdir1/go.mod |
| src/subdir1/subsubdir1/add.go:0:0:0:0 | src/subdir1/subsubdir1/add.go |
| src/subdir1/test.go:0:0:0:0 | src/subdir1/test.go |
#select

View File

@@ -0,0 +1,18 @@
import os
import subprocess
from create_database_utils import *
from diagnostics_test_utils import *
# Set up a GOPATH relative to this test's root directory;
# we set os.environ instead of using extra_env because we
# need it to be set for the call to "go clean -modcache" later
goPath = os.path.join(os.path.abspath(os.getcwd()), ".go")
os.environ['GOPATH'] = goPath
run_codeql_database_create([], lang="go", source="src")
check_diagnostics()
# Clean up the temporary GOPATH to prevent Bazel failures next
# time the tests are run; see https://github.com/golang/go/issues/27161
subprocess.call(["go", "clean", "-modcache"])

View File

@@ -0,0 +1,8 @@
import go
import semmle.go.DiagnosticsReporting
query predicate extractedFiles(File f) { any() }
from string msg, int sev
where reportableDiagnostics(_, msg, sev)
select msg, sev