Merge "Handle pack_relocations in linker.cc"
diff --git a/bpf/Android.bp b/bpf/Android.bp
new file mode 100644
index 0000000..7bd4d44
--- /dev/null
+++ b/bpf/Android.bp
@@ -0,0 +1,30 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// 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.
+//
+
+bootstrap_go_package {
+ name: "soong-bpf",
+ pkgPath: "android/soong/bpf",
+ deps: [
+ "blueprint",
+ "blueprint-proptools",
+ "soong-android",
+ "soong-cc-config",
+ ],
+ srcs: [
+ "bpf.go",
+ ],
+ pluginFor: ["soong_build"],
+}
diff --git a/bpf/bpf.go b/bpf/bpf.go
new file mode 100644
index 0000000..fa1f3ff
--- /dev/null
+++ b/bpf/bpf.go
@@ -0,0 +1,135 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// 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 bpf
+
+import (
+ "fmt"
+ "io"
+ "strings"
+
+ "android/soong/android"
+ _ "android/soong/cc/config"
+
+ "github.com/google/blueprint"
+)
+
+func init() {
+ android.RegisterModuleType("bpf", bpfFactory)
+ pctx.Import("android/soong/cc/config")
+}
+
+var (
+ pctx = android.NewPackageContext("android/soong/bpf")
+
+ cc = pctx.AndroidGomaStaticRule("cc",
+ blueprint.RuleParams{
+ Depfile: "${out}.d",
+ Deps: blueprint.DepsGCC,
+ Command: "$ccCmd --target=bpf -c $cFlags -MD -MF ${out}.d -o $out $in",
+ CommandDeps: []string{"$ccCmd"},
+ },
+ "ccCmd", "cFlags")
+)
+
+type BpfProperties struct {
+ Srcs []string
+ Cflags []string
+ Include_dirs []string
+}
+
+type bpf struct {
+ android.ModuleBase
+
+ properties BpfProperties
+
+ objs android.Paths
+}
+
+func (bpf *bpf) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ cflags := []string{
+ "-nostdlibinc",
+ "-O2",
+ "-isystem bionic/libc/include",
+ "-isystem bionic/libc/kernel/uapi",
+ // The architecture doesn't matter here, but asm/types.h is included by linux/types.h.
+ "-isystem bionic/libc/kernel/uapi/asm-arm64",
+ "-isystem bionic/libc/kernel/android/uapi",
+ "-I " + ctx.ModuleDir(),
+ }
+
+ for _, dir := range android.PathsForSource(ctx, bpf.properties.Include_dirs) {
+ cflags = append(cflags, "-I "+dir.String())
+ }
+
+ cflags = append(cflags, bpf.properties.Cflags...)
+
+ srcs := ctx.ExpandSources(bpf.properties.Srcs, nil)
+
+ for _, src := range srcs {
+ obj := android.ObjPathWithExt(ctx, "", src, "o")
+
+ ctx.Build(pctx, android.BuildParams{
+ Rule: cc,
+ Input: src,
+ Output: obj,
+ Args: map[string]string{
+ "cFlags": strings.Join(cflags, " "),
+ "ccCmd": "${config.ClangBin}/clang",
+ },
+ })
+
+ bpf.objs = append(bpf.objs, obj)
+ }
+}
+
+func (bpf *bpf) DepsMutator(ctx android.BottomUpMutatorContext) {
+ android.ExtractSourcesDeps(ctx, bpf.properties.Srcs)
+}
+
+func (bpf *bpf) AndroidMk() android.AndroidMkData {
+ return android.AndroidMkData{
+ Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
+ var names []string
+ fmt.Fprintln(w)
+ fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
+ fmt.Fprintln(w)
+ for _, obj := range bpf.objs {
+ objName := name + "_" + obj.Base()
+ names = append(names, objName)
+ fmt.Fprintln(w, "include $(CLEAR_VARS)")
+ fmt.Fprintln(w, "LOCAL_MODULE := ", objName)
+ fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", obj.String())
+ fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", obj.Base())
+ fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC")
+ fmt.Fprintln(w, "LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/bpf")
+ fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
+ fmt.Fprintln(w)
+ }
+ fmt.Fprintln(w, "include $(CLEAR_VARS)")
+ fmt.Fprintln(w, "LOCAL_MODULE := ", name)
+ fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES :=", strings.Join(names, " "))
+ fmt.Fprintln(w, "include $(BUILD_PHONY_PACKAGE)")
+ },
+ }
+}
+
+func bpfFactory() android.Module {
+ module := &bpf{}
+
+ module.AddProperties(&module.properties)
+
+ android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+ return module
+}
diff --git a/cc/androidmk.go b/cc/androidmk.go
index f417de0..263f0f3 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -71,7 +71,7 @@
fmt.Fprintln(w, "LOCAL_SHARED_LIBRARIES := "+strings.Join(c.Properties.AndroidMkSharedLibs, " "))
}
if c.Target().Os == android.Android &&
- String(c.Properties.Sdk_version) != "" && !c.useVndk() {
+ String(c.Properties.Sdk_version) != "" && !c.useVndk() && !c.inRecovery() {
fmt.Fprintln(w, "LOCAL_SDK_VERSION := "+String(c.Properties.Sdk_version))
fmt.Fprintln(w, "LOCAL_NDK_STL_VARIANT := none")
} else {
diff --git a/cc/sanitize.go b/cc/sanitize.go
index a704cf1..e59edf5 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -560,7 +560,6 @@
sanitize.Properties.Sanitize.Integer_overflow = boolPtr(b)
case cfi:
sanitize.Properties.Sanitize.Cfi = boolPtr(b)
- sanitize.Properties.Sanitize.Diag.Cfi = boolPtr(b)
default:
panic(fmt.Errorf("unknown sanitizerType %d", t))
}
diff --git a/cmd/path_interposer/Android.bp b/cmd/path_interposer/Android.bp
new file mode 100644
index 0000000..41a219f
--- /dev/null
+++ b/cmd/path_interposer/Android.bp
@@ -0,0 +1,20 @@
+// Copyright 2018 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: "path_interposer",
+ deps: ["soong-ui-build-paths"],
+ srcs: ["main.go"],
+ testSrcs: ["main_test.go"],
+}
diff --git a/cmd/path_interposer/main.go b/cmd/path_interposer/main.go
new file mode 100644
index 0000000..cd28b96
--- /dev/null
+++ b/cmd/path_interposer/main.go
@@ -0,0 +1,247 @@
+// Copyright 2018 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 (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "syscall"
+
+ "android/soong/ui/build/paths"
+)
+
+func main() {
+ interposer, err := os.Executable()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "Unable to locate interposer executable:", err)
+ os.Exit(1)
+ }
+
+ if fi, err := os.Lstat(interposer); err == nil {
+ if fi.Mode()&os.ModeSymlink != 0 {
+ link, err := os.Readlink(interposer)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "Unable to read link to interposer executable:", err)
+ os.Exit(1)
+ }
+ if filepath.IsAbs(link) {
+ interposer = link
+ } else {
+ interposer = filepath.Join(filepath.Dir(interposer), link)
+ }
+ }
+ } else {
+ fmt.Fprintln(os.Stderr, "Unable to stat interposer executable:", err)
+ os.Exit(1)
+ }
+
+ disableError := false
+ if e, ok := os.LookupEnv("TEMPORARY_DISABLE_PATH_RESTRICTIONS"); ok {
+ disableError = e == "1" || e == "y" || e == "yes" || e == "on" || e == "true"
+ }
+
+ exitCode, err := Main(os.Stdout, os.Stderr, interposer, os.Args, mainOpts{
+ disableError: disableError,
+
+ sendLog: paths.SendLog,
+ config: paths.GetConfig,
+ lookupParents: lookupParents,
+ })
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err.Error())
+ }
+ os.Exit(exitCode)
+}
+
+var usage = fmt.Errorf(`To use the PATH interposer:
+ * Write the original PATH variable to <interposer>_origpath
+ * Set up a directory of symlinks to the PATH interposer, and use that in PATH
+
+If a tool isn't in the allowed list, a log will be posted to the unix domain
+socket at <interposer>_log.`)
+
+type mainOpts struct {
+ disableError bool
+
+ sendLog func(logSocket string, entry *paths.LogEntry, done chan interface{})
+ config func(name string) paths.PathConfig
+ lookupParents func() []paths.LogProcess
+}
+
+func Main(stdout, stderr io.Writer, interposer string, args []string, opts mainOpts) (int, error) {
+ base := filepath.Base(args[0])
+
+ origPathFile := interposer + "_origpath"
+ if base == filepath.Base(interposer) {
+ return 1, usage
+ }
+
+ origPath, err := ioutil.ReadFile(origPathFile)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return 1, usage
+ } else {
+ return 1, fmt.Errorf("Failed to read original PATH: %v", err)
+ }
+ }
+
+ cmd := &exec.Cmd{
+ Args: args,
+ Env: os.Environ(),
+
+ Stdin: os.Stdin,
+ Stdout: stdout,
+ Stderr: stderr,
+ }
+
+ if err := os.Setenv("PATH", string(origPath)); err != nil {
+ return 1, fmt.Errorf("Failed to set PATH env: %v", err)
+ }
+
+ if config := opts.config(base); config.Log || config.Error {
+ var procs []paths.LogProcess
+ if opts.lookupParents != nil {
+ procs = opts.lookupParents()
+ }
+
+ if opts.sendLog != nil {
+ waitForLog := make(chan interface{})
+ opts.sendLog(interposer+"_log", &paths.LogEntry{
+ Basename: base,
+ Args: args,
+ Parents: procs,
+ }, waitForLog)
+ defer func() { <-waitForLog }()
+ }
+ if config.Error && !opts.disableError {
+ return 1, fmt.Errorf("%q is not allowed to be used. See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.", base)
+ }
+ }
+
+ cmd.Path, err = exec.LookPath(base)
+ if err != nil {
+ return 1, err
+ }
+
+ if err = cmd.Run(); err != nil {
+ if exitErr, ok := err.(*exec.ExitError); ok {
+ if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
+ if status.Exited() {
+ return status.ExitStatus(), nil
+ } else if status.Signaled() {
+ exitCode := 128 + int(status.Signal())
+ return exitCode, nil
+ } else {
+ return 1, exitErr
+ }
+ } else {
+ return 1, nil
+ }
+ }
+ }
+
+ return 0, nil
+}
+
+type procEntry struct {
+ Pid int
+ Ppid int
+ Command string
+}
+
+func readProcs() map[int]procEntry {
+ cmd := exec.Command("ps", "-o", "pid,ppid,command")
+ data, err := cmd.Output()
+ if err != nil {
+ return nil
+ }
+
+ return parseProcs(data)
+}
+
+func parseProcs(data []byte) map[int]procEntry {
+ lines := bytes.Split(data, []byte("\n"))
+ if len(lines) < 2 {
+ return nil
+ }
+ // Remove the header
+ lines = lines[1:]
+
+ ret := make(map[int]procEntry, len(lines))
+ for _, line := range lines {
+ fields := bytes.SplitN(line, []byte(" "), 2)
+ if len(fields) != 2 {
+ continue
+ }
+
+ pid, err := strconv.Atoi(string(fields[0]))
+ if err != nil {
+ continue
+ }
+
+ line = bytes.TrimLeft(fields[1], " ")
+
+ fields = bytes.SplitN(line, []byte(" "), 2)
+ if len(fields) != 2 {
+ continue
+ }
+
+ ppid, err := strconv.Atoi(string(fields[0]))
+ if err != nil {
+ continue
+ }
+
+ ret[pid] = procEntry{
+ Pid: pid,
+ Ppid: ppid,
+ Command: string(bytes.TrimLeft(fields[1], " ")),
+ }
+ }
+
+ return ret
+}
+
+func lookupParents() []paths.LogProcess {
+ procs := readProcs()
+ if procs == nil {
+ return nil
+ }
+
+ list := []paths.LogProcess{}
+ pid := os.Getpid()
+ for {
+ entry, ok := procs[pid]
+ if !ok {
+ break
+ }
+
+ list = append([]paths.LogProcess{
+ {
+ Pid: pid,
+ Command: entry.Command,
+ },
+ }, list...)
+
+ pid = entry.Ppid
+ }
+
+ return list
+}
diff --git a/cmd/path_interposer/main_test.go b/cmd/path_interposer/main_test.go
new file mode 100644
index 0000000..4b25c44
--- /dev/null
+++ b/cmd/path_interposer/main_test.go
@@ -0,0 +1,196 @@
+// Copyright 2018 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"
+ "path/filepath"
+ "testing"
+
+ "android/soong/ui/build/paths"
+)
+
+var tmpDir string
+var origPATH string
+
+func TestMain(m *testing.M) {
+ os.Exit(func() int {
+ var err error
+ tmpDir, err = ioutil.TempDir("", "interposer_test")
+ if err != nil {
+ panic(err)
+ }
+ defer os.RemoveAll(tmpDir)
+
+ origPATH = os.Getenv("PATH")
+ err = os.Setenv("PATH", "")
+ if err != nil {
+ panic(err)
+ }
+
+ return m.Run()
+ }())
+}
+
+func setup(t *testing.T) string {
+ f, err := ioutil.TempFile(tmpDir, "interposer")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+
+ err = ioutil.WriteFile(f.Name()+"_origpath", []byte(origPATH), 0666)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return f.Name()
+}
+
+func TestInterposer(t *testing.T) {
+ interposer := setup(t)
+
+ logConfig := func(name string) paths.PathConfig {
+ if name == "true" {
+ return paths.PathConfig{
+ Log: false,
+ Error: false,
+ }
+ } else if name == "path_interposer_test_not_allowed" {
+ return paths.PathConfig{
+ Log: false,
+ Error: true,
+ }
+ }
+ return paths.PathConfig{
+ Log: true,
+ Error: false,
+ }
+ }
+
+ testCases := []struct {
+ name string
+ args []string
+
+ exitCode int
+ err error
+ logEntry string
+ }{
+ {
+ name: "direct call",
+ args: []string{interposer},
+
+ exitCode: 1,
+ err: usage,
+ },
+ {
+ name: "relative call",
+ args: []string{filepath.Base(interposer)},
+
+ exitCode: 1,
+ err: usage,
+ },
+ {
+ name: "true",
+ args: []string{"/my/path/true"},
+ },
+ {
+ name: "relative true",
+ args: []string{"true"},
+ },
+ {
+ name: "exit code",
+ args: []string{"bash", "-c", "exit 42"},
+
+ exitCode: 42,
+ logEntry: "bash",
+ },
+ {
+ name: "signal",
+ args: []string{"bash", "-c", "kill -9 $$"},
+
+ exitCode: 137,
+ logEntry: "bash",
+ },
+ {
+ name: "does not exist",
+ args: []string{"path_interposer_test_does_not_exist"},
+
+ exitCode: 1,
+ err: fmt.Errorf(`exec: "path_interposer_test_does_not_exist": executable file not found in $PATH`),
+ logEntry: "path_interposer_test_does_not_exist",
+ },
+ {
+ name: "not allowed",
+ args: []string{"path_interposer_test_not_allowed"},
+
+ exitCode: 1,
+ err: fmt.Errorf(`"path_interposer_test_not_allowed" is not allowed to be used. See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.`),
+ logEntry: "path_interposer_test_not_allowed",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ logged := false
+ logFunc := func(logSocket string, entry *paths.LogEntry, done chan interface{}) {
+ defer close(done)
+
+ logged = true
+ if entry.Basename != testCase.logEntry {
+ t.Errorf("unexpected log entry:\nwant: %q\n got: %q", testCase.logEntry, entry.Basename)
+ }
+ }
+
+ exitCode, err := Main(ioutil.Discard, ioutil.Discard, interposer, testCase.args, mainOpts{
+ sendLog: logFunc,
+ config: logConfig,
+ })
+
+ errstr := func(err error) string {
+ if err == nil {
+ return ""
+ }
+ return err.Error()
+ }
+ if errstr(testCase.err) != errstr(err) {
+ t.Errorf("unexpected error:\nwant: %v\n got: %v", testCase.err, err)
+ }
+ if testCase.exitCode != exitCode {
+ t.Errorf("expected exit code %d, got %d", testCase.exitCode, exitCode)
+ }
+ if !logged && testCase.logEntry != "" {
+ t.Errorf("no log entry, but expected %q", testCase.logEntry)
+ }
+ })
+ }
+}
+
+func TestMissingPath(t *testing.T) {
+ interposer := setup(t)
+ err := os.Remove(interposer + "_origpath")
+ if err != nil {
+ t.Fatalf("Failed to remove:", err)
+ }
+
+ exitCode, err := Main(ioutil.Discard, ioutil.Discard, interposer, []string{"true"}, mainOpts{})
+ if err != usage {
+ t.Errorf("Unexpected error:\n got: %v\nwant: %v", err, usage)
+ }
+ if exitCode != 1 {
+ t.Errorf("expected exit code %d, got %d", 1, exitCode)
+ }
+}
diff --git a/java/aar.go b/java/aar.go
index 21e38a4..415170e 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -213,7 +213,7 @@
sdkDep := decodeSdkDep(ctx, sdkVersion)
if sdkDep.useFiles {
- sharedLibs = append(sharedLibs, sdkDep.jar)
+ sharedLibs = append(sharedLibs, sdkDep.jars...)
}
ctx.VisitDirectDeps(func(module android.Module) {
diff --git a/java/config/config.go b/java/config/config.go
index d44315c..ddcd156 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -30,6 +30,8 @@
DefaultBootclasspathLibraries = []string{"core-oj", "core-libart"}
DefaultSystemModules = "core-system-modules"
DefaultLibraries = []string{"ext", "framework", "okhttp"}
+ DefaultLambdaStubsLibrary = "core-lambda-stubs"
+ SdkLambdaStubsPath = "prebuilts/sdk/tools/core-lambda-stubs.jar"
DefaultJacocoExcludeFilter = []string{"org.junit.*", "org.jacoco.*", "org.mockito.*"}
diff --git a/java/droiddoc.go b/java/droiddoc.go
index cda50f0..6c76262 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -293,7 +293,7 @@
ctx.AddDependency(ctx.Module(), libTag, []string{"ext", "framework"}...)
}
} else if sdkDep.useModule {
- ctx.AddDependency(ctx.Module(), bootClasspathTag, sdkDep.module)
+ ctx.AddDependency(ctx.Module(), bootClasspathTag, sdkDep.modules...)
}
}
@@ -377,9 +377,9 @@
sdkDep := decodeSdkDep(ctx, String(j.properties.Sdk_version))
if sdkDep.invalidVersion {
- ctx.AddMissingDependencies([]string{sdkDep.module})
+ ctx.AddMissingDependencies(sdkDep.modules)
} else if sdkDep.useFiles {
- deps.bootClasspath = append(deps.bootClasspath, sdkDep.jar)
+ deps.bootClasspath = append(deps.bootClasspath, sdkDep.jars...)
}
ctx.VisitDirectDeps(func(module android.Module) {
diff --git a/java/java.go b/java/java.go
index 02fdc00..e571913 100644
--- a/java/java.go
+++ b/java/java.go
@@ -338,12 +338,12 @@
type sdkDep struct {
useModule, useFiles, useDefaultLibs, invalidVersion bool
- module string
+ modules []string
systemModules string
frameworkResModule string
- jar android.Path
+ jars android.Paths
aidl android.Path
}
@@ -412,11 +412,12 @@
aidl := filepath.Join(public_dir, "framework.aidl")
jarPath := android.ExistentPathForSource(ctx, jar)
aidlPath := android.ExistentPathForSource(ctx, aidl)
+ lambdaStubsPath := android.PathForSource(ctx, config.SdkLambdaStubsPath)
if (!jarPath.Valid() || !aidlPath.Valid()) && ctx.Config().AllowMissingDependencies() {
return sdkDep{
invalidVersion: true,
- module: fmt.Sprintf("sdk_%s_%s_android", api, v),
+ modules: []string{fmt.Sprintf("sdk_%s_%s_android", api, v)},
}
}
@@ -432,7 +433,7 @@
return sdkDep{
useFiles: true,
- jar: jarPath.Path(),
+ jars: android.Paths{jarPath.Path(), lambdaStubsPath},
aidl: aidlPath.Path(),
}
}
@@ -440,7 +441,7 @@
toModule := func(m, r string) sdkDep {
ret := sdkDep{
useModule: true,
- module: m,
+ modules: []string{m, config.DefaultLambdaStubsLibrary},
systemModules: m + "_system_modules",
frameworkResModule: r,
}
@@ -489,7 +490,7 @@
if ctx.Config().TargetOpenJDK9() {
ctx.AddDependency(ctx.Module(), systemModulesTag, sdkDep.systemModules)
}
- ctx.AddDependency(ctx.Module(), bootClasspathTag, sdkDep.module)
+ ctx.AddDependency(ctx.Module(), bootClasspathTag, sdkDep.modules...)
if Bool(j.deviceProperties.Optimize.Enabled) {
ctx.AddDependency(ctx.Module(), proguardRaiseTag, config.DefaultBootclasspathLibraries...)
ctx.AddDependency(ctx.Module(), proguardRaiseTag, config.DefaultLibraries...)
@@ -625,8 +626,9 @@
func getLinkType(m *Module, name string) linkType {
ver := String(m.deviceProperties.Sdk_version)
+ noStdLibs := Bool(m.properties.No_standard_libs)
switch {
- case name == "core.current.stubs" || ver == "core_current":
+ case name == "core.current.stubs" || ver == "core_current" || noStdLibs:
return javaCore
case name == "android_system_stubs_current" || strings.HasPrefix(ver, "system_"):
return javaSystem
@@ -684,10 +686,10 @@
if ctx.Device() {
sdkDep := decodeSdkDep(ctx, String(j.deviceProperties.Sdk_version))
if sdkDep.invalidVersion {
- ctx.AddMissingDependencies([]string{sdkDep.module})
+ ctx.AddMissingDependencies(sdkDep.modules)
} else if sdkDep.useFiles {
// sdkDep.jar is actually equivalent to turbine header.jar.
- deps.classpath = append(deps.classpath, sdkDep.jar)
+ deps.classpath = append(deps.classpath, sdkDep.jars...)
deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, sdkDep.aidl)
}
}
diff --git a/java/java_test.go b/java/java_test.go
index baf4b72..1678296 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -25,6 +25,8 @@
"strconv"
"strings"
"testing"
+
+ "github.com/google/blueprint/proptools"
)
var buildDir string
@@ -98,6 +100,7 @@
extraModules := []string{
"core-oj",
"core-libart",
+ "core-lambda-stubs",
"framework",
"ext",
"okhttp",
@@ -166,6 +169,7 @@
"prebuilts/sdk/14/public/android.jar": nil,
"prebuilts/sdk/14/public/framework.aidl": nil,
"prebuilts/sdk/14/system/android.jar": nil,
+ "prebuilts/sdk/current/core/android.jar": nil,
"prebuilts/sdk/current/public/android.jar": nil,
"prebuilts/sdk/current/public/framework.aidl": nil,
"prebuilts/sdk/current/public/core.jar": nil,
@@ -183,6 +187,7 @@
"prebuilts/sdk/28/public/api/bar-removed.txt": nil,
"prebuilts/sdk/28/system/api/bar-removed.txt": nil,
"prebuilts/sdk/28/test/api/bar-removed.txt": nil,
+ "prebuilts/sdk/tools/core-lambda-stubs.jar": nil,
"prebuilts/sdk/Android.bp": []byte(`prebuilt_apis { name: "prebuilt_apis",}`),
// For framework-res, which is an implicit dependency for framework
@@ -338,6 +343,7 @@
var classpathTestcases = []struct {
name string
+ unbundled bool
moduleType string
host android.OsClass
properties string
@@ -364,20 +370,20 @@
properties: `sdk_version: "14",`,
bootclasspath: []string{`""`},
system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
- classpath: []string{"prebuilts/sdk/14/public/android.jar"},
+ classpath: []string{"prebuilts/sdk/14/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
},
{
name: "current",
properties: `sdk_version: "current",`,
- bootclasspath: []string{"android_stubs_current"},
+ bootclasspath: []string{"android_stubs_current", "core-lambda-stubs"},
system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
},
{
name: "system_current",
properties: `sdk_version: "system_current",`,
- bootclasspath: []string{"android_system_stubs_current"},
+ bootclasspath: []string{"android_system_stubs_current", "core-lambda-stubs"},
system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
},
{
@@ -386,20 +392,20 @@
properties: `sdk_version: "system_14",`,
bootclasspath: []string{`""`},
system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
- classpath: []string{"prebuilts/sdk/14/system/android.jar"},
+ classpath: []string{"prebuilts/sdk/14/system/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
},
{
name: "test_current",
properties: `sdk_version: "test_current",`,
- bootclasspath: []string{"android_test_stubs_current"},
+ bootclasspath: []string{"android_test_stubs_current", "core-lambda-stubs"},
system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
},
{
name: "core_current",
properties: `sdk_version: "core_current",`,
- bootclasspath: []string{"core.current.stubs"},
+ bootclasspath: []string{"core.current.stubs", "core-lambda-stubs"},
system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
},
{
@@ -448,6 +454,24 @@
properties: `host_supported: true, no_standard_libs: true, system_modules: "none"`,
classpath: []string{},
},
+ {
+
+ name: "unbundled sdk v14",
+ unbundled: true,
+ properties: `sdk_version: "14",`,
+ bootclasspath: []string{`""`},
+ system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
+ classpath: []string{"prebuilts/sdk/14/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+ },
+ {
+
+ name: "unbundled current",
+ unbundled: true,
+ properties: `sdk_version: "current",`,
+ bootclasspath: []string{`""`},
+ system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
+ classpath: []string{"prebuilts/sdk/current/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+ },
}
func TestClasspath(t *testing.T) {
@@ -498,7 +522,12 @@
t.Run("1.8", func(t *testing.T) {
// Test default javac 1.8
- ctx := testJava(t, bp)
+ config := testConfig(nil)
+ if testcase.unbundled {
+ config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true)
+ }
+ ctx := testContext(config, bp, nil)
+ run(t, ctx, config)
javac := ctx.ModuleForTests("foo", variant).Rule("javac")
@@ -526,6 +555,9 @@
// Test again with javac 1.9
t.Run("1.9", func(t *testing.T) {
config := testConfig(map[string]string{"EXPERIMENTAL_USE_OPENJDK9": "true"})
+ if testcase.unbundled {
+ config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true)
+ }
ctx := testContext(config, bp, nil)
run(t, ctx, config)
diff --git a/java/sdk_library.go b/java/sdk_library.go
index ba121ab..d649d5a 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -345,6 +345,10 @@
Current ApiToCheck
Last_released ApiToCheck
}
+ Aidl struct {
+ Include_dirs []string
+ Local_include_dirs []string
+ }
}{}
props.Name = proptools.StringPtr(module.docsName(apiScope))
@@ -353,6 +357,8 @@
props.Custom_template = proptools.StringPtr("droiddoc-templates-sdk")
props.Installable = proptools.BoolPtr(false)
props.Libs = module.properties.Libs
+ props.Aidl.Include_dirs = module.deviceProperties.Aidl.Include_dirs
+ props.Aidl.Local_include_dirs = module.deviceProperties.Aidl.Local_include_dirs
droiddocArgs := " -hide 110 -hide 111 -hide 113 -hide 121 -hide 125 -hide 126 -hide 127 -hide 128" +
" -stubpackages " + strings.Join(module.properties.Api_packages, ":") +
diff --git a/python/scripts/stub_template_host.txt b/python/scripts/stub_template_host.txt
index b90a28b..386298e 100644
--- a/python/scripts/stub_template_host.txt
+++ b/python/scripts/stub_template_host.txt
@@ -18,8 +18,6 @@
for directory in search_path:
if directory == '': continue
path = os.path.join(directory, name)
- if os.path.islink(path):
- path = os.path.realpath(path)
# Check if path is actual executable file.
if os.path.isfile(path) and os.access(path, os.X_OK):
return path
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
index 5809894..1fe5b6f 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -13,9 +13,22 @@
// limitations under the License.
bootstrap_go_package {
+ name: "soong-ui-build-paths",
+ pkgPath: "android/soong/ui/build/paths",
+ srcs: [
+ "paths/config.go",
+ "paths/logs.go",
+ ],
+ testSrcs: [
+ "paths/logs_test.go",
+ ],
+}
+
+bootstrap_go_package {
name: "soong-ui-build",
pkgPath: "android/soong/ui/build",
deps: [
+ "soong-ui-build-paths",
"soong-ui-logger",
"soong-ui-tracer",
"soong-shared",
@@ -33,6 +46,7 @@
"finder.go",
"kati.go",
"ninja.go",
+ "path.go",
"proc_sync.go",
"signal.go",
"soong.go",
diff --git a/ui/build/build.go b/ui/build/build.go
index 78eb6a3..66dbf03 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -140,6 +140,8 @@
ensureEmptyDirectoriesExist(ctx, config.TempDir())
+ SetupPath(ctx, config)
+
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 5622dff..6f2d24a 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -51,6 +51,8 @@
targetDeviceDir string
brokenDupRules bool
+
+ pathReplaced bool
}
const srcDirFileCheck = "build/soong/root.bp"
diff --git a/ui/build/path.go b/ui/build/path.go
new file mode 100644
index 0000000..52658ef
--- /dev/null
+++ b/ui/build/path.go
@@ -0,0 +1,149 @@
+// Copyright 2018 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 (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/google/blueprint/microfactory"
+
+ "android/soong/ui/build/paths"
+)
+
+func parsePathDir(dir string) []string {
+ f, err := os.Open(dir)
+ if err != nil {
+ return nil
+ }
+ defer f.Close()
+
+ if s, err := f.Stat(); err != nil || !s.IsDir() {
+ return nil
+ }
+
+ infos, err := f.Readdir(-1)
+ if err != nil {
+ return nil
+ }
+
+ ret := make([]string, 0, len(infos))
+ for _, info := range infos {
+ if m := info.Mode(); !m.IsDir() && m&0111 != 0 {
+ ret = append(ret, info.Name())
+ }
+ }
+ return ret
+}
+
+func SetupPath(ctx Context, config Config) {
+ if config.pathReplaced {
+ return
+ }
+
+ ctx.BeginTrace("path")
+ defer ctx.EndTrace()
+
+ origPath, _ := config.Environment().Get("PATH")
+ myPath := filepath.Join(config.OutDir(), ".path")
+ interposer := myPath + "_interposer"
+
+ var cfg microfactory.Config
+ cfg.Map("android/soong", "build/soong")
+ cfg.TrimPath, _ = filepath.Abs(".")
+ if _, err := microfactory.Build(&cfg, interposer, "android/soong/cmd/path_interposer"); err != nil {
+ ctx.Fatalln("Failed to build path interposer:", err)
+ }
+
+ if err := ioutil.WriteFile(interposer+"_origpath", []byte(origPath), 0777); err != nil {
+ ctx.Fatalln("Failed to write original path:", err)
+ }
+
+ entries, err := paths.LogListener(ctx.Context, interposer+"_log")
+ if err != nil {
+ ctx.Fatalln("Failed to listen for path logs:", err)
+ }
+
+ go func() {
+ for log := range entries {
+ curPid := os.Getpid()
+ for i, proc := range log.Parents {
+ if proc.Pid == curPid {
+ log.Parents = log.Parents[i:]
+ break
+ }
+ }
+ procPrints := []string{
+ "See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.",
+ }
+ if len(log.Parents) > 0 {
+ procPrints = append(procPrints, "Process tree:")
+ for i, proc := range log.Parents {
+ procPrints = append(procPrints, fmt.Sprintf("%s→ %s", strings.Repeat(" ", i), proc.Command))
+ }
+ }
+
+ config := paths.GetConfig(log.Basename)
+ if config.Error {
+ ctx.Printf("Disallowed PATH tool %q used: %#v", log.Basename, log.Args)
+ for _, line := range procPrints {
+ ctx.Println(line)
+ }
+ } else {
+ ctx.Verbosef("Unknown PATH tool %q used: %#v", log.Basename, log.Args)
+ for _, line := range procPrints {
+ ctx.Verboseln(line)
+ }
+ }
+ }
+ }()
+
+ ensureEmptyDirectoriesExist(ctx, myPath)
+
+ var execs []string
+ for _, pathEntry := range filepath.SplitList(origPath) {
+ if pathEntry == "" {
+ // Ignore the current directory
+ continue
+ }
+ // TODO(dwillemsen): remove path entries under TOP? or anything
+ // that looks like an android source dir? They won't exist on
+ // the build servers, since they're added by envsetup.sh.
+ // (Except for the JDK, which is configured in ui/build/config.go)
+
+ execs = append(execs, parsePathDir(pathEntry)...)
+ }
+
+ allowAllSymlinks := config.Environment().IsEnvTrue("TEMPORARY_DISABLE_PATH_RESTRICTIONS")
+ for _, name := range execs {
+ if !paths.GetConfig(name).Symlink && !allowAllSymlinks {
+ continue
+ }
+
+ err := os.Symlink("../.path_interposer", filepath.Join(myPath, name))
+ // Intentionally ignore existing files -- that means that we
+ // just created it, and the first one should win.
+ if err != nil && !os.IsExist(err) {
+ ctx.Fatalln("Failed to create symlink:", err)
+ }
+ }
+
+ myPath, _ = filepath.Abs(myPath)
+ config.Environment().Set("PATH", myPath)
+ config.pathReplaced = true
+}
diff --git a/ui/build/paths/config.go b/ui/build/paths/config.go
new file mode 100644
index 0000000..548b038
--- /dev/null
+++ b/ui/build/paths/config.go
@@ -0,0 +1,160 @@
+// Copyright 2018 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 paths
+
+import "runtime"
+
+type PathConfig struct {
+ // Whether to create the symlink in the new PATH for this tool.
+ Symlink bool
+
+ // Whether to log about usages of this tool to the soong.log
+ Log bool
+
+ // Whether to exit with an error instead of invoking the underlying tool.
+ Error bool
+}
+
+var Allowed = PathConfig{
+ Symlink: true,
+ Log: false,
+ Error: false,
+}
+
+var Forbidden = PathConfig{
+ Symlink: false,
+ Log: true,
+ Error: true,
+}
+
+// The configuration used if the tool is not listed in the config below.
+// Currently this will create the symlink, but log a warning. In the future,
+// I expect this to move closer to Forbidden.
+var Missing = PathConfig{
+ Symlink: true,
+ Log: true,
+ Error: false,
+}
+
+func GetConfig(name string) PathConfig {
+ if config, ok := Configuration[name]; ok {
+ return config
+ }
+ return Missing
+}
+
+var Configuration = map[string]PathConfig{
+ "awk": Allowed,
+ "basename": Allowed,
+ "bash": Allowed,
+ "bzip2": Allowed,
+ "cat": Allowed,
+ "chmod": Allowed,
+ "cmp": Allowed,
+ "comm": Allowed,
+ "cp": Allowed,
+ "cut": Allowed,
+ "date": Allowed,
+ "dd": Allowed,
+ "diff": Allowed,
+ "dirname": Allowed,
+ "echo": Allowed,
+ "egrep": Allowed,
+ "env": Allowed,
+ "expr": Allowed,
+ "find": Allowed,
+ "getconf": Allowed,
+ "getopt": Allowed,
+ "git": Allowed,
+ "grep": Allowed,
+ "gzip": Allowed,
+ "head": Allowed,
+ "hexdump": Allowed,
+ "hostname": Allowed,
+ "jar": Allowed,
+ "java": Allowed,
+ "javap": Allowed,
+ "ln": Allowed,
+ "ls": Allowed,
+ "m4": Allowed,
+ "make": Allowed,
+ "md5sum": Allowed,
+ "mkdir": Allowed,
+ "mktemp": Allowed,
+ "mv": Allowed,
+ "openssl": Allowed,
+ "patch": Allowed,
+ "perl": Allowed,
+ "pstree": Allowed,
+ "python": Allowed,
+ "python2.7": Allowed,
+ "python3": Allowed,
+ "readlink": Allowed,
+ "realpath": Allowed,
+ "rm": Allowed,
+ "rsync": Allowed,
+ "runalarm": Allowed,
+ "sed": Allowed,
+ "setsid": Allowed,
+ "sh": Allowed,
+ "sha256sum": Allowed,
+ "sha512sum": Allowed,
+ "sort": Allowed,
+ "stat": Allowed,
+ "sum": Allowed,
+ "tar": Allowed,
+ "tail": Allowed,
+ "touch": Allowed,
+ "tr": Allowed,
+ "true": Allowed,
+ "uname": Allowed,
+ "uniq": Allowed,
+ "unzip": Allowed,
+ "wc": Allowed,
+ "which": Allowed,
+ "whoami": Allowed,
+ "xargs": Allowed,
+ "xmllint": Allowed,
+ "xz": Allowed,
+ "zip": Allowed,
+ "zipinfo": Allowed,
+
+ // Host toolchain is removed. In-tree toolchain should be used instead.
+ // GCC also can't find cc1 with this implementation.
+ "ar": Forbidden,
+ "as": Forbidden,
+ "cc": Forbidden,
+ "clang": Forbidden,
+ "clang++": Forbidden,
+ "gcc": Forbidden,
+ "g++": Forbidden,
+ "ld": Forbidden,
+ "ld.bfd": Forbidden,
+ "ld.gold": Forbidden,
+ "pkg-config": Forbidden,
+
+ // We've got prebuilts of these
+ //"dtc": Forbidden,
+ //"lz4": Forbidden,
+ //"lz4c": Forbidden,
+}
+
+func init() {
+ if runtime.GOOS == "darwin" {
+ Configuration["md5"] = Allowed
+ Configuration["sw_vers"] = Allowed
+ Configuration["xcrun"] = Allowed
+ }
+}
diff --git a/ui/build/paths/logs.go b/ui/build/paths/logs.go
new file mode 100644
index 0000000..6c24968
--- /dev/null
+++ b/ui/build/paths/logs.go
@@ -0,0 +1,211 @@
+// Copyright 2018 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 paths
+
+import (
+ "context"
+ "encoding/gob"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+ "path/filepath"
+ "runtime"
+ "sync"
+ "syscall"
+ "time"
+)
+
+type LogProcess struct {
+ Pid int
+ Command string
+}
+
+type LogEntry struct {
+ Basename string
+ Args []string
+ Parents []LogProcess
+}
+
+const timeoutDuration = time.Duration(100) * time.Millisecond
+
+type socketAddrFunc func(string) (string, func(), error)
+
+func procFallback(name string) (string, func(), error) {
+ d, err := os.Open(filepath.Dir(name))
+ if err != nil {
+ return "", func() {}, err
+ }
+
+ return fmt.Sprintf("/proc/self/fd/%d/%s", d.Fd(), filepath.Base(name)), func() {
+ d.Close()
+ }, nil
+}
+
+func tmpFallback(name string) (addr string, cleanup func(), err error) {
+ d, err := ioutil.TempDir("/tmp", "log_sock")
+ if err != nil {
+ cleanup = func() {}
+ return
+ }
+ cleanup = func() {
+ os.RemoveAll(d)
+ }
+
+ dir := filepath.Dir(name)
+
+ absDir, err := filepath.Abs(dir)
+ if err != nil {
+ return
+ }
+
+ err = os.Symlink(absDir, filepath.Join(d, "d"))
+ if err != nil {
+ return
+ }
+
+ addr = filepath.Join(d, "d", filepath.Base(name))
+
+ return
+}
+
+func getSocketAddr(name string) (string, func(), error) {
+ maxNameLen := len(syscall.RawSockaddrUnix{}.Path)
+
+ if len(name) < maxNameLen {
+ return name, func() {}, nil
+ }
+
+ if runtime.GOOS == "linux" {
+ addr, cleanup, err := procFallback(name)
+ if err == nil {
+ if len(addr) < maxNameLen {
+ return addr, cleanup, nil
+ }
+ }
+ cleanup()
+ }
+
+ addr, cleanup, err := tmpFallback(name)
+ if err == nil {
+ if len(addr) < maxNameLen {
+ return addr, cleanup, nil
+ }
+ }
+ cleanup()
+
+ return name, func() {}, fmt.Errorf("Path to socket is still over size limit, fallbacks failed.")
+}
+
+func dial(name string, lookup socketAddrFunc, timeout time.Duration) (net.Conn, error) {
+ socket, cleanup, err := lookup(name)
+ defer cleanup()
+ if err != nil {
+ return nil, err
+ }
+
+ dialer := &net.Dialer{
+ Timeout: timeout,
+ }
+ return dialer.Dial("unix", socket)
+}
+
+func listen(name string, lookup socketAddrFunc) (net.Listener, error) {
+ socket, cleanup, err := lookup(name)
+ defer cleanup()
+ if err != nil {
+ return nil, err
+ }
+
+ return net.Listen("unix", socket)
+}
+
+func SendLog(logSocket string, entry *LogEntry, done chan interface{}) {
+ sendLog(logSocket, getSocketAddr, timeoutDuration, entry, done)
+}
+
+func sendLog(logSocket string, lookup socketAddrFunc, timeout time.Duration, entry *LogEntry, done chan interface{}) {
+ defer close(done)
+
+ conn, err := dial(logSocket, lookup, timeout)
+ if err != nil {
+ return
+ }
+ defer conn.Close()
+
+ if timeout != 0 {
+ conn.SetDeadline(time.Now().Add(timeout))
+ }
+
+ enc := gob.NewEncoder(conn)
+ enc.Encode(entry)
+}
+
+func LogListener(ctx context.Context, logSocket string) (chan *LogEntry, error) {
+ return logListener(ctx, logSocket, getSocketAddr)
+}
+
+func logListener(ctx context.Context, logSocket string, lookup socketAddrFunc) (chan *LogEntry, error) {
+ ret := make(chan *LogEntry, 5)
+
+ if err := os.Remove(logSocket); err != nil && !os.IsNotExist(err) {
+ return nil, err
+ }
+
+ ln, err := listen(logSocket, lookup)
+ if err != nil {
+ return nil, err
+ }
+
+ go func() {
+ for {
+ select {
+ case <-ctx.Done():
+ ln.Close()
+ }
+ }
+ }()
+
+ go func() {
+ var wg sync.WaitGroup
+ defer func() {
+ wg.Wait()
+ close(ret)
+ }()
+
+ for {
+ conn, err := ln.Accept()
+ if err != nil {
+ ln.Close()
+ break
+ }
+ conn.SetDeadline(time.Now().Add(timeoutDuration))
+ wg.Add(1)
+
+ go func() {
+ defer wg.Done()
+ defer conn.Close()
+
+ dec := gob.NewDecoder(conn)
+ entry := &LogEntry{}
+ if err := dec.Decode(entry); err != nil {
+ return
+ }
+ ret <- entry
+ }()
+ }
+ }()
+ return ret, nil
+}
diff --git a/ui/build/paths/logs_test.go b/ui/build/paths/logs_test.go
new file mode 100644
index 0000000..3b1005f
--- /dev/null
+++ b/ui/build/paths/logs_test.go
@@ -0,0 +1,154 @@
+// Copyright 2018 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 paths
+
+import (
+ "context"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "strings"
+ "testing"
+)
+
+func TestSendLog(t *testing.T) {
+ t.Run("Short name", func(t *testing.T) {
+ d, err := ioutil.TempDir("", "s")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(d)
+ f := filepath.Join(d, "s")
+
+ testSendLog(t, f, getSocketAddr)
+ })
+
+ testLongName := func(t *testing.T, lookup socketAddrFunc) {
+ d, err := ioutil.TempDir("", strings.Repeat("s", 150))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(d)
+ f := filepath.Join(d, strings.Repeat("s", 10))
+
+ testSendLog(t, f, lookup)
+ }
+
+ // Using a name longer than the ~100 limit of the underlying calls to bind, etc
+ t.Run("Long name", func(t *testing.T) {
+ testLongName(t, getSocketAddr)
+ })
+
+ if runtime.GOOS == "linux" {
+ t.Run("Long name proc fallback", func(t *testing.T) {
+ testLongName(t, procFallback)
+ })
+ }
+
+ t.Run("Long name tmp fallback", func(t *testing.T) {
+ testLongName(t, tmpFallback)
+ })
+}
+
+func testSendLog(t *testing.T, socket string, lookup socketAddrFunc) {
+ recv, err := logListener(context.Background(), socket, lookup)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ go func() {
+ for i := 0; i < 10; i++ {
+ sendLog(socket, lookup, 0, &LogEntry{
+ Basename: "test",
+ Args: []string{"foo", "bar"},
+ }, make(chan interface{}))
+ }
+ }()
+
+ count := 0
+ for {
+ entry := <-recv
+ if entry == nil {
+ if count != 10 {
+ t.Errorf("Expected 10 logs, got %d", count)
+ }
+ return
+ }
+
+ ref := LogEntry{
+ Basename: "test",
+ Args: []string{"foo", "bar"},
+ }
+ if !reflect.DeepEqual(ref, *entry) {
+ t.Fatalf("Bad log entry: %v", entry)
+ }
+ count++
+
+ if count == 10 {
+ return
+ }
+ }
+}
+
+func TestSendLogError(t *testing.T) {
+ d, err := ioutil.TempDir("", "log_socket")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(d)
+
+ // Missing log sockets should not block waiting for the timeout to elapse
+ t.Run("Missing file", func(t *testing.T) {
+ sendLog(filepath.Join(d, "missing"), getSocketAddr, 0, &LogEntry{}, make(chan interface{}))
+ })
+
+ // Non-sockets should not block waiting for the timeout to elapse
+ t.Run("Regular file", func(t *testing.T) {
+ f := filepath.Join(d, "file")
+ if fp, err := os.Create(f); err == nil {
+ fp.Close()
+ } else {
+ t.Fatal(err)
+ }
+
+ sendLog(f, getSocketAddr, 0, &LogEntry{}, make(chan interface{}))
+ })
+
+ // If the reader is stuck, we should be able to make progress
+ t.Run("Reader not reading", func(t *testing.T) {
+ f := filepath.Join(d, "sock1")
+
+ ln, err := listen(f, getSocketAddr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer ln.Close()
+
+ done := make(chan bool, 1)
+ go func() {
+ for i := 0; i < 10; i++ {
+ sendLog(f, getSocketAddr, timeoutDuration, &LogEntry{
+ // Ensure a relatively large payload
+ Basename: strings.Repeat(" ", 100000),
+ }, make(chan interface{}))
+ }
+ done <- true
+ }()
+
+ <-done
+ })
+}
diff --git a/ui/build/sandbox/darwin/global.sb b/ui/build/sandbox/darwin/global.sb
index 47d0c43..e32b64b 100644
--- a/ui/build/sandbox/darwin/global.sb
+++ b/ui/build/sandbox/darwin/global.sb
@@ -35,6 +35,12 @@
(global-name-regex #"^com\.apple\.distributed_notifications") ; xcodebuild in Soong
)
+; Allow suid /bin/ps to function
+(allow process-exec (literal "/bin/ps") (with no-sandbox))
+
+; Allow path_interposer unix domain socket without logging
+(allow network-outbound (literal (string-append (param "OUT_DIR") "/.path_interposer_log")))
+
; Allow executing any file
(allow process-exec*)
(allow process-fork)