Merge "Add OWNERS in build/soong"
diff --git a/Android.bp b/Android.bp
index 9f508d5..c571e74 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,17 +1,6 @@
-//
-// WARNING: Modifying this file will NOT automatically regenerate build.ninja.in!
-//
-// Before modifying this file make sure minibp is up to date:
-// 1) "repo sync build/soong" to make sure you have the latest build.ninja.in
-// 2) build minibp, which builds automicatically through the normal build steps. For example:
-//
-// After modifying this file regenerate build.ninja.in and build your changes:
-// 1) In your build directory, execute "../bootstrap.bash -r" to regenerate build.ninja.in
-// 2) Build again
-//
-
subdirs = [
"androidmk",
+ "bpfix",
"cmd/*",
"third_party/zip",
"ui/*",
@@ -168,6 +157,7 @@
"blueprint-pathtools",
"soong",
"soong-android",
+ "soong-shared",
],
srcs: [
"genrule/filegroup.go",
@@ -233,6 +223,14 @@
pluginFor: ["soong_build"],
}
+bootstrap_go_package {
+ name: "soong-shared",
+ pkgPath: "android/soong/shared",
+ srcs: [
+ "shared/paths.go",
+ ],
+}
+
//
// Defaults to enable various configurations of host bionic
//
diff --git a/android/config.go b/android/config.go
index 869a5c4..c3beb08 100644
--- a/android/config.go
+++ b/android/config.go
@@ -52,6 +52,10 @@
*config
}
+func (c Config) BuildDir() string {
+ return c.buildDir
+}
+
// A DeviceConfig object represents the configuration for a particular device being built. For
// now there will only be one of these, but in the future there may be multiple devices being
// built
diff --git a/android/paths.go b/android/paths.go
index 26b72d1..aa06127 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -672,15 +672,17 @@
func PathForModuleInstall(ctx ModuleContext, pathComponents ...string) OutputPath {
var outPaths []string
if ctx.Device() {
- partition := "system"
+ var partition string
if ctx.Vendor() {
partition = ctx.DeviceConfig().VendorPath()
+ } else if ctx.InstallInData() {
+ partition = "data"
+ } else {
+ partition = "system"
}
if ctx.InstallInSanitizerDir() {
partition = "data/asan/" + partition
- } else if ctx.InstallInData() {
- partition = "data"
}
outPaths = []string{"target", "product", ctx.AConfig().DeviceName(), partition}
} else {
diff --git a/androidmk/Android.bp b/androidmk/Android.bp
index 67baf0a..442452f 100644
--- a/androidmk/Android.bp
+++ b/androidmk/Android.bp
@@ -29,6 +29,7 @@
deps: [
"androidmk-parser",
"blueprint-parser",
+ "bpfix-lib",
],
}
diff --git a/androidmk/cmd/androidmk/androidmk.go b/androidmk/cmd/androidmk/androidmk.go
index 1d94b65..d26643a 100644
--- a/androidmk/cmd/androidmk/androidmk.go
+++ b/androidmk/cmd/androidmk/androidmk.go
@@ -8,6 +8,8 @@
"strings"
"text/scanner"
+ "android/soong/bpfix/bpfix"
+
mkparser "android/soong/androidmk/parser"
bpparser "github.com/google/blueprint/parser"
@@ -176,10 +178,18 @@
}
}
- out, err := bpparser.Print(&bpparser.File{
+ tree := &bpparser.File{
Defs: file.defs,
Comments: file.comments,
- })
+ }
+
+ // check for common supported but undesirable structures and clean them up
+ fixed, err := bpfix.FixTree(tree, bpfix.NewFixRequest().AddAll())
+ if err != nil {
+ return "", []error{err}
+ }
+
+ out, err := bpparser.Print(fixed)
if err != nil {
return "", []error{err}
}
diff --git a/bpfix/Android.bp b/bpfix/Android.bp
new file mode 100644
index 0000000..90a453d
--- /dev/null
+++ b/bpfix/Android.bp
@@ -0,0 +1,43 @@
+// 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.
+
+//
+// androidmk Android.mk to Blueprints translator
+//
+
+blueprint_go_binary {
+ name: "bpfix",
+ srcs: [
+ "cmd/bpfix.go",
+ ],
+ deps: [
+ "bpfix-lib",
+ ],
+}
+
+bootstrap_go_package {
+ name: "bpfix-lib",
+ pkgPath: "android/soong/bpfix/bpfix",
+ srcs: [
+ "bpfix/bpfix.go",
+ ],
+ testSrcs: [
+ "bpfix/bpfix_test.go",
+ ],
+ deps: [
+ "blueprint-parser",
+ ],
+}
+
+
diff --git a/bpfix/bpfix/bpfix.go b/bpfix/bpfix/bpfix.go
new file mode 100644
index 0000000..1249494
--- /dev/null
+++ b/bpfix/bpfix/bpfix.go
@@ -0,0 +1,185 @@
+// 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.
+
+// This file implements the logic of bpfix and also provides a programmatic interface
+
+package bpfix
+
+import (
+ "bytes"
+ "fmt"
+ "github.com/google/blueprint/parser"
+)
+
+// A FixRequest specifies the details of which fixes to apply to an individual file
+// A FixRequest doesn't specify whether to do a dry run or where to write the results; that's in cmd/bpfix.go
+type FixRequest struct {
+ simplifyKnownRedundantVariables bool
+ removeEmptyLists bool
+}
+
+func NewFixRequest() FixRequest {
+ return FixRequest{}
+}
+
+func (r FixRequest) AddAll() (result FixRequest) {
+ result = r
+ result.simplifyKnownRedundantVariables = true
+ result.removeEmptyLists = true
+ return result
+}
+
+// FixTree repeatedly applies the fixes listed in the given FixRequest to the given File
+// until there is no fix that affects the tree
+func FixTree(tree *parser.File, config FixRequest) (fixed *parser.File, err error) {
+ prevIdentifier, err := fingerprint(tree)
+ if err != nil {
+ return nil, err
+ }
+
+ fixed = tree
+ maxNumIterations := 20
+ i := 0
+ for {
+ fixed, err = fixTreeOnce(fixed, config)
+ newIdentifier, err := fingerprint(tree)
+ if err != nil {
+ return nil, err
+ }
+ if bytes.Equal(newIdentifier, prevIdentifier) {
+ break
+ }
+ prevIdentifier = newIdentifier
+ // any errors from a previous iteration generally get thrown away and overwritten by errors on the next iteration
+
+ // detect infinite loop
+ i++
+ if i >= maxNumIterations {
+ return nil, fmt.Errorf("Applied fixes %s times and yet the tree continued to change. Is there an infinite loop?", i)
+ break
+ }
+ }
+ return fixed, err
+}
+
+// returns a unique identifier for the given tree that can be used to determine whether the tree changed
+func fingerprint(tree *parser.File) (fingerprint []byte, err error) {
+ bytes, err := parser.Print(tree)
+ if err != nil {
+ return nil, err
+ }
+ return bytes, nil
+}
+
+func fixTreeOnce(tree *parser.File, config FixRequest) (fixed *parser.File, err error) {
+ if config.simplifyKnownRedundantVariables {
+ tree, err = simplifyKnownPropertiesDuplicatingEachOther(tree)
+ if err != nil {
+ return nil, err
+ }
+ }
+ if config.removeEmptyLists {
+ tree, err = removePropertiesHavingTheirDefaultValues(tree)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return tree, err
+}
+
+func simplifyKnownPropertiesDuplicatingEachOther(tree *parser.File) (fixed *parser.File, err error) {
+ // remove from local_include_dirs anything in export_include_dirs
+ fixed, err = removeMatchingModuleListProperties(tree, "export_include_dirs", "local_include_dirs")
+ return fixed, err
+}
+
+// removes from <items> every item present in <removals>
+func filterExpressionList(items *parser.List, removals *parser.List) {
+ writeIndex := 0
+ for _, item := range items.Values {
+ included := true
+ for _, removal := range removals.Values {
+ equal, err := parser.ExpressionsAreSame(item, removal)
+ if err != nil {
+ continue
+ }
+ if equal {
+ included = false
+ break
+ }
+ }
+ if included {
+ items.Values[writeIndex] = item
+ writeIndex++
+ }
+ }
+ items.Values = items.Values[:writeIndex]
+}
+
+// Remove each modules[i].Properties[<legacyName>][j] that matches a modules[i].Properties[<canonicalName>][k]
+func removeMatchingModuleListProperties(tree *parser.File, canonicalName string, legacyName string) (fixed *parser.File, err error) {
+ for _, def := range tree.Defs {
+ mod, ok := def.(*parser.Module)
+ if !ok {
+ continue
+ }
+ legacy, ok := mod.GetProperty(legacyName)
+ if !ok {
+ continue
+ }
+ legacyList, ok := legacy.Value.(*parser.List)
+ if !ok {
+ continue
+ }
+ canonical, ok := mod.GetProperty(canonicalName)
+ if !ok {
+ continue
+ }
+ canonicalList, ok := canonical.Value.(*parser.List)
+ if !ok {
+ continue
+ }
+ filterExpressionList(legacyList, canonicalList)
+ }
+ return tree, nil
+}
+
+func removePropertiesHavingTheirDefaultValues(tree *parser.File) (fixed *parser.File, err error) {
+ for _, def := range tree.Defs {
+ mod, ok := def.(*parser.Module)
+ if !ok {
+ continue
+ }
+ writeIndex := 0
+ for _, prop := range mod.Properties {
+ val := prop.Value
+ keep := true
+ switch val := val.(type) {
+ case *parser.List:
+ if len(val.Values) == 0 {
+ keep = false
+ }
+ break
+ default:
+ keep = true
+ }
+ if keep {
+ mod.Properties[writeIndex] = prop
+ writeIndex++
+ }
+ }
+ mod.Properties = mod.Properties[:writeIndex]
+ }
+ return tree, nil
+}
diff --git a/bpfix/bpfix/bpfix_test.go b/bpfix/bpfix/bpfix_test.go
new file mode 100644
index 0000000..06ae139
--- /dev/null
+++ b/bpfix/bpfix/bpfix_test.go
@@ -0,0 +1,114 @@
+// 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.
+
+// This file implements the logic of bpfix and also provides a programmatic interface
+
+package bpfix
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/google/blueprint/parser"
+ "reflect"
+)
+
+// TODO(jeffrygaston) remove this when position is removed from ParseNode (in b/38325146) and we can directly do reflect.DeepEqual
+func printListOfStrings(items []string) (text string) {
+ if len(items) == 0 {
+ return "[]"
+ }
+ return fmt.Sprintf("[\"%s\"]", strings.Join(items, "\", \""))
+
+}
+
+func buildTree(local_include_dirs []string, export_include_dirs []string) (file *parser.File, errs []error) {
+ // TODO(jeffrygaston) use the builder class when b/38325146 is done
+ input := fmt.Sprintf(`cc_library_shared {
+ name: "iAmAModule",
+ local_include_dirs: %s,
+ export_include_dirs: %s,
+ }
+ `,
+ printListOfStrings(local_include_dirs), printListOfStrings(export_include_dirs))
+ tree, errs := parser.Parse("", strings.NewReader(input), parser.NewScope(nil))
+ if len(errs) > 0 {
+ errs = append([]error{fmt.Errorf("failed to parse:\n%s", input)}, errs...)
+ }
+ return tree, errs
+}
+
+func implFilterListTest(t *testing.T, local_include_dirs []string, export_include_dirs []string, expectedResult []string) {
+ // build tree
+ tree, errs := buildTree(local_include_dirs, export_include_dirs)
+ if len(errs) > 0 {
+ t.Error("failed to build tree")
+ for _, err := range errs {
+ t.Error(err)
+ }
+ t.Fatalf("%d parse errors", len(errs))
+ }
+
+ // apply simplifications
+ tree, err := simplifyKnownPropertiesDuplicatingEachOther(tree)
+ if len(errs) > 0 {
+ t.Fatal(err)
+ }
+
+ // lookup legacy property
+ mod := tree.Defs[0].(*parser.Module)
+ _, found := mod.GetProperty("local_include_dirs")
+ if !found {
+ t.Fatalf("failed to include key local_include_dirs in parse tree")
+ }
+
+ // check that the value for the legacy property was updated to the correct value
+ errorHeader := fmt.Sprintf("\nFailed to correctly simplify key 'local_include_dirs' in the presence of 'export_include_dirs.'\n"+
+ "original local_include_dirs: %q\n"+
+ "original export_include_dirs: %q\n"+
+ "expected result: %q\n"+
+ "actual result: ",
+ local_include_dirs, export_include_dirs, expectedResult)
+ result, ok := mod.GetProperty("local_include_dirs")
+ if !ok {
+ t.Fatal(errorHeader + "property not found")
+ }
+
+ listResult, ok := result.Value.(*parser.List)
+ if !ok {
+ t.Fatalf("%sproperty is not a list: %v", errorHeader, listResult)
+ }
+
+ actualExpressions := listResult.Values
+ actualValues := make([]string, 0)
+ for _, expr := range actualExpressions {
+ str := expr.(*parser.String)
+ actualValues = append(actualValues, str.Value)
+ }
+
+ if !reflect.DeepEqual(actualValues, expectedResult) {
+ t.Fatalf("%s%q\nlists are different", errorHeader, actualValues)
+ }
+}
+
+func TestSimplifyKnownVariablesDuplicatingEachOther(t *testing.T) {
+ // TODO use []Expression{} once buildTree above can support it (which is after b/38325146 is done)
+ implFilterListTest(t, []string{"include"}, []string{"include"}, []string{})
+ implFilterListTest(t, []string{"include1"}, []string{"include2"}, []string{"include1"})
+ implFilterListTest(t, []string{"include1", "include2", "include3", "include4"}, []string{"include2"},
+ []string{"include1", "include3", "include4"})
+ implFilterListTest(t, []string{}, []string{"include"}, []string{})
+ implFilterListTest(t, []string{}, []string{}, []string{})
+}
diff --git a/bpfix/cmd/bpfix.go b/bpfix/cmd/bpfix.go
new file mode 100644
index 0000000..f51c6f7
--- /dev/null
+++ b/bpfix/cmd/bpfix.go
@@ -0,0 +1,188 @@
+// 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.
+
+// This file provides a command-line interface to bpfix
+
+// TODO(jeffrygaston) should this file be consolidated with bpfmt.go?
+
+package main
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+
+ "github.com/google/blueprint/parser"
+
+ "android/soong/bpfix/bpfix"
+)
+
+var (
+ // main operation modes
+ list = flag.Bool("l", false, "list files whose formatting differs from bpfmt's")
+ write = flag.Bool("w", false, "write result to (source) file instead of stdout")
+ doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
+)
+
+var (
+ exitCode = 0
+)
+
+func report(err error) {
+ fmt.Fprintln(os.Stderr, err)
+ exitCode = 2
+}
+
+func openAndProcess(filename string, out io.Writer, fixRequest bpfix.FixRequest) error {
+ f, err := os.Open(filename)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ return processFile(filename, f, out, fixRequest)
+}
+
+// If in == nil, the source is the contents of the file with the given filename.
+func processFile(filename string, in io.Reader, out io.Writer, fixRequest bpfix.FixRequest) error {
+ // load the input file
+ src, err := ioutil.ReadAll(in)
+ if err != nil {
+ return err
+ }
+ r := bytes.NewBuffer(src)
+ file, errs := parser.Parse(filename, r, parser.NewScope(nil))
+ if len(errs) > 0 {
+ for _, err := range errs {
+ fmt.Fprintln(os.Stderr, err)
+ }
+ return fmt.Errorf("%d parsing errors", len(errs))
+ }
+
+ // compute and apply any requested fixes
+ fixed, err := bpfix.FixTree(file, fixRequest)
+ if err != nil {
+ return err
+ }
+
+ // output the results
+ res, err := parser.Print(fixed)
+ if err != nil {
+ return err
+ }
+ if !bytes.Equal(src, res) {
+ // contents have changed
+ if *list {
+ fmt.Fprintln(out, filename)
+ }
+ if *write {
+ err = ioutil.WriteFile(filename, res, 0644)
+ if err != nil {
+ return err
+ }
+ }
+ if *doDiff {
+ data, err := diff(src, res)
+ if err != nil {
+ return fmt.Errorf("computing diff: %s", err)
+ }
+ fmt.Printf("diff %s bpfix/%s\n", filename, filename)
+ out.Write(data)
+ }
+ }
+ if !*list && !*write && !*doDiff {
+ _, err = out.Write(res)
+ }
+ return err
+}
+
+func makeFileVisitor(fixRequest bpfix.FixRequest) func(string, os.FileInfo, error) error {
+ return func(path string, f os.FileInfo, err error) error {
+ if err == nil && (f.Name() == "Blueprints" || f.Name() == "Android.bp") {
+ err = openAndProcess(path, os.Stdout, fixRequest)
+ }
+ if err != nil {
+ report(err)
+ }
+ return nil
+ }
+}
+
+func walkDir(path string, fixRequest bpfix.FixRequest) {
+ filepath.Walk(path, makeFileVisitor(fixRequest))
+}
+
+func main() {
+ flag.Parse()
+
+ fixRequest := bpfix.NewFixRequest().AddAll()
+
+ if flag.NArg() == 0 {
+ if *write {
+ fmt.Fprintln(os.Stderr, "error: cannot use -w with standard input")
+ exitCode = 2
+ return
+ }
+ if err := processFile("<standard input>", os.Stdin, os.Stdout, fixRequest); err != nil {
+ report(err)
+ }
+ return
+ }
+
+ for i := 0; i < flag.NArg(); i++ {
+ path := flag.Arg(i)
+ switch dir, err := os.Stat(path); {
+ case err != nil:
+ report(err)
+ case dir.IsDir():
+ walkDir(path, fixRequest)
+ default:
+ if err := openAndProcess(path, os.Stdout, fixRequest); err != nil {
+ report(err)
+ }
+ }
+ }
+}
+
+func diff(b1, b2 []byte) (data []byte, err error) {
+ f1, err := ioutil.TempFile("", "bpfix")
+ if err != nil {
+ return
+ }
+ defer os.Remove(f1.Name())
+ defer f1.Close()
+
+ f2, err := ioutil.TempFile("", "bpfix")
+ if err != nil {
+ return
+ }
+ defer os.Remove(f2.Name())
+ defer f2.Close()
+
+ f1.Write(b1)
+ f2.Write(b2)
+
+ data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
+ if len(data) > 0 {
+ // diff exits with a non-zero status when the files don't match.
+ // Ignore that failure as long as we get output.
+ err = nil
+ }
+ return
+
+}
diff --git a/build_test.bash b/build_test.bash
index f833366..ab841cb 100755
--- a/build_test.bash
+++ b/build_test.bash
@@ -30,5 +30,11 @@
export TOP=$(cd $(dirname ${BASH_SOURCE[0]})/../..; PWD= /bin/pwd)
source "${TOP}/build/soong/cmd/microfactory/microfactory.bash"
+case $(uname) in
+ Linux)
+ export LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so
+ ;;
+esac
+
build_go multiproduct_kati android/soong/cmd/multiproduct_kati
exec "$(getoutdir)/multiproduct_kati" "$@"
diff --git a/cc/builder.go b/cc/builder.go
index 51c4ce9..61c0572 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -206,26 +206,27 @@
}
type builderFlags struct {
- globalFlags string
- arFlags string
- asFlags string
- cFlags string
- conlyFlags string
- cppFlags string
- ldFlags string
- libFlags string
- yaccFlags string
- protoFlags string
- tidyFlags string
- sAbiFlags string
- yasmFlags string
- aidlFlags string
- rsFlags string
- toolchain config.Toolchain
- clang bool
- tidy bool
- coverage bool
- sAbiDump bool
+ globalFlags string
+ arFlags string
+ asFlags string
+ cFlags string
+ toolingCFlags string // Seperate set of Cflags for clang LibTooling tools
+ conlyFlags string
+ cppFlags string
+ ldFlags string
+ libFlags string
+ yaccFlags string
+ protoFlags string
+ tidyFlags string
+ sAbiFlags string
+ yasmFlags string
+ aidlFlags string
+ rsFlags string
+ toolchain config.Toolchain
+ clang bool
+ tidy bool
+ coverage bool
+ sAbiDump bool
systemIncludeFlags string
@@ -275,25 +276,40 @@
coverageFiles = make(android.Paths, 0, len(srcFiles))
}
- cflags := strings.Join([]string{
+ commonFlags := strings.Join([]string{
flags.globalFlags,
flags.systemIncludeFlags,
+ }, " ")
+
+ toolingCflags := strings.Join([]string{
+ commonFlags,
+ flags.toolingCFlags,
+ flags.conlyFlags,
+ }, " ")
+
+ cflags := strings.Join([]string{
+ commonFlags,
flags.cFlags,
flags.conlyFlags,
}, " ")
+ toolingCppflags := strings.Join([]string{
+ commonFlags,
+ flags.toolingCFlags,
+ flags.cppFlags,
+ }, " ")
+
cppflags := strings.Join([]string{
- flags.globalFlags,
- flags.systemIncludeFlags,
+ commonFlags,
flags.cFlags,
flags.cppFlags,
}, " ")
asflags := strings.Join([]string{
- flags.globalFlags,
- flags.systemIncludeFlags,
+ commonFlags,
flags.asFlags,
}, " ")
+
var sAbiDumpFiles android.Paths
if flags.sAbiDump && flags.clang {
sAbiDumpFiles = make(android.Paths, 0, len(srcFiles))
@@ -301,7 +317,9 @@
if flags.clang {
cflags += " ${config.NoOverrideClangGlobalCflags}"
+ toolingCflags += " ${config.NoOverrideClangGlobalCflags}"
cppflags += " ${config.NoOverrideClangGlobalCflags}"
+ toolingCppflags += " ${config.NoOverrideClangGlobalCflags}"
} else {
cflags += " ${config.NoOverrideGlobalCflags}"
cppflags += " ${config.NoOverrideGlobalCflags}"
@@ -327,6 +345,7 @@
}
var moduleCflags string
+ var moduleToolingCflags string
var ccCmd string
tidy := flags.tidy && flags.clang
coverage := flags.coverage
@@ -342,9 +361,11 @@
case ".c":
ccCmd = "gcc"
moduleCflags = cflags
+ moduleToolingCflags = toolingCflags
case ".cpp", ".cc", ".mm":
ccCmd = "g++"
moduleCflags = cppflags
+ moduleToolingCflags = toolingCppflags
default:
ctx.ModuleErrorf("File %s has unknown extension", srcFile)
continue
@@ -402,7 +423,7 @@
// support exporting dependencies.
Implicit: objFile,
Args: map[string]string{
- "cFlags": moduleCflags,
+ "cFlags": moduleToolingCflags,
"tidyFlags": flags.tidyFlags,
},
})
@@ -419,7 +440,7 @@
Input: srcFile,
Implicit: objFile,
Args: map[string]string{
- "cFlags": moduleCflags,
+ "cFlags": moduleToolingCflags,
"exportDirs": flags.sAbiFlags,
},
})
diff --git a/cc/cc.go b/cc/cc.go
index 43825ca..867b196 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -97,21 +97,23 @@
}
type Flags struct {
- GlobalFlags []string // Flags that apply to C, C++, and assembly source files
- ArFlags []string // Flags that apply to ar
- AsFlags []string // Flags that apply to assembly source files
- CFlags []string // Flags that apply to C and C++ source files
- ConlyFlags []string // Flags that apply to C source files
- CppFlags []string // Flags that apply to C++ source files
- YaccFlags []string // Flags that apply to Yacc source files
- protoFlags []string // Flags that apply to proto source files
- aidlFlags []string // Flags that apply to aidl source files
- rsFlags []string // Flags that apply to renderscript source files
- LdFlags []string // Flags that apply to linker command lines
- libFlags []string // Flags to add libraries early to the link order
- TidyFlags []string // Flags that apply to clang-tidy
- SAbiFlags []string // Flags that apply to header-abi-dumper
- YasmFlags []string // Flags that apply to yasm assembly source files
+ GlobalFlags []string // Flags that apply to C, C++, and assembly source files
+ ArFlags []string // Flags that apply to ar
+ AsFlags []string // Flags that apply to assembly source files
+ CFlags []string // Flags that apply to C and C++ source files
+ ToolingCFlags []string // Flags that apply to C and C++ source files parsed by clang LibTooling tools
+ ConlyFlags []string // Flags that apply to C source files
+ CppFlags []string // Flags that apply to C++ source files
+ ToolingCppFlags []string // Flags that apply to C++ source files parsed by clang LibTooling tools
+ YaccFlags []string // Flags that apply to Yacc source files
+ protoFlags []string // Flags that apply to proto source files
+ aidlFlags []string // Flags that apply to aidl source files
+ rsFlags []string // Flags that apply to renderscript source files
+ LdFlags []string // Flags that apply to linker command lines
+ libFlags []string // Flags to add libraries early to the link order
+ TidyFlags []string // Flags that apply to clang-tidy
+ SAbiFlags []string // Flags that apply to header-abi-dumper
+ YasmFlags []string // Flags that apply to yasm assembly source files
// Global include flags that apply to C, C++, and assembly source files
// These must be after any module include flags, which will be in GlobalFlags.
@@ -507,9 +509,6 @@
if c.coverage != nil {
flags = c.coverage.flags(ctx, flags)
}
- if c.sabi != nil {
- flags = c.sabi.flags(ctx, flags)
- }
for _, feature := range c.features {
flags = feature.flags(ctx, flags)
}
@@ -527,7 +526,10 @@
}
flags.GlobalFlags = append(flags.GlobalFlags, deps.Flags...)
c.flags = flags
-
+ // We need access to all the flags seen by a source file.
+ if c.sabi != nil {
+ flags = c.sabi.flags(ctx, flags)
+ }
// Optimization to reduce size of build.ninja
// Replace the long list of flags for each file with a module-local variable
ctx.Variable(pctx, "cflags", strings.Join(flags.CFlags, " "))
diff --git a/cc/config/clang.go b/cc/config/clang.go
index 3ff6b1b..977afe1 100644
--- a/cc/config/clang.go
+++ b/cc/config/clang.go
@@ -65,6 +65,11 @@
"-mbionic",
})
+var ClangLibToolingUnknownCflags = []string{
+ "-flto",
+ "-fsanitize*",
+}
+
func init() {
pctx.StaticVariable("ClangExtraCflags", strings.Join([]string{
"-D__compiler_offsetof=__builtin_offsetof",
diff --git a/cc/linker.go b/cc/linker.go
index 5a3b478..2c39132 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -87,6 +87,14 @@
// group static libraries. This can resolve missing symbols issues with interdependencies
// between static libraries, but it is generally better to order them correctly instead.
Group_static_libs *bool `android:"arch_variant"`
+
+ Target struct {
+ Vendor struct {
+ // list of shared libs that should not be used to build
+ // the vendor variant of the C/C++ module.
+ Exclude_shared_libs []string
+ }
+ }
}
func NewBaseLinker() *baseLinker {
@@ -123,6 +131,10 @@
deps.StaticLibs = append(deps.StaticLibs, linker.Properties.Static_libs...)
deps.SharedLibs = append(deps.SharedLibs, linker.Properties.Shared_libs...)
+ if ctx.vndk() {
+ deps.SharedLibs = removeListFromList(deps.SharedLibs, linker.Properties.Target.Vendor.Exclude_shared_libs)
+ }
+
deps.ReexportHeaderLibHeaders = append(deps.ReexportHeaderLibHeaders, linker.Properties.Export_header_lib_headers...)
deps.ReexportStaticLibHeaders = append(deps.ReexportStaticLibHeaders, linker.Properties.Export_static_lib_headers...)
deps.ReexportSharedLibHeaders = append(deps.ReexportSharedLibHeaders, linker.Properties.Export_shared_lib_headers...)
diff --git a/cc/makevars.go b/cc/makevars.go
index a1e97a5..2a5c813 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -59,12 +59,12 @@
ctx.Strict("BOARD_VNDK_VERSION", "")
}
- ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_CFLAGS", asanCflags)
- ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_LDFLAGS", asanLdflags)
- ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_STATIC_LIBRARIES", asanLibs)
+ ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_CFLAGS", strings.Join(asanCflags, " "))
+ ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_LDFLAGS", strings.Join(asanLdflags, " "))
+ ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_STATIC_LIBRARIES", strings.Join(asanLibs, " "))
- ctx.Strict("CFI_EXTRA_CFLAGS", cfiCflags)
- ctx.Strict("CFI_EXTRA_LDFLAGS", cfiLdflags)
+ ctx.Strict("CFI_EXTRA_CFLAGS", strings.Join(cfiCflags, " "))
+ ctx.Strict("CFI_EXTRA_LDFLAGS", strings.Join(cfiLdflags, " "))
ctx.Strict("DEFAULT_C_STD_VERSION", config.CStdVersion)
ctx.Strict("DEFAULT_CPP_STD_VERSION", config.CppStdVersion)
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index ba3ec7d..d801775 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -281,6 +281,10 @@
}
func (c *stubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
+ if !strings.HasSuffix(c.properties.Symbol_file, ".map.txt") {
+ ctx.PropertyErrorf("symbol_file", "must end with .map.txt")
+ }
+
objs, versionScript := compileStubLibrary(ctx, flags, c.properties.Symbol_file, c.properties.ApiLevel, "")
c.versionScriptPath = versionScript
return objs
diff --git a/cc/sabi.go b/cc/sabi.go
index 01ef737..92fc7cf 100644
--- a/cc/sabi.go
+++ b/cc/sabi.go
@@ -15,6 +15,8 @@
package cc
import (
+ "strings"
+
"android/soong/android"
"github.com/google/blueprint"
@@ -40,7 +42,31 @@
return deps
}
+func inListWithPrefixSearch(flag string, filter []string) bool {
+ // Assuming the filter is small enough.
+ // If the suffix of a filter element is *, try matching prefixes as well.
+ for _, f := range filter {
+ if (f == flag) || (strings.HasSuffix(f, "*") && strings.HasPrefix(flag, strings.TrimSuffix(f, "*"))) {
+ return true
+ }
+ }
+ return false
+}
+
+func filterOutWithPrefix(list []string, filter []string) (remainder []string) {
+ // Go through the filter, matching and optionally doing a prefix search for list elements.
+ for _, l := range list {
+ if !inListWithPrefixSearch(l, filter) {
+ remainder = append(remainder, l)
+ }
+ }
+ return
+}
+
func (sabimod *sabi) flags(ctx ModuleContext, flags Flags) Flags {
+ // Assuming that the cflags which clang LibTooling tools cannot
+ // understand have not been converted to ninja variables yet.
+ flags.ToolingCFlags = filterOutWithPrefix(flags.CFlags, config.ClangLibToolingUnknownCflags)
return flags
}
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 8900ee8..b847c6f 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -25,17 +25,21 @@
"android/soong/cc/config"
)
-const (
- asanCflags = "-fno-omit-frame-pointer"
- asanLdflags = "-Wl,-u,__asan_preinit"
- asanLibs = "libasan"
+var (
+ // Any C flags added by sanitizer which libTooling tools may not
+ // understand also need to be added to ClangLibToolingUnknownCflags in
+ // cc/config/clang.go
- cfiCflags = "-flto -fsanitize-cfi-cross-dso -fvisibility=default " +
- "-fsanitize-blacklist=external/compiler-rt/lib/cfi/cfi_blacklist.txt"
+ asanCflags = []string{"-fno-omit-frame-pointer"}
+ asanLdflags = []string{"-Wl,-u,__asan_preinit"}
+ asanLibs = []string{"libasan"}
+
+ cfiCflags = []string{"-flto", "-fsanitize-cfi-cross-dso", "-fvisibility=default",
+ "-fsanitize-blacklist=external/compiler-rt/lib/cfi/cfi_blacklist.txt"}
// FIXME: revert the __cfi_check flag when clang is updated to r280031.
- cfiLdflags = "-flto -fsanitize-cfi-cross-dso -fsanitize=cfi " +
- "-Wl,-plugin-opt,O1 -Wl,-export-dynamic-symbol=__cfi_check"
- cfiArflags = "--plugin ${config.ClangBin}/../lib64/LLVMgold.so"
+ cfiLdflags = []string{"-flto", "-fsanitize-cfi-cross-dso", "-fsanitize=cfi",
+ "-Wl,-plugin-opt,O1 -Wl,-export-dynamic-symbol=__cfi_check"}
+ cfiArflags = []string{"--plugin ${config.ClangBin}/../lib64/LLVMgold.so"}
)
type sanitizerType int
@@ -85,8 +89,9 @@
// Replaces abort() on error with a human-readable error message.
// Address and Thread sanitizers always run in diagnostic mode.
Diag struct {
- Undefined *bool `android:"arch_variant"`
- Cfi *bool `android:"arch_variant"`
+ Undefined *bool `android:"arch_variant"`
+ Cfi *bool `android:"arch_variant"`
+ Misc_undefined []string `android:"arch_variant"`
}
// value to pass to -fsanitize-recover=
@@ -212,8 +217,8 @@
// TODO(ccross): error for compile_multilib = "32"?
}
- if Bool(s.All_undefined) || Bool(s.Undefined) || Bool(s.Address) ||
- Bool(s.Thread) || Bool(s.Coverage) || Bool(s.Safestack) || Bool(s.Cfi) {
+ if ctx.Os() != android.Windows && (Bool(s.All_undefined) || Bool(s.Undefined) || Bool(s.Address) || Bool(s.Thread) ||
+ Bool(s.Coverage) || Bool(s.Safestack) || Bool(s.Cfi) || len(s.Misc_undefined) > 0) {
sanitize.Properties.SanitizerEnabled = true
}
@@ -231,7 +236,7 @@
if ctx.Device() {
if Bool(sanitize.Properties.Sanitize.Address) {
- deps.StaticLibs = append(deps.StaticLibs, asanLibs)
+ deps.StaticLibs = append(deps.StaticLibs, asanLibs...)
}
if Bool(sanitize.Properties.Sanitize.Address) || Bool(sanitize.Properties.Sanitize.Thread) {
deps.SharedLibs = append(deps.SharedLibs, "libdl")
@@ -287,30 +292,26 @@
sanitizers = append(sanitizers, sanitize.Properties.Sanitize.Misc_undefined...)
}
- if Bool(sanitize.Properties.Sanitize.Diag.Undefined) &&
- (Bool(sanitize.Properties.Sanitize.All_undefined) ||
- Bool(sanitize.Properties.Sanitize.Undefined) ||
- len(sanitize.Properties.Sanitize.Misc_undefined) > 0) {
+ if Bool(sanitize.Properties.Sanitize.Diag.Undefined) {
diagSanitizers = append(diagSanitizers, "undefined")
}
+ diagSanitizers = append(diagSanitizers, sanitize.Properties.Sanitize.Diag.Misc_undefined...)
+
if Bool(sanitize.Properties.Sanitize.Address) {
if ctx.Arch().ArchType == android.Arm {
// Frame pointer based unwinder in ASan requires ARM frame setup.
// TODO: put in flags?
flags.RequiredInstructionSet = "arm"
}
- flags.CFlags = append(flags.CFlags, asanCflags)
- flags.LdFlags = append(flags.LdFlags, asanLdflags)
+ flags.CFlags = append(flags.CFlags, asanCflags...)
+ flags.LdFlags = append(flags.LdFlags, asanLdflags...)
if ctx.Host() {
// -nodefaultlibs (provided with libc++) prevents the driver from linking
// libraries needed with -fsanitize=address. http://b/18650275 (WAI)
flags.LdFlags = append(flags.LdFlags, "-lm", "-lpthread")
flags.LdFlags = append(flags.LdFlags, "-Wl,--no-as-needed")
- // Host ASAN only links symbols in the final executable, so
- // there will always be undefined symbols in intermediate libraries.
- _, flags.LdFlags = removeFromList("-Wl,--no-undefined", flags.LdFlags)
} else {
flags.CFlags = append(flags.CFlags, "-mllvm", "-asan-globals=0")
flags.DynamicLinker = "/system/bin/linker_asan"
@@ -340,9 +341,9 @@
flags.LdFlags = append(flags.LdFlags, "-march=armv7-a")
}
sanitizers = append(sanitizers, "cfi")
- flags.CFlags = append(flags.CFlags, cfiCflags)
- flags.LdFlags = append(flags.LdFlags, cfiLdflags)
- flags.ArFlags = append(flags.ArFlags, cfiArflags)
+ flags.CFlags = append(flags.CFlags, cfiCflags...)
+ flags.LdFlags = append(flags.LdFlags, cfiLdflags...)
+ flags.ArFlags = append(flags.ArFlags, cfiArflags...)
if Bool(sanitize.Properties.Sanitize.Diag.Cfi) {
diagSanitizers = append(diagSanitizers, "cfi")
}
@@ -354,7 +355,13 @@
if ctx.Host() {
flags.CFlags = append(flags.CFlags, "-fno-sanitize-recover=all")
flags.LdFlags = append(flags.LdFlags, sanitizeArg)
- flags.LdFlags = append(flags.LdFlags, "-lrt", "-ldl")
+ if ctx.Os() == android.Linux {
+ flags.LdFlags = append(flags.LdFlags, "-lrt")
+ }
+ flags.LdFlags = append(flags.LdFlags, "-ldl")
+ // Host sanitizers only link symbols in the final executable, so
+ // there will always be undefined symbols in intermediate libraries.
+ _, flags.LdFlags = removeFromList("-Wl,--no-undefined", flags.LdFlags)
} else {
flags.CFlags = append(flags.CFlags, "-fsanitize-trap=all", "-ftrap-function=abort")
}
diff --git a/cc/util.go b/cc/util.go
index 2febb57..eeb64eb 100644
--- a/cc/util.go
+++ b/cc/util.go
@@ -66,6 +66,16 @@
return
}
+func removeListFromList(list []string, filter_out []string) (result []string) {
+ result = make([]string, 0, len(list))
+ for _, l := range list {
+ if !inList(l, filter_out) {
+ result = append(result, l)
+ }
+ }
+ return
+}
+
func removeFromList(s string, list []string) (bool, []string) {
i := indexList(s, list)
if i != -1 {
@@ -87,26 +97,27 @@
func flagsToBuilderFlags(in Flags) builderFlags {
return builderFlags{
- globalFlags: strings.Join(in.GlobalFlags, " "),
- arFlags: strings.Join(in.ArFlags, " "),
- asFlags: strings.Join(in.AsFlags, " "),
- cFlags: strings.Join(in.CFlags, " "),
- conlyFlags: strings.Join(in.ConlyFlags, " "),
- cppFlags: strings.Join(in.CppFlags, " "),
- yaccFlags: strings.Join(in.YaccFlags, " "),
- protoFlags: strings.Join(in.protoFlags, " "),
- aidlFlags: strings.Join(in.aidlFlags, " "),
- rsFlags: strings.Join(in.rsFlags, " "),
- ldFlags: strings.Join(in.LdFlags, " "),
- libFlags: strings.Join(in.libFlags, " "),
- tidyFlags: strings.Join(in.TidyFlags, " "),
- sAbiFlags: strings.Join(in.SAbiFlags, " "),
- yasmFlags: strings.Join(in.YasmFlags, " "),
- toolchain: in.Toolchain,
- clang: in.Clang,
- coverage: in.Coverage,
- tidy: in.Tidy,
- sAbiDump: in.SAbiDump,
+ globalFlags: strings.Join(in.GlobalFlags, " "),
+ arFlags: strings.Join(in.ArFlags, " "),
+ asFlags: strings.Join(in.AsFlags, " "),
+ cFlags: strings.Join(in.CFlags, " "),
+ toolingCFlags: strings.Join(in.ToolingCFlags, " "),
+ conlyFlags: strings.Join(in.ConlyFlags, " "),
+ cppFlags: strings.Join(in.CppFlags, " "),
+ yaccFlags: strings.Join(in.YaccFlags, " "),
+ protoFlags: strings.Join(in.protoFlags, " "),
+ aidlFlags: strings.Join(in.aidlFlags, " "),
+ rsFlags: strings.Join(in.rsFlags, " "),
+ ldFlags: strings.Join(in.LdFlags, " "),
+ libFlags: strings.Join(in.libFlags, " "),
+ tidyFlags: strings.Join(in.TidyFlags, " "),
+ sAbiFlags: strings.Join(in.SAbiFlags, " "),
+ yasmFlags: strings.Join(in.YasmFlags, " "),
+ toolchain: in.Toolchain,
+ clang: in.Clang,
+ coverage: in.Coverage,
+ tidy: in.Tidy,
+ sAbiDump: in.SAbiDump,
systemIncludeFlags: strings.Join(in.SystemIncludeFlags, " "),
diff --git a/cmd/pom2mk/Android.bp b/cmd/pom2mk/Android.bp
new file mode 100644
index 0000000..54422b1
--- /dev/null
+++ b/cmd/pom2mk/Android.bp
@@ -0,0 +1,19 @@
+// 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.
+
+blueprint_go_binary {
+ name: "pom2mk",
+ deps: ["blueprint-proptools"],
+ srcs: ["pom2mk.go"],
+}
diff --git a/cmd/pom2mk/pom2mk.go b/cmd/pom2mk/pom2mk.go
new file mode 100644
index 0000000..e6144a5
--- /dev/null
+++ b/cmd/pom2mk/pom2mk.go
@@ -0,0 +1,254 @@
+// 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 main
+
+import (
+ "encoding/xml"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "strings"
+ "text/template"
+
+ "github.com/google/blueprint/proptools"
+)
+
+type RewriteNames []RewriteName
+type RewriteName struct {
+ regexp *regexp.Regexp
+ repl string
+}
+
+func (r *RewriteNames) String() string {
+ return ""
+}
+
+func (r *RewriteNames) Set(v string) error {
+ split := strings.SplitN(v, "=", 2)
+ if len(split) != 2 {
+ return fmt.Errorf("Must be in the form of <regex>=<replace>")
+ }
+ regex, err := regexp.Compile(split[0])
+ if err != nil {
+ return nil
+ }
+ *r = append(*r, RewriteName{
+ regexp: regex,
+ repl: split[1],
+ })
+ return nil
+}
+
+func (r *RewriteNames) Rewrite(name string) string {
+ for _, r := range *r {
+ if r.regexp.MatchString(name) {
+ return r.regexp.ReplaceAllString(name, r.repl)
+ }
+ }
+ return name
+}
+
+var rewriteNames = RewriteNames{}
+
+type ExtraDeps map[string][]string
+
+func (d ExtraDeps) String() string {
+ return ""
+}
+
+func (d ExtraDeps) Set(v string) error {
+ split := strings.SplitN(v, "=", 2)
+ if len(split) != 2 {
+ return fmt.Errorf("Must be in the form of <module>=<module>[,<module>]")
+ }
+ d[split[0]] = strings.Split(split[1], ",")
+ return nil
+}
+
+var extraDeps = make(ExtraDeps)
+
+type Dependency struct {
+ XMLName xml.Name `xml:"dependency"`
+
+ GroupId string `xml:"groupId"`
+ ArtifactId string `xml:"artifactId"`
+ Version string `xml:"version"`
+ Type string `xml:"type"`
+
+ Scope string `xml:"scope"`
+}
+
+type Pom struct {
+ XMLName xml.Name `xml:"http://maven.apache.org/POM/4.0.0 project"`
+
+ ArtifactFile string `xml:"-"`
+
+ GroupId string `xml:"groupId"`
+ ArtifactId string `xml:"artifactId"`
+ Version string `xml:"version"`
+ Packaging string `xml:"packaging"`
+
+ Dependencies []Dependency `xml:"dependencies>dependency"`
+}
+
+func (p Pom) MkName() string {
+ return rewriteNames.Rewrite(p.ArtifactId)
+}
+
+func (p Pom) MkDeps() []string {
+ var ret []string
+ for _, d := range p.Dependencies {
+ if d.Type != "aar" {
+ continue
+ }
+ name := rewriteNames.Rewrite(d.ArtifactId)
+ ret = append(ret, name)
+ ret = append(ret, extraDeps[name]...)
+ }
+ return ret
+}
+
+var mkTemplate = template.Must(template.New("mk").Parse(`
+include $(CLEAR_VARS)
+LOCAL_MODULE := {{.MkName}}
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_UNINSTALLABLE_MODULE := true
+LOCAL_SRC_FILES := {{.ArtifactFile}}
+LOCAL_BUILT_MODULE_STEM := javalib.jar
+LOCAL_MODULE_SUFFIX := .{{.Packaging}}
+LOCAL_USE_AAPT2 := true
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+{{range .MkDeps}} {{.}} \
+{{end}}
+include $(BUILD_PREBUILT)
+`))
+
+func convert(filename string, out io.Writer) error {
+ data, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return err
+ }
+
+ var pom Pom
+ err = xml.Unmarshal(data, &pom)
+ if err != nil {
+ return err
+ }
+
+ if pom.Packaging == "" {
+ pom.Packaging = "jar"
+ }
+
+ pom.ArtifactFile = strings.TrimSuffix(filename, ".pom") + "." + pom.Packaging
+
+ return mkTemplate.Execute(out, pom)
+}
+
+func main() {
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, `pom2mk, a tool to create Android.mk files from maven repos
+
+The tool will extract the necessary information from *.pom files to create an Android.mk whose
+aar libraries can be linked against when using AAPT2.
+
+Usage: %s [--rewrite <regex>=<replace>] [--extra-deps <module>=<module>[,<module>]] <dir>
+
+ -rewrite <regex>=<replace>
+ rewrite can be used to specify mappings between the artifactId in the pom files and module
+ names in the Android.mk files. This can be specified multiple times, the first matching
+ regex will be used.
+ -extra-deps <module>=<module>[,<module>]
+ Some Android.mk modules have transitive dependencies that must be specified when they are
+ depended upon (like android-support-v7-mediarouter requires android-support-v7-appcompat).
+ This may be specified multiple times to declare these dependencies.
+ <dir>
+ The directory to search for *.pom files under.
+
+The makefile is written to stdout, to be put in the current directory (often as Android.mk)
+`, os.Args[0])
+ }
+
+ flag.Var(&extraDeps, "extra-deps", "Extra dependencies needed when depending on a module")
+ flag.Var(&rewriteNames, "rewrite", "Regex(es) to rewrite artifact names")
+ flag.Parse()
+
+ if flag.NArg() != 1 {
+ flag.Usage()
+ os.Exit(1)
+ }
+
+ dir := flag.Arg(0)
+ absDir, err := filepath.Abs(dir)
+ if err != nil {
+ fmt.Println(os.Stderr, "Failed to get absolute directory:", err)
+ os.Exit(1)
+ }
+
+ var filenames []string
+ err = filepath.Walk(absDir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ name := info.Name()
+ if info.IsDir() {
+ if strings.HasPrefix(name, ".") {
+ return filepath.SkipDir
+ }
+ return nil
+ }
+
+ if strings.HasPrefix(name, ".") {
+ return nil
+ }
+
+ if strings.HasSuffix(name, ".pom") {
+ path, err = filepath.Rel(absDir, path)
+ if err != nil {
+ return err
+ }
+ filenames = append(filenames, filepath.Join(dir, path))
+ }
+ return nil
+ })
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "Error walking files:", err)
+ os.Exit(1)
+ }
+
+ if len(filenames) == 0 {
+ fmt.Fprintln(os.Stderr, "Error: no *.pom files found under", dir)
+ os.Exit(1)
+ }
+
+ sort.Strings(filenames)
+
+ fmt.Println("# Automatically generated with:")
+ fmt.Println("# pom2mk", strings.Join(proptools.ShellEscape(os.Args[1:]), " "))
+ fmt.Println("LOCAL_PATH := $(call my-dir)")
+
+ for _, filename := range filenames {
+ err := convert(filename, os.Stdout)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "Error converting", filename, err)
+ os.Exit(1)
+ }
+ }
+}
diff --git a/cmd/sbox/Android.bp b/cmd/sbox/Android.bp
new file mode 100644
index 0000000..fe4c7bb
--- /dev/null
+++ b/cmd/sbox/Android.bp
@@ -0,0 +1,21 @@
+// 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.
+
+blueprint_go_binary {
+ name: "sbox",
+ srcs: [
+ "sbox.go",
+ ],
+}
+
diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go
new file mode 100644
index 0000000..5064626
--- /dev/null
+++ b/cmd/sbox/sbox.go
@@ -0,0 +1,185 @@
+// 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 main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path"
+ "path/filepath"
+ "strings"
+)
+
+func main() {
+ error := run()
+ if error != nil {
+ fmt.Fprintln(os.Stderr, error)
+ os.Exit(1)
+ }
+}
+
+var usage = "Usage: sbox -c <commandToRun> --sandbox-path <sandboxPath> --output-root <outputRoot> <outputFile> [<outputFile>...]\n" +
+ "\n" +
+ "Runs <commandToRun> and moves each <outputFile> out of <sandboxPath>\n" +
+ "If any file in <outputFiles> is specified by absolute path, then <outputRoot> must be specified as well,\n" +
+ "to enable sbox to compute the relative path within the sandbox of the specified output files"
+
+func usageError(violation string) error {
+ return fmt.Errorf("Usage error: %s.\n\n%s", violation, usage)
+}
+
+func run() error {
+ var outFiles []string
+ args := os.Args[1:]
+
+ var rawCommand string
+ var sandboxesRoot string
+ removeTempDir := true
+ var outputRoot string
+
+ for i := 0; i < len(args); i++ {
+ arg := args[i]
+ if arg == "--sandbox-path" {
+ sandboxesRoot = args[i+1]
+ i++
+ } else if arg == "-c" {
+ rawCommand = args[i+1]
+ i++
+ } else if arg == "--output-root" {
+ outputRoot = args[i+1]
+ i++
+ } else if arg == "--keep-out-dir" {
+ removeTempDir = false
+ } else {
+ outFiles = append(outFiles, arg)
+ }
+ }
+ if len(rawCommand) == 0 {
+ return usageError("-c <commandToRun> is required and must be non-empty")
+ }
+ if outFiles == nil {
+ return usageError("at least one output file must be given")
+ }
+ if len(sandboxesRoot) == 0 {
+ // In practice, the value of sandboxesRoot will mostly likely be at a fixed location relative to OUT_DIR,
+ // and the sbox executable will most likely be at a fixed location relative to OUT_DIR too, so
+ // the value of sandboxesRoot will most likely be at a fixed location relative to the sbox executable
+ // However, Soong also needs to be able to separately remove the sandbox directory on startup (if it has anything left in it)
+ // and by passing it as a parameter we don't need to duplicate its value
+ return usageError("--sandbox-path <sandboxPath> is required and must be non-empty")
+ }
+
+ // Rewrite output file paths to be relative to output root
+ // This facilitates matching them up against the corresponding paths in the temporary directory in case they're absolute
+ for i, filePath := range outFiles {
+ if path.IsAbs(filePath) {
+ if len(outputRoot) == 0 {
+ return fmt.Errorf("Absolute path %s requires nonempty value for --output-root", filePath)
+ }
+ }
+ relativePath, err := filepath.Rel(outputRoot, filePath)
+ if err != nil {
+ return err
+ }
+ outFiles[i] = relativePath
+ }
+
+ os.MkdirAll(sandboxesRoot, 0777)
+
+ tempDir, err := ioutil.TempDir(sandboxesRoot, "sbox")
+ if err != nil {
+ return fmt.Errorf("Failed to create temp dir: %s", err)
+ }
+
+ // In the common case, the following line of code is what removes the sandbox
+ // If a fatal error occurs (such as if our Go process is killed unexpectedly),
+ // then at the beginning of the next build, Soong will retry the cleanup
+ defer func() {
+ // in some cases we decline to remove the temp dir, to facilitate debugging
+ if removeTempDir {
+ os.RemoveAll(tempDir)
+ }
+ }()
+
+ if strings.Contains(rawCommand, "__SBOX_OUT_DIR__") {
+ rawCommand = strings.Replace(rawCommand, "__SBOX_OUT_DIR__", tempDir, -1)
+ }
+
+ if strings.Contains(rawCommand, "__SBOX_OUT_FILES__") {
+ // expands into a space-separated list of output files to be generated into the sandbox directory
+ tempOutPaths := []string{}
+ for _, outputPath := range outFiles {
+ tempOutPath := path.Join(tempDir, outputPath)
+ tempOutPaths = append(tempOutPaths, tempOutPath)
+ }
+ pathsText := strings.Join(tempOutPaths, " ")
+ rawCommand = strings.Replace(rawCommand, "__SBOX_OUT_FILES__", pathsText, -1)
+ }
+
+ for _, filePath := range outFiles {
+ os.MkdirAll(path.Join(tempDir, filepath.Dir(filePath)), 0777)
+ }
+
+ commandDescription := rawCommand
+
+ cmd := exec.Command("bash", "-c", rawCommand)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err = cmd.Run()
+
+ if exit, ok := err.(*exec.ExitError); ok && !exit.Success() {
+ return fmt.Errorf("sbox command (%s) failed with err %#v\n", commandDescription, err.Error())
+ } else if err != nil {
+ return err
+ }
+
+ // validate that all files are created properly
+ var outputErrors []error
+ for _, filePath := range outFiles {
+ tempPath := filepath.Join(tempDir, filePath)
+ fileInfo, err := os.Stat(tempPath)
+ if err != nil {
+ outputErrors = append(outputErrors, fmt.Errorf("failed to create expected output file: %s\n", tempPath))
+ continue
+ }
+ if fileInfo.IsDir() {
+ outputErrors = append(outputErrors, fmt.Errorf("Output path %s refers to a directory, not a file. This is not permitted because it prevents robust up-to-date checks\n", filePath))
+ }
+ }
+ if len(outputErrors) > 0 {
+ // Keep the temporary output directory around in case a user wants to inspect it for debugging purposes.
+ // Soong will delete it later anyway.
+ removeTempDir = false
+ return fmt.Errorf("mismatch between declared and actual outputs in sbox command (%s):\n%v", commandDescription, outputErrors)
+ }
+ // the created files match the declared files; now move them
+ for _, filePath := range outFiles {
+ tempPath := filepath.Join(tempDir, filePath)
+ destPath := filePath
+ if len(outputRoot) != 0 {
+ destPath = filepath.Join(outputRoot, filePath)
+ }
+ err := os.Rename(tempPath, destPath)
+ if err != nil {
+ return err
+ }
+ }
+
+ // TODO(jeffrygaston) if a process creates more output files than it declares, should there be a warning?
+ return nil
+}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 2ff018f..dc4e968 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -16,11 +16,14 @@
import (
"fmt"
+ "path"
"strings"
"github.com/google/blueprint"
"android/soong/android"
+ "android/soong/shared"
+ "path/filepath"
)
func init() {
@@ -32,6 +35,10 @@
pctx = android.NewPackageContext("android/soong/genrule")
)
+func init() {
+ pctx.HostBinToolVariable("sboxCmd", "sbox")
+}
+
type SourceFileGenerator interface {
GeneratedSourceFiles() android.Paths
GeneratedHeaderDirs() android.Paths
@@ -42,7 +49,11 @@
}
type generatorProperties struct {
- // command to run on one or more input files. Available variables for substitution:
+ // The command to run on one or more input files. Cmd supports substitution of a few variables
+ // (the actual substitution is implemented in GenerateAndroidBuildActions below)
+ //
+ // Available variables for substitution:
+ //
// $(location): the path to the first entry in tools or tool_files
// $(location <label>): the path to the tool or tool_file with name <label>
// $(in): one or more input files
@@ -51,9 +62,8 @@
// $(genDir): the sandbox directory for this tool; contains $(out)
// $$: a literal $
//
- // DO NOT directly reference paths to files in the source tree, or the
- // command will be missing proper dependencies to re-run if the files
- // change.
+ // All files used must be declared as inputs (to ensure proper up-to-date checks).
+ // Use "$(in)" directly in Cmd to ensure that all inputs used are declared.
Cmd string
// Enable reading a file containing dependencies in gcc format after the command completes
@@ -164,7 +174,7 @@
}
}
- cmd, err := android.Expand(g.properties.Cmd, func(name string) (string, error) {
+ rawCommand, err := android.Expand(g.properties.Cmd, func(name string) (string, error) {
switch name {
case "location":
if len(g.properties.Tools) > 0 {
@@ -175,14 +185,22 @@
case "in":
return "${in}", nil
case "out":
- return "${out}", nil
+ return "__SBOX_OUT_FILES__", nil
case "depfile":
if !g.properties.Depfile {
return "", fmt.Errorf("$(depfile) used without depfile property")
}
return "${depfile}", nil
case "genDir":
- return android.PathForModuleGen(ctx, "").String(), nil
+ genPath := android.PathForModuleGen(ctx, "").String()
+ var relativePath string
+ var err error
+ outputPath := android.PathForOutput(ctx).String()
+ relativePath, err = filepath.Rel(outputPath, genPath)
+ if err != nil {
+ panic(err)
+ }
+ return path.Join("__SBOX_OUT_DIR__", relativePath), nil
default:
if strings.HasPrefix(name, "location ") {
label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
@@ -201,8 +219,17 @@
return
}
+ // tell the sbox command which directory to use as its sandbox root
+ buildDir := android.PathForOutput(ctx).String()
+ sandboxPath := shared.TempDirForOutDir(buildDir)
+
+ // recall that Sprintf replaces percent sign expressions, whereas dollar signs expressions remain as written,
+ // to be replaced later by ninja_strings.go
+ sandboxCommand := fmt.Sprintf("$sboxCmd --sandbox-path %s --output-root %s -c %q $out", sandboxPath, buildDir, rawCommand)
+
ruleParams := blueprint.RuleParams{
- Command: cmd,
+ Command: sandboxCommand,
+ CommandDeps: []string{"$sboxCmd"},
}
var args []string
if g.properties.Depfile {
diff --git a/java/builder.go b/java/builder.go
index 895a999..041c303 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -37,10 +37,11 @@
// this, all java rules write into separate directories and then a post-processing step lists
// the files in the the directory into a list file that later rules depend on (and sometimes
// read from directly using @<listfile>)
- javac = pctx.AndroidStaticRule("javac",
+ javac = pctx.AndroidGomaStaticRule("javac",
blueprint.RuleParams{
Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` +
- `$javacCmd -encoding UTF-8 $javacFlags $bootClasspath $classpath ` +
+ `${config.JavacWrapper}$javacCmd ` +
+ `-encoding UTF-8 $javacFlags $bootClasspath $classpath ` +
`-extdirs "" -d $outDir @$out.rsp || ( rm -rf "$outDir"; exit 41 ) && ` +
`find $outDir -name "*.class" > $out`,
Rspfile: "$out.rsp",
@@ -88,6 +89,13 @@
pctx.StaticVariable("jarCmd", filepath.Join("${bootstrap.ToolDir}", "soong_zip"))
pctx.HostBinToolVariable("dxCmd", "dx")
pctx.HostJavaToolVariable("jarjarCmd", "jarjar.jar")
+
+ pctx.VariableFunc("JavacWrapper", func(config interface{}) (string, error) {
+ if override := config.(android.Config).Getenv("JAVAC_WRAPPER"); override != "" {
+ return override + " ", nil
+ }
+ return "", nil
+ })
}
type javaBuilderFlags struct {
diff --git a/python/python_test.go b/python/python_test.go
index 3f719f8..bb407e4 100644
--- a/python/python_test.go
+++ b/python/python_test.go
@@ -305,7 +305,7 @@
func TestPythonModule(t *testing.T) {
config, buildDir := setupBuildEnv(t)
- defer tearDownBuildEnv()
+ defer tearDownBuildEnv(buildDir)
android.TestPreDepsMutators(func(ctx android.RegisterMutatorsContext) {
ctx.BottomUp("version_split", versionSplitMutator()).Parallel()
})
@@ -432,8 +432,8 @@
return
}
-func tearDownBuildEnv() {
- os.RemoveAll(buildNamePrefix)
+func tearDownBuildEnv(buildDir string) {
+ os.RemoveAll(buildDir)
}
func findModule(ctx *blueprint.Context, name, variant string) blueprint.Module {
diff --git a/shared/paths.go b/shared/paths.go
new file mode 100644
index 0000000..f5dc5ac
--- /dev/null
+++ b/shared/paths.go
@@ -0,0 +1,26 @@
+// 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 shared
+
+// This file exists to share path-related logic between both soong_ui and soong
+
+import (
+ "path/filepath"
+)
+
+// Given the out directory, returns the root of the temp directory (to be cleared at the start of each execution of Soong)
+func TempDirForOutDir(outDir string) (tempPath string) {
+ return filepath.Join(outDir, ".temp")
+}
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
index 25520da..489c06d 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -18,6 +18,7 @@
deps: [
"soong-ui-logger",
"soong-ui-tracer",
+ "soong-shared",
],
srcs: [
"build.go",
diff --git a/ui/build/build.go b/ui/build/build.go
index 83dbcb6..1400c48 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -125,6 +125,8 @@
checkCaseSensitivity(ctx, config)
+ ensureEmptyDirectoriesExist(ctx, config.TempDir())
+
if what&BuildProductConfig != 0 {
// Run make for product config
runMakeProductConfig(ctx, config)
diff --git a/ui/build/config.go b/ui/build/config.go
index 7e8091b..16826f2 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -21,6 +21,8 @@
"runtime"
"strconv"
"strings"
+
+ "android/soong/shared"
)
type Config struct{ *configImpl }
@@ -250,6 +252,10 @@
return filepath.Join(c.OutDir(), "soong")
}
+func (c *configImpl) TempDir() string {
+ return shared.TempDirForOutDir(c.SoongOutDir())
+}
+
func (c *configImpl) KatiSuffix() string {
if c.katiSuffix != "" {
return c.katiSuffix
@@ -306,7 +312,7 @@
}
// RemoteParallel controls how many remote jobs (i.e., commands which contain
-// gomacc) are run in parallel. Note the paralleism of all other jobs is
+// gomacc) are run in parallel. Note the parallelism of all other jobs is
// still limited by Parallel()
func (c *configImpl) RemoteParallel() int {
if v, ok := c.environ.Get("NINJA_REMOTE_NUM_JOBS"); ok {
diff --git a/ui/build/exec.go b/ui/build/exec.go
index c8c5c9a..90fb19d 100644
--- a/ui/build/exec.go
+++ b/ui/build/exec.go
@@ -64,17 +64,20 @@
func (c *Cmd) Run() error {
c.prepare()
- return c.Cmd.Run()
+ err := c.Cmd.Run()
+ return err
}
func (c *Cmd) Output() ([]byte, error) {
c.prepare()
- return c.Cmd.Output()
+ bytes, err := c.Cmd.Output()
+ return bytes, err
}
func (c *Cmd) CombinedOutput() ([]byte, error) {
c.prepare()
- return c.Cmd.CombinedOutput()
+ bytes, err := c.Cmd.CombinedOutput()
+ return bytes, err
}
// StartOrFatal is equivalent to Start, but handles the error with a call to ctx.Fatal
diff --git a/ui/build/kati.go b/ui/build/kati.go
index 972b559..48c38d4 100644
--- a/ui/build/kati.go
+++ b/ui/build/kati.go
@@ -74,6 +74,7 @@
"--detect_android_echo",
"--color_warnings",
"--gen_all_targets",
+ "--werror_find_emulator",
"-f", "build/core/main.mk",
}
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index 5787a00..78d1170 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -15,6 +15,8 @@
package build
import (
+ "fmt"
+ "os"
"path/filepath"
"strconv"
"strings"
@@ -69,7 +71,69 @@
cmd.Stdin = ctx.Stdin()
cmd.Stdout = ctx.Stdout()
cmd.Stderr = ctx.Stderr()
+ logPath := filepath.Join(config.OutDir(), ".ninja_log")
+ ninjaHeartbeatDuration := time.Minute * 5
+ if overrideText, ok := cmd.Environment.Get("NINJA_HEARTBEAT_INTERVAL"); ok {
+ // For example, "1m"
+ overrideDuration, err := time.ParseDuration(overrideText)
+ if err == nil && overrideDuration.Seconds() > 0 {
+ ninjaHeartbeatDuration = overrideDuration
+ }
+ }
+ // Poll the ninja log for updates; if it isn't updated enough, then we want to show some diagnostics
+ done := make(chan struct{})
+ defer close(done)
+ ticker := time.NewTicker(ninjaHeartbeatDuration)
+ defer ticker.Stop()
+ checker := &statusChecker{}
+ go func() {
+ for {
+ select {
+ case <-ticker.C:
+ checker.check(ctx, config, logPath)
+ case <-done:
+ return
+ }
+ }
+ }()
+
startTime := time.Now()
- defer ctx.ImportNinjaLog(filepath.Join(config.OutDir(), ".ninja_log"), startTime)
+ defer ctx.ImportNinjaLog(logPath, startTime)
+
cmd.RunOrFatal()
}
+
+type statusChecker struct {
+ prevTime time.Time
+}
+
+func (c *statusChecker) check(ctx Context, config Config, pathToCheck string) {
+ info, err := os.Stat(pathToCheck)
+ var newTime time.Time
+ if err == nil {
+ newTime = info.ModTime()
+ }
+ if newTime == c.prevTime {
+ // ninja may be stuck
+ dumpStucknessDiagnostics(ctx, config, pathToCheck, newTime)
+ }
+ c.prevTime = newTime
+}
+
+// dumpStucknessDiagnostics gets called when it is suspected that Ninja is stuck and we want to output some diagnostics
+func dumpStucknessDiagnostics(ctx Context, config Config, statusPath string, lastUpdated time.Time) {
+
+ ctx.Verbosef("ninja may be stuck; last update to %v was %v. dumping process tree...", statusPath, lastUpdated)
+
+ // The "pstree" command doesn't exist on Mac, but "pstree" on Linux gives more convenient output than "ps"
+ // So, we try pstree first, and ps second
+ pstreeCommandText := fmt.Sprintf("pstree -pal %v", os.Getpid())
+ psCommandText := "ps -ef"
+ commandText := pstreeCommandText + " || " + psCommandText
+
+ cmd := Command(ctx, config, "dump process tree", "bash", "-c", commandText)
+ output := cmd.CombinedOutputOrFatal()
+ ctx.Verbose(string(output))
+
+ ctx.Verbosef("done\n")
+}
diff --git a/ui/build/util.go b/ui/build/util.go
index 37ac6b9..2555e8a 100644
--- a/ui/build/util.go
+++ b/ui/build/util.go
@@ -50,6 +50,19 @@
}
}
+// ensureEmptyDirectoriesExist ensures that the given directories exist and are empty
+func ensureEmptyDirectoriesExist(ctx Context, dirs ...string) {
+ // remove all the directories
+ for _, dir := range dirs {
+ err := os.RemoveAll(dir)
+ if err != nil {
+ ctx.Fatalf("Error removing %s: %q\n", dir, err)
+ }
+ }
+ // recreate all the directories
+ ensureDirectoriesExist(ctx, dirs...)
+}
+
// ensureEmptyFileExists ensures that the containing directory exists, and the
// specified file exists. If it doesn't exist, it will write an empty file.
func ensureEmptyFileExists(ctx Context, file string) {