diff --git a/extractor/autobuilder/autobuilder.go b/extractor/autobuilder/autobuilder.go new file mode 100644 index 00000000000..f31e3b6ffae --- /dev/null +++ b/extractor/autobuilder/autobuilder.go @@ -0,0 +1,81 @@ +// Package autobuilder implements a simple system that attempts to run build commands for common +// build frameworks, if the relevant files exist. +package autobuilder + +import ( + "log" + "os" + "os/exec" + + "github.com/github/codeql-go/extractor/util" +) + +// CheckExtracted sets whether the autobuilder should check whether source files have been extracted +// to the CodeQL source directory as well as whether the build command executed successfully. +var CheckExtracted = false + +// checkEmpty checks whether a directory either doesn't exist or is empty. +func checkEmpty(dir string) (bool, error) { + if !util.DirExists(dir) { + return true, nil + } + + d, err := os.Open(dir) + if err != nil { + return false, err + } + defer d.Close() + + names, err := d.Readdirnames(-1) + if err != nil { + return false, err + } + return len(names) == 0, nil +} + +// checkExtractorRun checks whether the CodeQL Go extractor has run, by checking if the source +// archive directory is empty or not. +func checkExtractorRun() bool { + srcDir := os.Getenv("CODEQL_EXTRACTOR_GO_SOURCE_ARCHIVE_DIR") + if srcDir != "" { + empty, err := checkEmpty(srcDir) + if err != nil { + log.Fatalf("Unable to read source archive directory %s.", srcDir) + } + if empty { + log.Printf("No Go code seen; continuing to try other builds.") + return false + } + return true + } else { + log.Fatalf("No source directory set.") + return false + } +} + +// tryBuildIfExists tries to run the command `cmd args...` if the file `buildFile` exists and is not +// a directory. Returns true if the command was successful and false if not. +func tryBuildIfExists(buildFile, cmd string, args ...string) bool { + if util.FileExists(buildFile) { + log.Printf("%s found.\n", buildFile) + return tryBuild(cmd, args...) + } + return false +} + +// tryBuild tries to run `cmd args...`, returning true if successful and false if not. +func tryBuild(cmd string, args ...string) bool { + log.Printf("Trying build command %s %v", cmd, args) + res := util.RunCmd(exec.Command(cmd, args...)) + 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") +} diff --git a/extractor/cli/go-autobuilder/go-autobuilder.go b/extractor/cli/go-autobuilder/go-autobuilder.go index 0860172223f..4344187cbd6 100644 --- a/extractor/cli/go-autobuilder/go-autobuilder.go +++ b/extractor/cli/go-autobuilder/go-autobuilder.go @@ -13,6 +13,7 @@ import ( "runtime" "strings" + "github.com/github/codeql-go/extractor/autobuilder" "github.com/github/codeql-go/extractor/util" ) @@ -403,13 +404,8 @@ func main() { inst := util.Getenv("CODEQL_EXTRACTOR_GO_BUILD_COMMAND", "LGTM_INDEX_BUILD_COMMAND") shouldInstallDependencies := false if inst == "" { - // if there is a build file, run the corresponding build tool - buildSucceeded := tryBuild("Makefile", "make") || - tryBuild("makefile", "make") || - tryBuild("GNUmakefile", "make") || - tryBuild("build.ninja", "ninja") || - tryBuild("build", "./build") || - tryBuild("build.sh", "./build.sh") + // try to build the project + buildSucceeded := autobuilder.Autobuild() if !buildSucceeded { // Build failed; we'll try to install dependencies ourselves