Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4f3e58c0 authored by Liz Kammer's avatar Liz Kammer Committed by Gerrit Code Review
Browse files

Merge "Add tests for bazel_handler"

parents 99e4fc13 8d62a4f7
Loading
Loading
Loading
Loading
+91 −42
Original line number Original line Diff line number Diff line
@@ -89,16 +89,24 @@ type BazelContext interface {
	BuildStatementsToRegister() []bazel.BuildStatement
	BuildStatementsToRegister() []bazel.BuildStatement
}
}


// A context object which tracks queued requests that need to be made to Bazel,
type bazelRunner interface {
// and their results after the requests have been made.
	issueBazelCommand(paths *bazelPaths, runName bazel.RunName, command bazelCommand, extraFlags ...string) (string, string, error)
type bazelContext struct {
}

type bazelPaths struct {
	homeDir      string
	homeDir      string
	bazelPath    string
	bazelPath    string
	outputBase   string
	outputBase   string
	workspaceDir string
	workspaceDir string
	buildDir     string
	buildDir     string
	metricsDir   string
	metricsDir   string
}


// A context object which tracks queued requests that need to be made to Bazel,
// and their results after the requests have been made.
type bazelContext struct {
	bazelRunner
	paths        *bazelPaths
	requests     map[cqueryKey]bool // cquery requests that have not yet been issued to Bazel
	requests     map[cqueryKey]bool // cquery requests that have not yet been issued to Bazel
	requestMutex sync.Mutex         // requests can be written in parallel
	requestMutex sync.Mutex         // requests can be written in parallel


@@ -228,42 +236,56 @@ func NewBazelContext(c *config) (BazelContext, error) {
		return noopBazelContext{}, nil
		return noopBazelContext{}, nil
	}
	}


	bazelCtx := bazelContext{buildDir: c.buildDir, requests: make(map[cqueryKey]bool)}
	p, err := bazelPathsFromConfig(c)
	if err != nil {
		return nil, err
	}
	return &bazelContext{
		bazelRunner: &builtinBazelRunner{},
		paths:       p,
		requests:    make(map[cqueryKey]bool),
	}, nil
}

func bazelPathsFromConfig(c *config) (*bazelPaths, error) {
	p := bazelPaths{
		buildDir: c.buildDir,
	}
	missingEnvVars := []string{}
	missingEnvVars := []string{}
	if len(c.Getenv("BAZEL_HOME")) > 1 {
	if len(c.Getenv("BAZEL_HOME")) > 1 {
		bazelCtx.homeDir = c.Getenv("BAZEL_HOME")
		p.homeDir = c.Getenv("BAZEL_HOME")
	} else {
	} else {
		missingEnvVars = append(missingEnvVars, "BAZEL_HOME")
		missingEnvVars = append(missingEnvVars, "BAZEL_HOME")
	}
	}
	if len(c.Getenv("BAZEL_PATH")) > 1 {
	if len(c.Getenv("BAZEL_PATH")) > 1 {
		bazelCtx.bazelPath = c.Getenv("BAZEL_PATH")
		p.bazelPath = c.Getenv("BAZEL_PATH")
	} else {
	} else {
		missingEnvVars = append(missingEnvVars, "BAZEL_PATH")
		missingEnvVars = append(missingEnvVars, "BAZEL_PATH")
	}
	}
	if len(c.Getenv("BAZEL_OUTPUT_BASE")) > 1 {
	if len(c.Getenv("BAZEL_OUTPUT_BASE")) > 1 {
		bazelCtx.outputBase = c.Getenv("BAZEL_OUTPUT_BASE")
		p.outputBase = c.Getenv("BAZEL_OUTPUT_BASE")
	} else {
	} else {
		missingEnvVars = append(missingEnvVars, "BAZEL_OUTPUT_BASE")
		missingEnvVars = append(missingEnvVars, "BAZEL_OUTPUT_BASE")
	}
	}
	if len(c.Getenv("BAZEL_WORKSPACE")) > 1 {
	if len(c.Getenv("BAZEL_WORKSPACE")) > 1 {
		bazelCtx.workspaceDir = c.Getenv("BAZEL_WORKSPACE")
		p.workspaceDir = c.Getenv("BAZEL_WORKSPACE")
	} else {
	} else {
		missingEnvVars = append(missingEnvVars, "BAZEL_WORKSPACE")
		missingEnvVars = append(missingEnvVars, "BAZEL_WORKSPACE")
	}
	}
	if len(c.Getenv("BAZEL_METRICS_DIR")) > 1 {
	if len(c.Getenv("BAZEL_METRICS_DIR")) > 1 {
		bazelCtx.metricsDir = c.Getenv("BAZEL_METRICS_DIR")
		p.metricsDir = c.Getenv("BAZEL_METRICS_DIR")
	} else {
	} else {
		missingEnvVars = append(missingEnvVars, "BAZEL_METRICS_DIR")
		missingEnvVars = append(missingEnvVars, "BAZEL_METRICS_DIR")
	}
	}
	if len(missingEnvVars) > 0 {
	if len(missingEnvVars) > 0 {
		return nil, errors.New(fmt.Sprintf("missing required env vars to use bazel: %s", missingEnvVars))
		return nil, errors.New(fmt.Sprintf("missing required env vars to use bazel: %s", missingEnvVars))
	} else {
	} else {
		return &bazelCtx, nil
		return &p, nil
	}
	}
}
}


