diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go index 0619b5ca7..2ca7ebfa7 100644 --- a/cmd/soong_ui/main.go +++ b/cmd/soong_ui/main.go @@ -114,7 +114,11 @@ func main() { } else if os.Args[1] == "--dumpvars-mode" { dumpVars(buildCtx, config, os.Args[2:]) } else { - build.Build(buildCtx, config, build.BuildAll) + toBuild := build.BuildAll + if config.Checkbuild() { + toBuild |= build.RunBuildTests + } + build.Build(buildCtx, config, toBuild) } } diff --git a/ui/build/Android.bp b/ui/build/Android.bp index d1b49438a..580989436 100644 --- a/ui/build/Android.bp +++ b/ui/build/Android.bp @@ -36,6 +36,7 @@ bootstrap_go_package { "proc_sync.go", "signal.go", "soong.go", + "test_build.go", "util.go", ], testSrcs: [ diff --git a/ui/build/build.go b/ui/build/build.go index 0df22b345..78eb6a327 100644 --- a/ui/build/build.go +++ b/ui/build/build.go @@ -68,6 +68,7 @@ const ( BuildSoong = 1 << iota BuildKati = 1 << iota BuildNinja = 1 << iota + RunBuildTests = 1 << iota BuildAll = BuildProductConfig | BuildSoong | BuildKati | BuildNinja ) @@ -172,14 +173,18 @@ func Build(ctx Context, config Config, what int) { } } + // Write combined ninja file + createCombinedBuildNinjaFile(ctx, config) + + if what&RunBuildTests != 0 { + testForDanglingRules(ctx, config) + } + if what&BuildNinja != 0 { if !config.SkipMake() { installCleanIfNecessary(ctx, config) } - // Write combined ninja file - createCombinedBuildNinjaFile(ctx, config) - // Run ninja runNinja(ctx, config) } diff --git a/ui/build/config.go b/ui/build/config.go index 940bb2fb2..99ae2dacf 100644 --- a/ui/build/config.go +++ b/ui/build/config.go @@ -34,11 +34,12 @@ type configImpl struct { environ *Environment // From the arguments - parallel int - keepGoing int - verbose bool - dist bool - skipMake bool + parallel int + keepGoing int + verbose bool + checkbuild bool + dist bool + skipMake bool // From the product config katiArgs []string @@ -206,6 +207,8 @@ func (c *configImpl) parseArgs(ctx Context, args []string) { } else { if arg == "dist" { c.dist = true + } else if arg == "checkbuild" { + c.checkbuild = true } c.arguments = append(c.arguments, arg) } @@ -313,6 +316,12 @@ func (c *configImpl) KatiSuffix() string { panic("SetKatiSuffix has not been called") } +// Checkbuild returns true if "checkbuild" was one of the build goals, which means that the +// user is interested in additional checks at the expense of build time. +func (c *configImpl) Checkbuild() bool { + return c.checkbuild +} + func (c *configImpl) Dist() bool { return c.dist } diff --git a/ui/build/test_build.go b/ui/build/test_build.go new file mode 100644 index 000000000..d5e244c48 --- /dev/null +++ b/ui/build/test_build.go @@ -0,0 +1,78 @@ +// Copyright 2017 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package build + +import ( + "bufio" + "path/filepath" + "strings" +) + +// Checks for files in the out directory that have a rule that depends on them but no rule to +// create them. This catches a common set of build failures where a rule to generate a file is +// deleted (either by deleting a module in an Android.mk file, or by modifying the build system +// incorrectly). These failures are often not caught by a local incremental build because the +// previously built files are still present in the output directory. +func testForDanglingRules(ctx Context, config Config) { + ctx.BeginTrace("test for dangling rules") + defer ctx.EndTrace() + + // Get a list of leaf nodes in the dependency graph from ninja + executable := config.PrebuiltBuildTool("ninja") + + args := []string{} + args = append(args, config.NinjaArgs()...) + args = append(args, "-f", config.CombinedNinjaFile()) + args = append(args, "-t", "targets", "rule") + + cmd := Command(ctx, config, "ninja", executable, args...) + stdout, err := cmd.StdoutPipe() + if err != nil { + ctx.Fatal(err) + } + + cmd.StartOrFatal() + + outDir := config.OutDir() + bootstrapDir := filepath.Join(outDir, "soong", ".bootstrap") + miniBootstrapDir := filepath.Join(outDir, "soong", ".minibootstrap") + + var danglingRules []string + + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + line := scanner.Text() + if !strings.HasPrefix(line, outDir) { + // Leaf node is not in the out directory. + continue + } + if strings.HasPrefix(line, bootstrapDir) || strings.HasPrefix(line, miniBootstrapDir) { + // Leaf node is in one of Soong's bootstrap directories, which do not have + // full build rules in the primary build.ninja file. + continue + } + danglingRules = append(danglingRules, line) + } + + cmd.WaitOrFatal() + + if len(danglingRules) > 0 { + ctx.Println("Dependencies in out found with no rule to create them:") + for _, dep := range danglingRules { + ctx.Println(dep) + } + ctx.Fatal("") + } +}