diff options
author | Patrice Arruda <patricearruda@google.com> | 2019-04-22 17:12:02 -0700 |
---|---|---|
committer | Luca Stefani <luca.stefani.ge1@gmail.com> | 2019-09-04 15:32:01 +0200 |
commit | 5b6688cf3e99b8c3a15d4e0cdb260b9ffc0be19f (patch) | |
tree | fdc878159c88f01a8dc214e19ca0e0957ecf45e9 /ui | |
parent | 40197a1070e70a57d24f5d82763454ecc814bae9 (diff) | |
download | build_soong-5b6688cf3e99b8c3a15d4e0cdb260b9ffc0be19f.tar.gz build_soong-5b6688cf3e99b8c3a15d4e0cdb260b9ffc0be19f.tar.bz2 build_soong-5b6688cf3e99b8c3a15d4e0cdb260b9ffc0be19f.zip |
soong_ui: Add build actions commands in soong_ui.
Add the following build actions {BUILD_MODULES_IN_A_DIRECTORY,
BUILD_MODULES_IN_DIRECTORIES} in soong_ui config so the bash code version of
build commands (m, mm, mma, mmm, mmma) in build/make/envsetup.sh can be deprecated.
This is to allow up to date bug fixes on the build commands.
Bug: b/130049705
Test: Unit test cases
Change-Id: I772db1d4e9c1da5273374d1994eb5e8f17cd52f2
Diffstat (limited to 'ui')
-rw-r--r-- | ui/build/config.go | 226 | ||||
-rw-r--r-- | ui/build/config_test.go | 878 | ||||
-rw-r--r-- | ui/build/util.go | 11 |
3 files changed, 1109 insertions, 6 deletions
diff --git a/ui/build/config.go b/ui/build/config.go index 7eb3a725..a3951278 100644 --- a/ui/build/config.go +++ b/ui/build/config.go @@ -62,6 +62,28 @@ type configImpl struct { const srcDirFileCheck = "build/soong/root.bp" +type BuildAction uint + +const ( + // Builds all of the modules and their dependencies of a specified directory, relative to the root + // directory of the source tree. + BUILD_MODULES_IN_A_DIRECTORY BuildAction = iota + + // Builds all of the modules and their dependencies of a list of specified directories. All specified + // directories are relative to the root directory of the source tree. + BUILD_MODULES_IN_DIRECTORIES +) + +// checkTopDir validates that the current directory is at the root directory of the source tree. +func checkTopDir(ctx Context) { + if _, err := os.Stat(srcDirFileCheck); err != nil { + if os.IsNotExist(err) { + ctx.Fatalf("Current working directory must be the source tree. %q not found.", srcDirFileCheck) + } + ctx.Fatalln("Error verifying tree state:", err) + } +} + func NewConfig(ctx Context, args ...string) Config { ret := &configImpl{ environ: OsEnvironment(), @@ -155,12 +177,7 @@ func NewConfig(ctx Context, args ...string) Config { ret.environ.Set("TMPDIR", absPath(ctx, ret.TempDir())) // Precondition: the current directory is the top of the source tree - if _, err := os.Stat(srcDirFileCheck); err != nil { - if os.IsNotExist(err) { - log.Fatalf("Current working directory must be the source tree. %q not found", srcDirFileCheck) - } - log.Fatalln("Error verifying tree state:", err) - } + checkTopDir(ctx) if srcDir := absPath(ctx, "."); strings.ContainsRune(srcDir, ' ') { log.Println("You are building in a directory whose absolute path contains a space character:") @@ -230,6 +247,203 @@ func NewConfig(ctx Context, args ...string) Config { return Config{ret} } +// NewBuildActionConfig returns a build configuration based on the build action. The arguments are +// processed based on the build action and extracts any arguments that belongs to the build action. +func NewBuildActionConfig(action BuildAction, dir string, buildDependencies bool, ctx Context, args ...string) Config { + return NewConfig(ctx, getConfigArgs(action, dir, buildDependencies, ctx, args)...) +} + +// getConfigArgs processes the command arguments based on the build action and creates a set of new +// arguments to be accepted by Config. +func getConfigArgs(action BuildAction, dir string, buildDependencies bool, ctx Context, args []string) []string { + // The next block of code verifies that the current directory is the root directory of the source + // tree. It then finds the relative path of dir based on the root directory of the source tree + // and verify that dir is inside of the source tree. + checkTopDir(ctx) + topDir, err := os.Getwd() + if err != nil { + ctx.Fatalf("Error retrieving top directory: %v", err) + } + dir, err = filepath.Abs(dir) + if err != nil { + ctx.Fatalf("Unable to find absolute path %s: %v", dir, err) + } + relDir, err := filepath.Rel(topDir, dir) + if err != nil { + ctx.Fatalf("Unable to find relative path %s of %s: %v", relDir, topDir, err) + } + // If there are ".." in the path, it's not in the source tree. + if strings.Contains(relDir, "..") { + ctx.Fatalf("Directory %s is not under the source tree %s", dir, topDir) + } + + configArgs := args[:] + + // If the arguments contains GET-INSTALL-PATH, change the target name prefix from MODULES-IN- to + // GET-INSTALL-PATH-IN- to extract the installation path instead of building the modules. + targetNamePrefix := "MODULES-IN-" + if inList("GET-INSTALL-PATH", configArgs) { + targetNamePrefix = "GET-INSTALL-PATH-IN-" + configArgs = removeFromList("GET-INSTALL-PATH", configArgs) + } + + var buildFiles []string + var targets []string + + switch action { + case BUILD_MODULES_IN_A_DIRECTORY: + // If dir is the root source tree, all the modules are built of the source tree are built so + // no need to find the build file. + if topDir == dir { + break + } + // Find the build file from the directory where the build action was triggered by traversing up + // the source tree. If a blank build filename is returned, simply use the directory where the build + // action was invoked. + buildFile := findBuildFile(ctx, relDir) + if buildFile == "" { + buildFile = filepath.Join(relDir, "Android.mk") + } + buildFiles = []string{buildFile} + targets = []string{convertToTarget(filepath.Dir(buildFile), targetNamePrefix)} + case BUILD_MODULES_IN_DIRECTORIES: + newConfigArgs, dirs := splitArgs(configArgs) + configArgs = newConfigArgs + targets, buildFiles = getTargetsFromDirs(ctx, relDir, dirs, targetNamePrefix) + } + + // This is to support building modules without building their dependencies. Soon, this will be + // deprecated. + if !buildDependencies && len(buildFiles) > 0 { + if err := os.Setenv("ONE_SHOT_MAKEFILE", strings.Join(buildFiles, " ")); err != nil { + ctx.Fatalf("Unable to set ONE_SHOT_MAKEFILE environment variable: %v", err) + } + } + + // Tidy only override all other specified targets. + tidyOnly := os.Getenv("WITH_TIDY_ONLY") + if tidyOnly == "true" || tidyOnly == "1" { + configArgs = append(configArgs, "tidy_only") + } else { + configArgs = append(configArgs, targets...) + } + + return configArgs +} + +// convertToTarget replaces "/" to "-" in dir and pre-append the targetNamePrefix to the target name. +func convertToTarget(dir string, targetNamePrefix string) string { + return targetNamePrefix + strings.ReplaceAll(dir, "/", "-") +} + +// findBuildFile finds a build file (makefile or blueprint file) by looking at dir first. If not +// found, go up one level and repeat again until one is found and the path of that build file +// relative to the root directory of the source tree is returned. The returned filename of build +// file is "Android.mk". If one was not found, a blank string is returned. +func findBuildFile(ctx Context, dir string) string { + // If the string is empty, assume it is top directory of the source tree. + if dir == "" { + return "" + } + + for ; dir != "."; dir = filepath.Dir(dir) { + for _, buildFile := range []string{"Android.bp", "Android.mk"} { + _, err := os.Stat(filepath.Join(dir, buildFile)) + if err == nil { + // Returning the filename Android.mk as it might be used for ONE_SHOT_MAKEFILE variable. + return filepath.Join(dir, "Android.mk") + } + if !os.IsNotExist(err) { + ctx.Fatalf("Error retrieving the build file stats: %v", err) + } + } + } + + return "" +} + +// splitArgs iterates over the arguments list and splits into two lists: arguments and directories. +func splitArgs(args []string) (newArgs []string, dirs []string) { + specialArgs := map[string]bool{ + "showcommands": true, + "snod": true, + "dist": true, + "checkbuild": true, + } + + newArgs = []string{} + dirs = []string{} + + for _, arg := range args { + // It's a dash argument if it starts with "-" or it's a key=value pair, it's not a directory. + if strings.IndexRune(arg, '-') == 0 || strings.IndexRune(arg, '=') != -1 { + newArgs = append(newArgs, arg) + continue + } + + if _, ok := specialArgs[arg]; ok { + newArgs = append(newArgs, arg) + continue + } + + dirs = append(dirs, arg) + } + + return newArgs, dirs +} + +// getTargetsFromDirs iterates over the dirs list and creates a list of targets to build. If a +// directory from the dirs list does not exist, a fatal error is raised. relDir is related to the +// source root tree where the build action command was invoked. Each directory is validated if the +// build file can be found and follows the format "dir1:target1,target2,...". Target is optional. +func getTargetsFromDirs(ctx Context, relDir string, dirs []string, targetNamePrefix string) (targets []string, buildFiles []string) { + for _, dir := range dirs { + // The directory may have specified specific modules to build. ":" is the separator to separate + // the directory and the list of modules. + s := strings.Split(dir, ":") + l := len(s) + if l > 2 { // more than one ":" was specified. + ctx.Fatalf("%s not in proper directory:target1,target2,... format (\":\" was specified more than once)", dir) + } + + dir = filepath.Join(relDir, s[0]) + if _, err := os.Stat(dir); err != nil { + ctx.Fatalf("couldn't find directory %s", dir) + } + + // Verify that if there are any targets specified after ":". Each target is separated by ",". + var newTargets []string + if l == 2 && s[1] != "" { + newTargets = strings.Split(s[1], ",") + if inList("", newTargets) { + ctx.Fatalf("%s not in proper directory:target1,target2,... format", dir) + } + } + + buildFile := findBuildFile(ctx, dir) + if buildFile == "" { + ctx.Fatalf("Build file not found for %s directory", dir) + } + buildFileDir := filepath.Dir(buildFile) + + // If there are specified targets, find the build file in the directory. If dir does not + // contain the build file, bail out as it is required for one shot build. If there are no + // target specified, build all the modules in dir (or the closest one in the dir path). + if len(newTargets) > 0 { + if buildFileDir != dir { + ctx.Fatalf("Couldn't locate a build file from %s directory", dir) + } + } else { + newTargets = []string{convertToTarget(buildFileDir, targetNamePrefix)} + } + + buildFiles = append(buildFiles, buildFile) + targets = append(targets, newTargets...) + } + + return targets, buildFiles +} + func (c *configImpl) parseArgs(ctx Context, args []string) { for i := 0; i < len(args); i++ { arg := strings.TrimSpace(args[i]) diff --git a/ui/build/config_test.go b/ui/build/config_test.go index 1d23fec5..1ef54566 100644 --- a/ui/build/config_test.go +++ b/ui/build/config_test.go @@ -17,6 +17,10 @@ package build import ( "bytes" "context" + "fmt" + "io/ioutil" + "os" + "path/filepath" "reflect" "strings" "testing" @@ -172,3 +176,877 @@ func TestConfigParseArgsVars(t *testing.T) { }) } } + +func TestConfigCheckTopDir(t *testing.T) { + ctx := testContext() + buildRootDir := filepath.Dir(srcDirFileCheck) + expectedErrStr := fmt.Sprintf("Current working directory must be the source tree. %q not found.", srcDirFileCheck) + + tests := []struct { + // ********* Setup ********* + // Test description. + description string + + // ********* Action ********* + // If set to true, the build root file is created. + rootBuildFile bool + + // The current path where Soong is being executed. + path string + + // ********* Validation ********* + // Expecting error and validate the error string against expectedErrStr. + wantErr bool + }{{ + description: "current directory is the root source tree", + rootBuildFile: true, + path: ".", + wantErr: false, + }, { + description: "one level deep in the source tree", + rootBuildFile: true, + path: "1", + wantErr: true, + }, { + description: "very deep in the source tree", + rootBuildFile: true, + path: "1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7", + wantErr: true, + }, { + description: "outside of source tree", + rootBuildFile: false, + path: "1/2/3/4/5", + wantErr: true, + }} + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + defer logger.Recover(func(err error) { + if !tt.wantErr { + t.Fatalf("Got unexpected error: %v", err) + } + if expectedErrStr != err.Error() { + t.Fatalf("expected %s, got %s", expectedErrStr, err.Error()) + } + }) + + // Create the root source tree. + rootDir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(rootDir) + + // Create the build root file. This is to test if topDir returns an error if the build root + // file does not exist. + if tt.rootBuildFile { + dir := filepath.Join(rootDir, buildRootDir) + if err := os.MkdirAll(dir, 0755); err != nil { + t.Errorf("failed to create %s directory: %v", dir, err) + } + f := filepath.Join(rootDir, srcDirFileCheck) + if err := ioutil.WriteFile(f, []byte{}, 0644); err != nil { + t.Errorf("failed to create file %s: %v", f, err) + } + } + + // Next block of code is to set the current directory. + dir := rootDir + if tt.path != "" { + dir = filepath.Join(dir, tt.path) + if err := os.MkdirAll(dir, 0755); err != nil { + t.Errorf("failed to create %s directory: %v", dir, err) + } + } + curDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get the current directory: %v", err) + } + defer func() { os.Chdir(curDir) }() + + if err := os.Chdir(dir); err != nil { + t.Fatalf("failed to change directory to %s: %v", dir, err) + } + + checkTopDir(ctx) + }) + } +} + +func TestConfigConvertToTarget(t *testing.T) { + tests := []struct { + // ********* Setup ********* + // Test description. + description string + + // ********* Action ********* + // The current directory where Soong is being executed. + dir string + + // The current prefix string to be pre-appended to the target. + prefix string + + // ********* Validation ********* + // The expected target to be invoked in ninja. + expectedTarget string + }{{ + description: "one level directory in source tree", + dir: "test1", + prefix: "MODULES-IN-", + expectedTarget: "MODULES-IN-test1", + }, { + description: "multiple level directories in source tree", + dir: "test1/test2/test3/test4", + prefix: "GET-INSTALL-PATH-IN-", + expectedTarget: "GET-INSTALL-PATH-IN-test1-test2-test3-test4", + }} + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + target := convertToTarget(tt.dir, tt.prefix) + if target != tt.expectedTarget { + t.Errorf("expected %s, got %s for target", tt.expectedTarget, target) + } + }) + } +} + +func setTop(t *testing.T, dir string) func() { + curDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get current directory: %v", err) + } + if err := os.Chdir(dir); err != nil { + t.Fatalf("failed to change directory to top dir %s: %v", dir, err) + } + return func() { os.Chdir(curDir) } +} + +func createBuildFiles(t *testing.T, topDir string, buildFiles []string) { + for _, buildFile := range buildFiles { + buildFile = filepath.Join(topDir, buildFile) + if err := ioutil.WriteFile(buildFile, []byte{}, 0644); err != nil { + t.Errorf("failed to create file %s: %v", buildFile, err) + } + } +} + +func createDirectories(t *testing.T, topDir string, dirs []string) { + for _, dir := range dirs { + dir = filepath.Join(topDir, dir) + if err := os.MkdirAll(dir, 0755); err != nil { + t.Errorf("failed to create %s directory: %v", dir, err) + } + } +} + +func TestConfigGetTargets(t *testing.T) { + ctx := testContext() + tests := []struct { + // ********* Setup ********* + // Test description. + description string + + // Directories that exist in the source tree. + dirsInTrees []string + + // Build files that exists in the source tree. + buildFiles []string + + // ********* Action ********* + // Directories passed in to soong_ui. + dirs []string + + // Current directory that the user executed the build action command. + curDir string + + // ********* Validation ********* + // Expected targets from the function. + expectedTargets []string + + // Expected build from the build system. + expectedBuildFiles []string + + // Expecting error from running test case. + errStr string + }{{ + description: "one target dir specified", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/3/Android.bp"}, + dirs: []string{"1/2/3"}, + curDir: "0", + expectedTargets: []string{"MODULES-IN-0-1-2-3"}, + expectedBuildFiles: []string{"0/1/2/3/Android.mk"}, + }, { + description: "one target dir specified, build file does not exist", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{}, + dirs: []string{"1/2/3"}, + curDir: "0", + errStr: "Build file not found for 0/1/2/3 directory", + }, { + description: "one target dir specified, invalid targets specified", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{}, + dirs: []string{"1/2/3:t1:t2"}, + curDir: "0", + errStr: "1/2/3:t1:t2 not in proper directory:target1,target2,... format (\":\" was specified more than once)", + }, { + description: "one target dir specified, no targets specified but has colon", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/3/Android.bp"}, + dirs: []string{"1/2/3:"}, + curDir: "0", + expectedTargets: []string{"MODULES-IN-0-1-2-3"}, + expectedBuildFiles: []string{"0/1/2/3/Android.mk"}, + }, { + description: "one target dir specified, two targets specified", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/3/Android.bp"}, + dirs: []string{"1/2/3:t1,t2"}, + curDir: "0", + expectedTargets: []string{"t1", "t2"}, + expectedBuildFiles: []string{"0/1/2/3/Android.mk"}, + }, { + description: "one target dir specified, no targets and has a comma", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/3/Android.bp"}, + dirs: []string{"1/2/3:,"}, + curDir: "0", + errStr: "0/1/2/3 not in proper directory:target1,target2,... format", + }, { + description: "one target dir specified, improper targets defined", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/3/Android.bp"}, + dirs: []string{"1/2/3:,t1"}, + curDir: "0", + errStr: "0/1/2/3 not in proper directory:target1,target2,... format", + }, { + description: "one target dir specified, blank target", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/3/Android.bp"}, + dirs: []string{"1/2/3:t1,"}, + curDir: "0", + errStr: "0/1/2/3 not in proper directory:target1,target2,... format", + }, { + description: "one target dir specified, many targets specified", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/3/Android.bp"}, + dirs: []string{"1/2/3:t1,t2,t3,t4,t5,t6,t7,t8,t9,t10"}, + curDir: "0", + expectedTargets: []string{"t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "t10"}, + expectedBuildFiles: []string{"0/1/2/3/Android.mk"}, + }, { + description: "one target dir specified, one target specified, build file does not exist", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{}, + dirs: []string{"1/2/3:t1"}, + curDir: "0", + errStr: "Build file not found for 0/1/2/3 directory", + }, { + description: "one target dir specified, one target specified, build file not in target dir", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/Android.mk"}, + dirs: []string{"1/2/3:t1"}, + curDir: "0", + errStr: "Couldn't locate a build file from 0/1/2/3 directory", + }, { + description: "one target dir specified, build file not in target dir", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/Android.mk"}, + dirs: []string{"1/2/3"}, + curDir: "0", + expectedTargets: []string{"MODULES-IN-0-1-2"}, + expectedBuildFiles: []string{"0/1/2/Android.mk"}, + }, { + description: "multiple targets dir specified, targets specified", + dirsInTrees: []string{"0/1/2/3", "0/3/4"}, + buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"}, + dirs: []string{"1/2/3:t1,t2", "3/4:t3,t4,t5"}, + curDir: "0", + expectedTargets: []string{"t1", "t2", "t3", "t4", "t5"}, + expectedBuildFiles: []string{"0/1/2/3/Android.mk", "0/3/4/Android.mk"}, + }, { + description: "multiple targets dir specified, one directory has targets specified", + dirsInTrees: []string{"0/1/2/3", "0/3/4"}, + buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"}, + dirs: []string{"1/2/3:t1,t2", "3/4"}, + curDir: "0", + expectedTargets: []string{"t1", "t2", "MODULES-IN-0-3-4"}, + expectedBuildFiles: []string{"0/1/2/3/Android.mk", "0/3/4/Android.mk"}, + }, { + description: "two dirs specified, only one dir exist", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/3/Android.mk"}, + dirs: []string{"1/2/3:t1", "3/4"}, + curDir: "0", + errStr: "couldn't find directory 0/3/4", + }, { + description: "multiple targets dirs specified at root source tree", + dirsInTrees: []string{"0/1/2/3", "0/3/4"}, + buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"}, + dirs: []string{"0/1/2/3:t1,t2", "0/3/4"}, + curDir: ".", + expectedTargets: []string{"t1", "t2", "MODULES-IN-0-3-4"}, + expectedBuildFiles: []string{"0/1/2/3/Android.mk", "0/3/4/Android.mk"}, + }, { + description: "no directories specified", + dirsInTrees: []string{"0/1/2/3", "0/3/4"}, + buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"}, + dirs: []string{}, + curDir: ".", + }} + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + defer logger.Recover(func(err error) { + if tt.errStr == "" { + t.Fatalf("Got unexpected error: %v", err) + } + if tt.errStr != err.Error() { + t.Errorf("expected %s, got %s", tt.errStr, err.Error()) + } + }) + + // Create the root source tree. + topDir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer os.RemoveAll(topDir) + + createDirectories(t, topDir, tt.dirsInTrees) + createBuildFiles(t, topDir, tt.buildFiles) + r := setTop(t, topDir) + defer r() + + targets, buildFiles := getTargetsFromDirs(ctx, tt.curDir, tt.dirs, "MODULES-IN-") + if !reflect.DeepEqual(targets, tt.expectedTargets) { + t.Errorf("expected %v, got %v for targets", tt.expectedTargets, targets) + } + if !reflect.DeepEqual(buildFiles, tt.expectedBuildFiles) { + t.Errorf("expected %v, got %v for build files", tt.expectedBuildFiles, buildFiles) + } + + // If the execution reached here and there was an expected error code, the unit test case failed. + if tt.errStr != "" { + t.Errorf("expecting error %s", tt.errStr) + } + }) + } +} + +func TestConfigFindBuildFile(t *testing.T) { + ctx := testContext() + + tests := []struct { + // ********* Setup ********* + // Test description. + description string + + // Array of build files to create in dir. + buildFiles []string + + // ********* Action ********* + // Directory to create, also the base directory is where findBuildFile is invoked. + dir string + + // ********* Validation ********* + // Expected build file path to find. + expectedBuildFile string + }{{ + description: "build file exists at leaf directory", + buildFiles: []string{"1/2/3/Android.bp"}, + dir: "1/2/3", + expectedBuildFile: "1/2/3/Android.mk", + }, { + description: "build file exists in all directory paths", + buildFiles: []string{"1/Android.mk", "1/2/Android.mk", "1/2/3/Android.mk"}, + dir: "1/2/3", + expectedBuildFile: "1/2/3/Android.mk", + }, { + description: "build file does not exist in all directory paths", + buildFiles: []string{}, + dir: "1/2/3", + expectedBuildFile: "", + }, { + description: "build file exists only at top directory", + buildFiles: []string{"Android.bp"}, + dir: "1/2/3", + expectedBuildFile: "", + }, { + description: "build file exist in a subdirectory", + buildFiles: []string{"1/2/Android.bp"}, + dir: "1/2/3", + expectedBuildFile: "1/2/Android.mk", + }, { + description: "build file exists in a subdirectory", + buildFiles: []string{"1/Android.mk"}, + dir: "1/2/3", + expectedBuildFile: "1/Android.mk", + }, { + description: "top directory", + buildFiles: []string{"Android.bp"}, + dir: ".", + expectedBuildFile: "", + }} + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + defer logger.Recover(func(err error) { + t.Fatalf("Got unexpected error: %v", err) + }) + + topDir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer os.RemoveAll(topDir) + + if tt.dir != "" { + createDirectories(t, topDir, []string{tt.dir}) + } + + createBuildFiles(t, topDir, tt.buildFiles) + + curDir, err := os.Getwd() + if err != nil { + t.Fatalf("Could not get working directory: %v", err) + } + defer func() { os.Chdir(curDir) }() + if err := os.Chdir(topDir); err != nil { + t.Fatalf("Could not change top dir to %s: %v", topDir, err) + } + + buildFile := findBuildFile(ctx, tt.dir) + if buildFile != tt.expectedBuildFile { + t.Errorf("expected %q, got %q for build file", tt.expectedBuildFile, buildFile) + } + }) + } +} + +func TestConfigSplitArgs(t *testing.T) { + tests := []struct { + // ********* Setup ********* + // Test description. + description string + + // ********* Action ********* + // Arguments passed in to soong_ui. + args []string + + // ********* Validation ********* + // Expected newArgs list after extracting the directories. + expectedNewArgs []string + + // Expected directories + expectedDirs []string + }{{ + description: "flags but no directories specified", + args: []string{"showcommands", "-j", "-k"}, + expectedNewArgs: []string{"showcommands", "-j", "-k"}, + expectedDirs: []string{}, + }, { + description: "flags and one directory specified", + args: []string{"snod", "-j", "dir:target1,target2"}, + expectedNewArgs: []string{"snod", "-j"}, + expectedDirs: []string{"dir:target1,target2"}, + }, { + description: "flags and directories specified", + args: []string{"dist", "-k", "dir1", "dir2:target1,target2"}, + expectedNewArgs: []string{"dist", "-k"}, + expectedDirs: []string{"dir1", "dir2:target1,target2"}, + }, { + description: "only directories specified", + args: []string{"dir1", "dir2", "dir3:target1,target2"}, + expectedNewArgs: []string{}, + expectedDirs: []string{"dir1", "dir2", "dir3:target1,target2"}, + }} + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + args, dirs := splitArgs(tt.args) + if !reflect.DeepEqual(tt.expectedNewArgs, args) { + t.Errorf("expected %v, got %v for arguments", tt.expectedNewArgs, args) + } + if !reflect.DeepEqual(tt.expectedDirs, dirs) { + t.Errorf("expected %v, got %v for directories", tt.expectedDirs, dirs) + } + }) + } +} + +type envVar struct { + name string + value string +} + +type buildActionTestCase struct { + // ********* Setup ********* + // Test description. + description string + + // Directories that exist in the source tree. + dirsInTrees []string + + // Build files that exists in the source tree. + buildFiles []string + + // ********* Action ********* + // Arguments passed in to soong_ui. + args []string + + // Directory where the build action was invoked. + curDir string + + // WITH_TIDY_ONLY environment variable specified. + tidyOnly string + + // ********* Validation ********* + // Expected arguments to be in Config instance. + expectedArgs []string + + // Expected environment variables to be set. + expectedEnvVars []envVar +} + +func testGetConfigArgs(t *testing.T, tt buildActionTestCase, action BuildAction, buildDependencies bool) { + ctx := testContext() + + // Environment variables to set it to blank on every test case run. + resetEnvVars := []string{ + "ONE_SHOT_MAKEFILE", + "WITH_TIDY_ONLY", + } + + for _, name := range resetEnvVars { + if err := os.Unsetenv(name); err != nil { + t.Fatalf("failed to unset environment variable %s: %v", name, err) + } + } + if tt.tidyOnly != "" { + if err := os.Setenv("WITH_TIDY_ONLY", tt.tidyOnly); err != nil { + t.Errorf("failed to set WITH_TIDY_ONLY to %s: %v", tt.tidyOnly, err) + } + } + + // Create the root source tree. + topDir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer os.RemoveAll(topDir) + + createDirectories(t, topDir, tt.dirsInTrees) + createBuildFiles(t, topDir, tt.buildFiles) + + r := setTop(t, topDir) + defer r() + + // The next block is to create the root build file. + rootBuildFileDir := filepath.Dir(srcDirFileCheck) + if err := os.MkdirAll(rootBuildFileDir, 0755); err != nil { + t.Fatalf("Failed to create %s directory: %v", rootBuildFileDir, err) + } + + if err := ioutil.WriteFile(srcDirFileCheck, []byte{}, 0644); err != nil { + t.Fatalf("failed to create %s file: %v", srcDirFileCheck, err) + } + + args := getConfigArgs(action, tt.curDir, buildDependencies, ctx, tt.args) + if !reflect.DeepEqual(tt.expectedArgs, args) { + t.Fatalf("expected %v, got %v for config arguments", tt.expectedArgs, args) + } + + for _, env := range tt.expectedEnvVars { + if val := os.Getenv(env.name); val != env.value { + t.Errorf("expecting %s, got %s for environment variable %s", env.value, val, env.name) + } + } +} + +// TODO: Remove this test case once mm shell build command has been deprecated. +func TestGetConfigArgsBuildModulesInDirecotoryNoDeps(t *testing.T) { + tests := []buildActionTestCase{{ + description: "normal execution in a directory", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/2/Android.mk"}, + args: []string{"-j", "-k", "showcommands", "fake-module"}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"-j", "-k", "showcommands", "fake-module", "MODULES-IN-0-1-2"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/2/Android.mk"}}, + }, { + description: "makefile in parent directory", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/Android.mk"}, + args: []string{}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"MODULES-IN-0-1"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/Android.mk"}}, + }, { + description: "build file not found", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{}, + args: []string{}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"MODULES-IN-0-1-2"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/2/Android.mk"}}, + }, { + description: "build action executed at root directory", + dirsInTrees: []string{}, + buildFiles: []string{}, + args: []string{}, + curDir: ".", + tidyOnly: "", + expectedArgs: []string{}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: ""}}, + }, { + description: "GET-INSTALL-PATH specified,", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/Android.mk"}, + args: []string{"GET-INSTALL-PATH"}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"GET-INSTALL-PATH-IN-0-1"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/Android.mk"}}, + }, { + description: "tidy only environment variable specified,", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/Android.mk"}, + args: []string{"GET-INSTALL-PATH"}, + curDir: "0/1/2", + tidyOnly: "true", + expectedArgs: []string{"tidy_only"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/Android.mk"}}, + }} + for _, tt := range tests { + t.Run("build action BUILD_MODULES_IN_DIR without their dependencies, "+tt.description, func(t *testing.T) { + testGetConfigArgs(t, tt, BUILD_MODULES_IN_A_DIRECTORY, false) + }) + } +} + +func TestGetConfigArgsBuildModulesInDirectory(t *testing.T) { + tests := []buildActionTestCase{{ + description: "normal execution in a directory", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/2/Android.mk"}, + args: []string{"fake-module"}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"fake-module", "MODULES-IN-0-1-2"}, + expectedEnvVars: []envVar{}, + }, { + description: "build file in parent directory", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/Android.mk"}, + args: []string{}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"MODULES-IN-0-1"}, + expectedEnvVars: []envVar{}, + }, + { + description: "build file in parent directory, multiple module names passed in", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/Android.mk"}, + args: []string{"fake-module1", "fake-module2", "fake-module3"}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"fake-module1", "fake-module2", "fake-module3", "MODULES-IN-0-1"}, + expectedEnvVars: []envVar{}, + }, { + description: "build file in 2nd level parent directory", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/Android.bp"}, + args: []string{}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"MODULES-IN-0"}, + expectedEnvVars: []envVar{}, + }, { + description: "build action executed at root directory", + dirsInTrees: []string{}, + buildFiles: []string{}, + args: []string{}, + curDir: ".", + tidyOnly: "", + expectedArgs: []string{}, + expectedEnvVars: []envVar{}, + }, { + description: "build file not found - no error is expected to return", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{}, + args: []string{}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"MODULES-IN-0-1-2"}, + expectedEnvVars: []envVar{}, + }, { + description: "GET-INSTALL-PATH specified,", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/Android.mk"}, + args: []string{"GET-INSTALL-PATH", "-j", "-k", "GET-INSTALL-PATH"}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"-j", "-k", "GET-INSTALL-PATH-IN-0-1"}, + expectedEnvVars: []envVar{}, + }, { + description: "tidy only environment variable specified,", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/Android.mk"}, + args: []string{"GET-INSTALL-PATH"}, + curDir: "0/1/2", + tidyOnly: "true", + expectedArgs: []string{"tidy_only"}, + expectedEnvVars: []envVar{}, + }, { + description: "normal execution in root directory with args", + dirsInTrees: []string{}, + buildFiles: []string{}, + args: []string{"-j", "-k", "fake_module"}, + curDir: "", + tidyOnly: "", + expectedArgs: []string{"-j", "-k", "fake_module"}, + expectedEnvVars: []envVar{}, + }} + for _, tt := range tests { + t.Run("build action BUILD_MODULES_IN_DIR, "+tt.description, func(t *testing.T) { + testGetConfigArgs(t, tt, BUILD_MODULES_IN_A_DIRECTORY, true) + }) + } +} + +// TODO: Remove this test case once mmm shell build command has been deprecated. +func TestGetConfigArgsBuildModulesInDirectoriesNoDeps(t *testing.T) { + tests := []buildActionTestCase{{ + description: "normal execution in a directory", + dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"}, + buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"}, + args: []string{"3.1/:t1,t2", "3.2/:t3,t4", "3.3/:t5,t6"}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"t1", "t2", "t3", "t4", "t5", "t6"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/2/3.1/Android.mk 0/1/2/3.2/Android.mk 0/1/2/3.3/Android.mk"}}, + }, { + description: "GET-INSTALL-PATH specified", + dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"}, + buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"}, + args: []string{"GET-INSTALL-PATH", "3.1/", "3.2/", "3.3/:t6"}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"GET-INSTALL-PATH-IN-0-1-2-3.1", "GET-INSTALL-PATH-IN-0-1-2-3.2", "t6"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/2/3.1/Android.mk 0/1/2/3.2/Android.mk 0/1/2/3.3/Android.mk"}}, + }, { + description: "tidy only environment variable specified", + dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"}, + buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"}, + args: []string{"GET-INSTALL-PATH", "3.1/", "3.2/", "3.3/:t6"}, + curDir: "0/1/2", + tidyOnly: "1", + expectedArgs: []string{"tidy_only"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/2/3.1/Android.mk 0/1/2/3.2/Android.mk 0/1/2/3.3/Android.mk"}}, + }, { + description: "normal execution from top dir directory", + dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"}, + buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"}, + args: []string{"0/1/2/3.1", "0/1/2/3.2/:t3,t4", "0/1/2/3.3/:t5,t6"}, + curDir: ".", + tidyOnly: "", + expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "t3", "t4", "t5", "t6"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/2/3.1/Android.mk 0/1/2/3.2/Android.mk 0/1/2/3.3/Android.mk"}}, + }} + for _, tt := range tests { + t.Run("build action BUILD_MODULES_IN_DIRS_NO_DEPS, "+tt.description, func(t *testing.T) { + testGetConfigArgs(t, tt, BUILD_MODULES_IN_DIRECTORIES, false) + }) + } +} + +func TestGetConfigArgsBuildModulesInDirectories(t *testing.T) { + tests := []buildActionTestCase{{ + description: "normal execution in a directory", + dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"}, + buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"}, + args: []string{"3.1/", "3.2/", "3.3/"}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-2-3.3"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: ""}}, + }, { + description: "GET-INSTALL-PATH specified", + dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3"}, + buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/Android.bp"}, + args: []string{"GET-INSTALL-PATH", "2/3.1/", "2/3.2", "3"}, + curDir: "0/1", + tidyOnly: "", + expectedArgs: []string{"GET-INSTALL-PATH-IN-0-1-2-3.1", "GET-INSTALL-PATH-IN-0-1-2-3.2", "GET-INSTALL-PATH-IN-0-1"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: ""}}, + }, { + description: "tidy only environment variable specified", + dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"}, + buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"}, + args: []string{"GET-INSTALL-PATH", "3.1/", "3.2/", "3.3"}, + curDir: "0/1/2", + tidyOnly: "1", + expectedArgs: []string{"tidy_only"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: ""}}, + }, { + description: "normal execution from top dir directory", + dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"}, + buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/3/Android.bp", "0/2/Android.bp"}, + args: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"}, + curDir: ".", + tidyOnly: "", + expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-3", "MODULES-IN-0-2"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: ""}}, + }} + for _, tt := range tests { + t.Run("build action BUILD_MODULES_IN_DIRS, "+tt.description, func(t *testing.T) { + testGetConfigArgs(t, tt, BUILD_MODULES_IN_DIRECTORIES, true) + }) + } +} diff --git a/ui/build/util.go b/ui/build/util.go index 0676a860..75e6753b 100644 --- a/ui/build/util.go +++ b/ui/build/util.go @@ -44,6 +44,17 @@ func inList(s string, list []string) bool { return indexList(s, list) != -1 } +// removeFromlist removes all occurrences of the string in list. +func removeFromList(s string, list []string) []string { + filteredList := make([]string, 0, len(list)) + for _, ls := range list { + if s != ls { + filteredList = append(filteredList, ls) + } + } + return filteredList +} + // ensureDirectoriesExist is a shortcut to os.MkdirAll, sending errors to the ctx logger. func ensureDirectoriesExist(ctx Context, dirs ...string) { for _, dir := range dirs { |