func (context *bazelContext) BazelMetricsDir() string {
func (p *bazelPaths) BazelMetricsDir() string {
	return context.metricsDir
	return p.metricsDir
}
}


func (context *bazelContext) BazelEnabled() bool {
func (context *bazelContext) BazelEnabled() bool {
@@ -296,17 +318,40 @@ func pwdPrefix() string {
	return ""
	return ""
}
}


type bazelCommand struct {
	command string
	// query or label
	expression string
}

type mockBazelRunner struct {
	bazelCommandResults map[bazelCommand]string
	commands            []bazelCommand
}

func (r *mockBazelRunner) issueBazelCommand(paths *bazelPaths,
	runName bazel.RunName,
	command bazelCommand,
	extraFlags ...string) (string, string, error) {
	r.commands = append(r.commands, command)
	if ret, ok := r.bazelCommandResults[command]; ok {
		return ret, "", nil
	}
	return "", "", nil
}

type builtinBazelRunner struct{}

// Issues the given bazel command with given build label and additional flags.
// Issues the given bazel command with given build label and additional flags.
// Returns (stdout, stderr, error). The first and second return values are strings
// Returns (stdout, stderr, error). The first and second return values are strings
// containing the stdout and stderr of the run command, and an error is returned if
// containing the stdout and stderr of the run command, and an error is returned if
// the invocation returned an error code.
// the invocation returned an error code.
func (context *bazelContext) issueBazelCommand(runName bazel.RunName, command string, labels []string,
func (r *builtinBazelRunner) issueBazelCommand(paths *bazelPaths, runName bazel.RunName, command bazelCommand,
	extraFlags ...string) (string, string, error) {
	extraFlags ...string) (string, string, error) {

	cmdFlags := []string{"--output_base=" + paths.outputBase, command.command}
	cmdFlags := []string{"--output_base=" + context.outputBase, command}
	cmdFlags = append(cmdFlags, command.expression)
	cmdFlags = append(cmdFlags, labels...)
	cmdFlags = append(cmdFlags, "--package_path=%workspace%/"+paths.intermediatesDir())
	cmdFlags = append(cmdFlags, "--package_path=%workspace%/"+context.intermediatesDir())
	cmdFlags = append(cmdFlags, "--profile="+shared.BazelMetricsFilename(paths, runName))
	cmdFlags = append(cmdFlags, "--profile="+shared.BazelMetricsFilename(context, runName))


	// Set default platforms to canonicalized values for mixed builds requests.
	// Set default platforms to canonicalized values for mixed builds requests.
	// If these are set in the bazelrc, they will have values that are
	// If these are set in the bazelrc, they will have values that are
@@ -328,9 +373,9 @@ func (context *bazelContext) issueBazelCommand(runName bazel.RunName, command st
	cmdFlags = append(cmdFlags, "--experimental_repository_disable_download")
	cmdFlags = append(cmdFlags, "--experimental_repository_disable_download")
	cmdFlags = append(cmdFlags, extraFlags...)
	cmdFlags = append(cmdFlags, extraFlags...)


	bazelCmd := exec.Command(context.bazelPath, cmdFlags...)
	bazelCmd := exec.Command(paths.bazelPath, cmdFlags...)
	bazelCmd.Dir = context.workspaceDir
	bazelCmd.Dir = paths.workspaceDir
	bazelCmd.Env = append(os.Environ(), "HOME="+context.homeDir, pwdPrefix(),
	bazelCmd.Env = append(os.Environ(), "HOME="+paths.homeDir, pwdPrefix(),
		// Disables local host detection of gcc; toolchain information is defined
		// Disables local host detection of gcc; toolchain information is defined
		// explicitly in BUILD files.
		// explicitly in BUILD files.
		"BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1")
		"BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1")
@@ -367,7 +412,7 @@ local_repository(
    path = "%[1]s/build/bazel/bazel_skylib",
    path = "%[1]s/build/bazel/bazel_skylib",
)
)
`
`
	return []byte(fmt.Sprintf(formatString, context.workspaceDir))
	return []byte(fmt.Sprintf(formatString, context.paths.workspaceDir))
}
}


func (context *bazelContext) mainBzlFileContents() []byte {
func (context *bazelContext) mainBzlFileContents() []byte {
@@ -577,8 +622,8 @@ def format(target):


// Returns a workspace-relative path containing build-related metadata required
// Returns a workspace-relative path containing build-related metadata required
// for interfacing with Bazel. Example: out/soong/bazel.
// for interfacing with Bazel. Example: out/soong/bazel.
func (context *bazelContext) intermediatesDir() string {
func (p *bazelPaths) intermediatesDir() string {
	return filepath.Join(context.buildDir, "bazel")
	return filepath.Join(p.buildDir, "bazel")
}
}


// Issues commands to Bazel to receive results for all cquery requests
// Issues commands to Bazel to receive results for all cquery requests
@@ -590,7 +635,7 @@ func (context *bazelContext) InvokeBazel() error {
	var cqueryErr string
	var cqueryErr string
	var err error
	var err error


	intermediatesDirPath := absolutePath(context.intermediatesDir())
	intermediatesDirPath := absolutePath(context.paths.intermediatesDir())
	if _, err := os.Stat(intermediatesDirPath); os.IsNotExist(err) {
	if _, err := os.Stat(intermediatesDirPath); os.IsNotExist(err) {
		err = os.Mkdir(intermediatesDirPath, 0777)
		err = os.Mkdir(intermediatesDirPath, 0777)
	}
	}
@@ -599,38 +644,38 @@ func (context *bazelContext) InvokeBazel() error {
		return err
		return err
	}
	}
	err = ioutil.WriteFile(
	err = ioutil.WriteFile(
		absolutePath(filepath.Join(context.intermediatesDir(), "main.bzl")),
		filepath.Join(intermediatesDirPath, "main.bzl"),
		context.mainBzlFileContents(), 0666)
		context.mainBzlFileContents(), 0666)
	if err != nil {
	if err != nil {
		return err
		return err
	}
	}
	err = ioutil.WriteFile(
	err = ioutil.WriteFile(
		absolutePath(filepath.Join(context.intermediatesDir(), "BUILD.bazel")),
		filepath.Join(intermediatesDirPath, "BUILD.bazel"),
		context.mainBuildFileContents(), 0666)
		context.mainBuildFileContents(), 0666)
	if err != nil {
	if err != nil {
		return err
		return err
	}
	}
	cqueryFileRelpath := filepath.Join(context.intermediatesDir(), "buildroot.cquery")
	cqueryFileRelpath := filepath.Join(context.paths.intermediatesDir(), "buildroot.cquery")
	err = ioutil.WriteFile(
	err = ioutil.WriteFile(
		absolutePath(cqueryFileRelpath),
		absolutePath(cqueryFileRelpath),
		context.cqueryStarlarkFileContents(), 0666)
		context.cqueryStarlarkFileContents(), 0666)
	if err != nil {
	if err != nil {
		return err
		return err
	}
	}
	workspaceFileRelpath := filepath.Join(context.intermediatesDir(), "WORKSPACE.bazel")
	err = ioutil.WriteFile(
	err = ioutil.WriteFile(
		absolutePath(workspaceFileRelpath),
		filepath.Join(intermediatesDirPath, "WORKSPACE.bazel"),
		context.workspaceFileContents(), 0666)
		context.workspaceFileContents(), 0666)
	if err != nil {
	if err != nil {
		return err
		return err
	}
	}
	buildrootLabel := "//:buildroot"
	buildrootLabel := "//:buildroot"
	cqueryOutput, cqueryErr, err = context.issueBazelCommand(bazel.CqueryBuildRootRunName, "cquery",
	cqueryOutput, cqueryErr, err = context.issueBazelCommand(
		[]string{fmt.Sprintf("kind(rule, deps(%s))", buildrootLabel)},
		context.paths,
		bazel.CqueryBuildRootRunName,
		bazelCommand{"cquery", fmt.Sprintf("kind(rule, deps(%s))", buildrootLabel)},
		"--output=starlark",
		"--output=starlark",
		"--starlark:file="+cqueryFileRelpath)
		"--starlark:file="+cqueryFileRelpath)
	err = ioutil.WriteFile(
	err = ioutil.WriteFile(filepath.Join(intermediatesDirPath, "cquery.out"),
		absolutePath(filepath.Join(context.intermediatesDir(), "cquery.out")),
		[]byte(cqueryOutput), 0666)
		[]byte(cqueryOutput), 0666)
	if err != nil {
	if err != nil {
		return err
		return err
@@ -661,11 +706,13 @@ func (context *bazelContext) InvokeBazel() error {
	//
	//
	// TODO(cparsons): Use --target_pattern_file to avoid command line limits.
	// TODO(cparsons): Use --target_pattern_file to avoid command line limits.
	var aqueryOutput string
	var aqueryOutput string
	aqueryOutput, _, err = context.issueBazelCommand(bazel.AqueryBuildRootRunName, "aquery",
	aqueryOutput, _, err = context.issueBazelCommand(
		[]string{fmt.Sprintf("deps(%s)", buildrootLabel),
		context.paths,
		bazel.AqueryBuildRootRunName,
		bazelCommand{"aquery", fmt.Sprintf("deps(%s)", buildrootLabel)},
		// Use jsonproto instead of proto; actual proto parsing would require a dependency on Bazel's
		// Use jsonproto instead of proto; actual proto parsing would require a dependency on Bazel's
		// proto sources, which would add a number of unnecessary dependencies.
		// proto sources, which would add a number of unnecessary dependencies.
			"--output=jsonproto"})
		"--output=jsonproto")


	if err != nil {
	if err != nil {
		return err
		return err
@@ -679,8 +726,10 @@ func (context *bazelContext) InvokeBazel() error {
	// Issue a build command of the phony root to generate symlink forests for dependencies of the
	// Issue a build command of the phony root to generate symlink forests for dependencies of the
	// Bazel build. This is necessary because aquery invocations do not generate this symlink forest,
	// Bazel build. This is necessary because aquery invocations do not generate this symlink forest,
	// but some of symlinks may be required to resolve source dependencies of the build.
	// but some of symlinks may be required to resolve source dependencies of the build.
	_, _, err = context.issueBazelCommand(bazel.BazelBuildPhonyRootRunName, "build",
	_, _, err = context.issueBazelCommand(
		[]string{"//:phonyroot"})
		context.paths,
		bazel.BazelBuildPhonyRootRunName,
		bazelCommand{"build", "//:phonyroot"})


	if err != nil {
	if err != nil {
		return err
		return err
@@ -696,7 +745,7 @@ func (context *bazelContext) BuildStatementsToRegister() []bazel.BuildStatement
}
}


func (context *bazelContext) OutputBase() string {
func (context *bazelContext) OutputBase() string {
	return context.outputBase
	return context.paths.outputBase
}
}


// Singleton used for registering BUILD file ninja dependencies (needed
// Singleton used for registering BUILD file ninja dependencies (needed
+118 −0
Original line number Original line Diff line number Diff line
package android

import (
	"os"
	"path/filepath"
	"reflect"
	"testing"
)

func TestRequestResultsAfterInvokeBazel(t *testing.T) {
	label := "//foo:bar"
	arch := Arm64
	bazelContext, _ := testBazelContext(t, map[bazelCommand]string{
		bazelCommand{command: "cquery", expression: "kind(rule, deps(//:buildroot))"}: `@sourceroot//foo:bar|arm64>>out/foo/bar.txt`,
	})
	g, ok := bazelContext.GetOutputFiles(label, arch)
	if ok {
		t.Errorf("Did not expect cquery results prior to running InvokeBazel(), but got %s", g)
	}
	err := bazelContext.InvokeBazel()
	if err != nil {
		t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
	}
	g, ok = bazelContext.GetOutputFiles(label, arch)
	if !ok {
		t.Errorf("Expected cquery results after running InvokeBazel(), but got none")
	} else if w := []string{"out/foo/bar.txt"}; !reflect.DeepEqual(w, g) {
		t.Errorf("Expected output %s, got %s", w, g)
	}
}

func TestInvokeBazelWritesBazelFiles(t *testing.T) {
	bazelContext, baseDir := testBazelContext(t, map[bazelCommand]string{})
	err := bazelContext.InvokeBazel()
	if err != nil {
		t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
	}
	if _, err := os.Stat(filepath.Join(baseDir, "bazel", "main.bzl")); os.IsNotExist(err) {
		t.Errorf("Expected main.bzl to exist, but it does not")
	} else if err != nil {
		t.Errorf("Unexpected error stating main.bzl %s", err)
	}

	if _, err := os.Stat(filepath.Join(baseDir, "bazel", "BUILD.bazel")); os.IsNotExist(err) {
		t.Errorf("Expected BUILD.bazel to exist, but it does not")
	} else if err != nil {
		t.Errorf("Unexpected error stating BUILD.bazel %s", err)
	}

	if _, err := os.Stat(filepath.Join(baseDir, "bazel", "WORKSPACE.bazel")); os.IsNotExist(err) {
		t.Errorf("Expected WORKSPACE.bazel to exist, but it does not")
	} else if err != nil {
		t.Errorf("Unexpected error stating WORKSPACE.bazel %s", err)
	}
}

func TestInvokeBazelPopulatesBuildStatements(t *testing.T) {
	bazelContext, _ := testBazelContext(t, map[bazelCommand]string{
		bazelCommand{command: "aquery", expression: "deps(//:buildroot)"}: `
{
  "artifacts": [{
    "id": 1,
    "pathFragmentId": 1
  }, {
    "id": 2,
    "pathFragmentId": 2
  }],
  "actions": [{
    "targetId": 1,
    "actionKey": "x",
    "mnemonic": "x",
    "arguments": ["touch", "foo"],
    "inputDepSetIds": [1],
    "outputIds": [1],
    "primaryOutputId": 1
  }],
  "depSetOfFiles": [{
    "id": 1,
    "directArtifactIds": [1, 2]
  }],
  "pathFragments": [{
    "id": 1,
    "label": "one"
  }, {
    "id": 2,
    "label": "two"
  }]
}`,
	})
	err := bazelContext.InvokeBazel()
	if err != nil {
		t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
	}

	got := bazelContext.BuildStatementsToRegister()
	if want := 1; len(got) != want {
		t.Errorf("Expected %d registered build statements, got %#v", want, got)
	}
}

func testBazelContext(t *testing.T, bazelCommandResults map[bazelCommand]string) (*bazelContext, string) {
	t.Helper()
	p := bazelPaths{
		buildDir:     t.TempDir(),
		outputBase:   "outputbase",
		workspaceDir: "workspace_dir",
	}
	aqueryCommand := bazelCommand{command: "aquery", expression: "deps(//:buildroot)"}
	if _, exists := bazelCommandResults[aqueryCommand]; !exists {
		bazelCommandResults[aqueryCommand] = "{}\n"
	}
	runner := &mockBazelRunner{bazelCommandResults: bazelCommandResults}
	return &bazelContext{
		bazelRunner: runner,
		paths:       &p,
		requests:    map[cqueryKey]bool{},
	}, p.buildDir
}