Merge "bp2build: build static version of libstdc++."
diff --git a/android/bazel.go b/android/bazel.go
index 9de016e..6c476a7 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -250,6 +250,7 @@
 		"libsystemproperties",              // cparsons@, cc_library_static, wrong include paths
 		"libpropertyinfoparser",            // cparsons@, cc_library_static, wrong include paths
 		"libarm-optimized-routines-string", // jingwen@, cc_library_static, OK for bp2build but b/186615213 (asflags not handled in  bp2build), version script assignment of 'LIBC' to symbol 'memcmp' failed: symbol not defined (also for memrchr, strnlen)
+		"fmtlib_ndk",                       // http://b/187040371, cc_library_static, OK for bp2build but format-inl.h:11:10: fatal error: 'cassert' file not found for mixed builds
 	}
 
 	// Used for quicker lookups
diff --git a/cc/builder.go b/cc/builder.go
index ad7e1e6..0542015 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -126,15 +126,22 @@
 
 	_ = pctx.SourcePathVariable("stripPath", "build/soong/scripts/strip.sh")
 	_ = pctx.SourcePathVariable("xzCmd", "prebuilts/build-tools/${config.HostPrebuiltTag}/bin/xz")
+	_ = pctx.SourcePathVariable("createMiniDebugInfo", "prebuilts/build-tools/${config.HostPrebuiltTag}/bin/create_minidebuginfo")
 
 	// Rule to invoke `strip` (to discard symbols and data from object files).
 	strip = pctx.AndroidStaticRule("strip",
 		blueprint.RuleParams{
-			Depfile:     "${out}.d",
-			Deps:        blueprint.DepsGCC,
-			Command:     "XZ=$xzCmd CLANG_BIN=${config.ClangBin} $stripPath ${args} -i ${in} -o ${out} -d ${out}.d",
-			CommandDeps: []string{"$stripPath", "$xzCmd"},
-			Pool:        darwinStripPool,
+			Depfile: "${out}.d",
+			Deps:    blueprint.DepsGCC,
+			Command: "XZ=$xzCmd CREATE_MINIDEBUGINFO=$createMiniDebugInfo CLANG_BIN=${config.ClangBin} $stripPath ${args} -i ${in} -o ${out} -d ${out}.d",
+			CommandDeps: func() []string {
+				if runtime.GOOS != "darwin" {
+					return []string{"$stripPath", "$xzCmd", "$createMiniDebugInfo"}
+				} else {
+					return []string{"$stripPath", "$xzCmd"}
+				}
+			}(),
+			Pool: darwinStripPool,
 		},
 		"args", "crossCompile")
 
diff --git a/cc/makevars.go b/cc/makevars.go
index fa0b2cc..2b7bb9b 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -151,6 +151,7 @@
 
 	ctx.Strict("SOONG_STRIP_PATH", "${stripPath}")
 	ctx.Strict("XZ", "${xzCmd}")
+	ctx.Strict("CREATE_MINIDEBUGINFO", "${createMiniDebugInfo}")
 
 	includeFlags, err := ctx.Eval("${config.CommonGlobalIncludes}")
 	if err != nil {
diff --git a/cmd/run_with_timeout/Android.bp b/cmd/run_with_timeout/Android.bp
new file mode 100644
index 0000000..76262cc
--- /dev/null
+++ b/cmd/run_with_timeout/Android.bp
@@ -0,0 +1,27 @@
+// Copyright 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+    name: "run_with_timeout",
+    srcs: [
+        "run_with_timeout.go",
+    ],
+    testSrcs: [
+        "run_with_timeout_test.go",
+    ],
+}
diff --git a/cmd/run_with_timeout/run_with_timeout.go b/cmd/run_with_timeout/run_with_timeout.go
new file mode 100644
index 0000000..e225872
--- /dev/null
+++ b/cmd/run_with_timeout/run_with_timeout.go
@@ -0,0 +1,110 @@
+// Copyright 2021 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.
+
+// run_with_timeout is a utility that can kill a wrapped command after a configurable timeout,
+// optionally running a command to collect debugging information first.
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"os/exec"
+	"syscall"
+	"time"
+)
+
+var (
+	timeout      = flag.Duration("timeout", 0, "time after which to kill command (example: 60s)")
+	onTimeoutCmd = flag.String("on_timeout", "", "command to run with `PID=<pid> sh -c` after timeout.")
+)
+
+func usage() {
+	fmt.Fprintf(os.Stderr, "usage: %s [--timeout N] [--on_timeout CMD] -- command [args...]\n", os.Args[0])
+	flag.PrintDefaults()
+	fmt.Fprintln(os.Stderr, "run_with_timeout is a utility that can kill a wrapped command after a configurable timeout,")
+	fmt.Fprintln(os.Stderr, "optionally running a command to collect debugging information first.")
+
+	os.Exit(2)
+}
+
+func main() {
+	flag.Usage = usage
+	flag.Parse()
+
+	if flag.NArg() < 1 {
+		fmt.Fprintln(os.Stderr, "command is required")
+		usage()
+	}
+
+	err := runWithTimeout(flag.Arg(0), flag.Args()[1:], *timeout, *onTimeoutCmd,
+		os.Stdin, os.Stdout, os.Stderr)
+	if err != nil {
+		if exitErr, ok := err.(*exec.ExitError); ok {
+			fmt.Fprintln(os.Stderr, "process exited with error:", exitErr.Error())
+		} else {
+			fmt.Fprintln(os.Stderr, "error:", err.Error())
+		}
+		os.Exit(1)
+	}
+}
+
+func runWithTimeout(command string, args []string, timeout time.Duration, onTimeoutCmdStr string,
+	stdin io.Reader, stdout, stderr io.Writer) error {
+	cmd := exec.Command(command, args...)
+	cmd.Stdin, cmd.Stdout, cmd.Stderr = stdin, stdout, stderr
+	err := cmd.Start()
+	if err != nil {
+		return err
+	}
+
+	// waitCh will signal the subprocess exited.
+	waitCh := make(chan error)
+	go func() {
+		waitCh <- cmd.Wait()
+	}()
+
+	// timeoutCh will signal the subprocess timed out if timeout was set.
+	var timeoutCh <-chan time.Time = make(chan time.Time)
+	if timeout > 0 {
+		timeoutCh = time.After(timeout)
+	}
+
+	select {
+	case err := <-waitCh:
+		if exitErr, ok := err.(*exec.ExitError); ok {
+			return fmt.Errorf("process exited with error: %w", exitErr)
+		}
+		return err
+	case <-timeoutCh:
+		// Continue below.
+	}
+
+	// Process timed out before exiting.
+	defer cmd.Process.Signal(syscall.SIGKILL)
+
+	if onTimeoutCmdStr != "" {
+		onTimeoutCmd := exec.Command("sh", "-c", onTimeoutCmdStr)
+		onTimeoutCmd.Stdin, onTimeoutCmd.Stdout, onTimeoutCmd.Stderr = stdin, stdout, stderr
+		onTimeoutCmd.Env = append(os.Environ(), fmt.Sprintf("PID=%d", cmd.Process.Pid))
+		err := onTimeoutCmd.Run()
+		if err != nil {
+			return fmt.Errorf("on_timeout command %q exited with error: %w", onTimeoutCmdStr, err)
+		}
+	}
+
+	return fmt.Errorf("timed out after %s", timeout.String())
+}
diff --git a/cmd/run_with_timeout/run_with_timeout_test.go b/cmd/run_with_timeout/run_with_timeout_test.go
new file mode 100644
index 0000000..aebd336
--- /dev/null
+++ b/cmd/run_with_timeout/run_with_timeout_test.go
@@ -0,0 +1,94 @@
+// Copyright 2021 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"
+	"io"
+	"testing"
+	"time"
+)
+
+func Test_runWithTimeout(t *testing.T) {
+	type args struct {
+		command      string
+		args         []string
+		timeout      time.Duration
+		onTimeoutCmd string
+		stdin        io.Reader
+	}
+	tests := []struct {
+		name       string
+		args       args
+		wantStdout string
+		wantStderr string
+		wantErr    bool
+	}{
+		{
+			name: "no timeout",
+			args: args{
+				command: "echo",
+				args:    []string{"foo"},
+			},
+			wantStdout: "foo\n",
+		},
+		{
+			name: "timeout not reached",
+			args: args{
+				command: "echo",
+				args:    []string{"foo"},
+				timeout: 1 * time.Second,
+			},
+			wantStdout: "foo\n",
+		},
+		{
+			name: "timed out",
+			args: args{
+				command: "sh",
+				args:    []string{"-c", "sleep 1 && echo foo"},
+				timeout: 1 * time.Millisecond,
+			},
+			wantErr: true,
+		},
+		{
+			name: "on_timeout command",
+			args: args{
+				command:      "sh",
+				args:         []string{"-c", "sleep 1 && echo foo"},
+				timeout:      1 * time.Millisecond,
+				onTimeoutCmd: "echo bar",
+			},
+			wantStdout: "bar\n",
+			wantErr:    true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stdout := &bytes.Buffer{}
+			stderr := &bytes.Buffer{}
+			err := runWithTimeout(tt.args.command, tt.args.args, tt.args.timeout, tt.args.onTimeoutCmd, tt.args.stdin, stdout, stderr)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("runWithTimeout() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if gotStdout := stdout.String(); gotStdout != tt.wantStdout {
+				t.Errorf("runWithTimeout() gotStdout = %v, want %v", gotStdout, tt.wantStdout)
+			}
+			if gotStderr := stderr.String(); gotStderr != tt.wantStderr {
+				t.Errorf("runWithTimeout() gotStderr = %v, want %v", gotStderr, tt.wantStderr)
+			}
+		})
+	}
+}
diff --git a/java/config/config.go b/java/config/config.go
index 30c6f91..273084c 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -69,6 +69,8 @@
 	pctx.StaticVariable("JavacHeapSize", "2048M")
 	pctx.StaticVariable("JavacHeapFlags", "-J-Xmx${JavacHeapSize}")
 	pctx.StaticVariable("DexFlags", "-JXX:OnError='cat hs_err_pid%p.log' -JXX:CICompilerCount=6 -JXX:+UseDynamicNumberOfGCThreads")
+	// TODO(b/181095653): remove duplicated flags.
+	pctx.StaticVariable("DexJavaFlags", "-XX:OnError='cat hs_err_pid%p.log' -XX:CICompilerCount=6 -XX:+UseDynamicNumberOfGCThreads -Xmx2G")
 
 	pctx.StaticVariable("CommonJdkFlags", strings.Join([]string{
 		`-Xmaxerrs 9999999`,
diff --git a/java/dex.go b/java/dex.go
index 7898e9d..6bf0143 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -84,6 +84,11 @@
 	return BoolDefault(d.dexProperties.Optimize.Enabled, d.dexProperties.Optimize.EnabledByDefault)
 }
 
+func init() {
+	pctx.HostBinToolVariable("runWithTimeoutCmd", "run_with_timeout")
+	pctx.SourcePathVariable("jstackCmd", "${config.JavaToolchain}/jstack")
+}
+
 var d8, d8RE = pctx.MultiCommandRemoteStaticRules("d8",
 	blueprint.RuleParams{
 		Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` +
@@ -117,7 +122,10 @@
 		Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` +
 			`rm -f "$outDict" && rm -rf "${outUsageDir}" && ` +
 			`mkdir -p $$(dirname ${outUsage}) && ` +
-			`$r8Template${config.R8Cmd} ${config.DexFlags} -injars $in --output $outDir ` +
+			// TODO(b/181095653): remove R8 timeout and go back to config.R8Cmd.
+			`${runWithTimeoutCmd} -timeout 30m -on_timeout '${jstackCmd} $$PID' -- ` +
+			`$r8Template${config.JavaCmd} ${config.DexJavaFlags} -cp ${config.R8Jar} ` +
+			`com.android.tools.r8.compatproguard.CompatProguard -injars $in --output $outDir ` +
 			`--no-data-resources ` +
 			`-printmapping ${outDict} ` +
 			`-printusage ${outUsage} ` +
@@ -128,9 +136,10 @@
 			`$zipTemplate${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` +
 			`${config.MergeZipsCmd} -D -stripFile "**/*.class" $out $outDir/classes.dex.jar $in`,
 		CommandDeps: []string{
-			"${config.R8Cmd}",
+			"${config.R8Jar}",
 			"${config.SoongZipCmd}",
 			"${config.MergeZipsCmd}",
+			"${runWithTimeoutCmd}",
 		},
 	}, map[string]*remoteexec.REParams{
 		"$r8Template": &remoteexec.REParams{
diff --git a/rust/androidmk.go b/rust/androidmk.go
index 5f89d73..ea45ebd 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -91,7 +91,6 @@
 	if binary.distFile.Valid() {
 		ret.DistFiles = android.MakeDefaultDistFiles(binary.distFile.Path())
 	}
-
 	ret.Class = "EXECUTABLES"
 }
 
@@ -201,3 +200,36 @@
 			entries.SetString("LOCAL_MODULE_STEM", stem)
 		})
 }
+
+func (fuzz *fuzzDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+	ctx.SubAndroidMk(entries, fuzz.binaryDecorator)
+
+	var fuzzFiles []string
+	for _, d := range fuzz.corpus {
+		fuzzFiles = append(fuzzFiles,
+			filepath.Dir(fuzz.corpusIntermediateDir.String())+":corpus/"+d.Base())
+	}
+
+	for _, d := range fuzz.data {
+		fuzzFiles = append(fuzzFiles,
+			filepath.Dir(fuzz.dataIntermediateDir.String())+":data/"+d.Rel())
+	}
+
+	if fuzz.dictionary != nil {
+		fuzzFiles = append(fuzzFiles,
+			filepath.Dir(fuzz.dictionary.String())+":"+fuzz.dictionary.Base())
+	}
+
+	if fuzz.config != nil {
+		fuzzFiles = append(fuzzFiles,
+			filepath.Dir(fuzz.config.String())+":config.json")
+	}
+
+	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext,
+		entries *android.AndroidMkEntries) {
+		entries.SetBool("LOCAL_IS_FUZZ_TARGET", true)
+		if len(fuzzFiles) > 0 {
+			entries.AddStrings("LOCAL_TEST_DATA", fuzzFiles...)
+		}
+	})
+}
diff --git a/rust/fuzz.go b/rust/fuzz.go
index d699971..7e1c55a 100644
--- a/rust/fuzz.go
+++ b/rust/fuzz.go
@@ -15,6 +15,10 @@
 package rust
 
 import (
+	"path/filepath"
+	"sort"
+	"strings"
+
 	"android/soong/android"
 	"android/soong/cc"
 	"android/soong/rust/config"
@@ -22,6 +26,7 @@
 
 func init() {
 	android.RegisterModuleType("rust_fuzz", RustFuzzFactory)
+	android.RegisterSingletonType("rust_fuzz_packaging", rustFuzzPackagingFactory)
 }
 
 type fuzzDecorator struct {
@@ -93,3 +98,204 @@
 func (fuzzer *fuzzDecorator) autoDep(ctx android.BottomUpMutatorContext) autoDep {
 	return rlibAutoDep
 }
+
+// Responsible for generating GNU Make rules that package fuzz targets into
+// their architecture & target/host specific zip file.
+type rustFuzzPackager struct {
+	packages    android.Paths
+	fuzzTargets map[string]bool
+}
+
+func rustFuzzPackagingFactory() android.Singleton {
+	return &rustFuzzPackager{}
+}
+
+type fileToZip struct {
+	SourceFilePath        android.Path
+	DestinationPathPrefix string
+}
+
+type archOs struct {
+	hostOrTarget string
+	arch         string
+	dir          string
+}
+
+func (s *rustFuzzPackager) GenerateBuildActions(ctx android.SingletonContext) {
+
+	// Map between each architecture + host/device combination.
+	archDirs := make(map[archOs][]fileToZip)
+
+	// List of individual fuzz targets.
+	s.fuzzTargets = make(map[string]bool)
+
+	ctx.VisitAllModules(func(module android.Module) {
+		// Discard non-fuzz targets.
+		rustModule, ok := module.(*Module)
+		if !ok {
+			return
+		}
+
+		fuzzModule, ok := rustModule.compiler.(*fuzzDecorator)
+		if !ok {
+			return
+		}
+
+		// Discard ramdisk + vendor_ramdisk + recovery modules, they're duplicates of
+		// fuzz targets we're going to package anyway.
+		if !rustModule.Enabled() || rustModule.Properties.PreventInstall ||
+			rustModule.InRamdisk() || rustModule.InVendorRamdisk() || rustModule.InRecovery() {
+			return
+		}
+
+		// Discard modules that are in an unavailable namespace.
+		if !rustModule.ExportedToMake() {
+			return
+		}
+
+		hostOrTargetString := "target"
+		if rustModule.Host() {
+			hostOrTargetString = "host"
+		}
+
+		archString := rustModule.Arch().ArchType.String()
+		archDir := android.PathForIntermediates(ctx, "fuzz", hostOrTargetString, archString)
+		archOs := archOs{hostOrTarget: hostOrTargetString, arch: archString, dir: archDir.String()}
+
+		var files []fileToZip
+		builder := android.NewRuleBuilder(pctx, ctx)
+
+		// Package the corpora into a zipfile.
+		if fuzzModule.corpus != nil {
+			corpusZip := archDir.Join(ctx, module.Name()+"_seed_corpus.zip")
+			command := builder.Command().BuiltTool("soong_zip").
+				Flag("-j").
+				FlagWithOutput("-o ", corpusZip)
+			rspFile := corpusZip.ReplaceExtension(ctx, "rsp")
+			command.FlagWithRspFileInputList("-r ", rspFile, fuzzModule.corpus)
+			files = append(files, fileToZip{corpusZip, ""})
+		}
+
+		// Package the data into a zipfile.
+		if fuzzModule.data != nil {
+			dataZip := archDir.Join(ctx, module.Name()+"_data.zip")
+			command := builder.Command().BuiltTool("soong_zip").
+				FlagWithOutput("-o ", dataZip)
+			for _, f := range fuzzModule.data {
+				intermediateDir := strings.TrimSuffix(f.String(), f.Rel())
+				command.FlagWithArg("-C ", intermediateDir)
+				command.FlagWithInput("-f ", f)
+			}
+			files = append(files, fileToZip{dataZip, ""})
+		}
+
+		// The executable.
+		files = append(files, fileToZip{rustModule.unstrippedOutputFile.Path(), ""})
+
+		// The dictionary.
+		if fuzzModule.dictionary != nil {
+			files = append(files, fileToZip{fuzzModule.dictionary, ""})
+		}
+
+		// Additional fuzz config.
+		if fuzzModule.config != nil {
+			files = append(files, fileToZip{fuzzModule.config, ""})
+		}
+
+		fuzzZip := archDir.Join(ctx, module.Name()+".zip")
+
+		command := builder.Command().BuiltTool("soong_zip").
+			Flag("-j").
+			FlagWithOutput("-o ", fuzzZip)
+
+		for _, file := range files {
+			if file.DestinationPathPrefix != "" {
+				command.FlagWithArg("-P ", file.DestinationPathPrefix)
+			} else {
+				command.Flag("-P ''")
+			}
+			command.FlagWithInput("-f ", file.SourceFilePath)
+		}
+
+		builder.Build("create-"+fuzzZip.String(),
+			"Package "+module.Name()+" for "+archString+"-"+hostOrTargetString)
+
+		// Don't add modules to 'make haiku-rust' that are set to not be
+		// exported to the fuzzing infrastructure.
+		if config := fuzzModule.Properties.Fuzz_config; config != nil {
+			if rustModule.Host() && !BoolDefault(config.Fuzz_on_haiku_host, true) {
+				return
+			} else if !BoolDefault(config.Fuzz_on_haiku_device, true) {
+				return
+			}
+		}
+
+		s.fuzzTargets[module.Name()] = true
+		archDirs[archOs] = append(archDirs[archOs], fileToZip{fuzzZip, ""})
+	})
+
+	var archOsList []archOs
+	for archOs := range archDirs {
+		archOsList = append(archOsList, archOs)
+	}
+	sort.Slice(archOsList, func(i, j int) bool { return archOsList[i].dir < archOsList[j].dir })
+
+	for _, archOs := range archOsList {
+		filesToZip := archDirs[archOs]
+		arch := archOs.arch
+		hostOrTarget := archOs.hostOrTarget
+		builder := android.NewRuleBuilder(pctx, ctx)
+		outputFile := android.PathForOutput(ctx, "fuzz-rust-"+hostOrTarget+"-"+arch+".zip")
+		s.packages = append(s.packages, outputFile)
+
+		command := builder.Command().BuiltTool("soong_zip").
+			Flag("-j").
+			FlagWithOutput("-o ", outputFile).
+			Flag("-L 0") // No need to try and re-compress the zipfiles.
+
+		for _, fileToZip := range filesToZip {
+			if fileToZip.DestinationPathPrefix != "" {
+				command.FlagWithArg("-P ", fileToZip.DestinationPathPrefix)
+			} else {
+				command.Flag("-P ''")
+			}
+			command.FlagWithInput("-f ", fileToZip.SourceFilePath)
+		}
+		builder.Build("create-fuzz-package-"+arch+"-"+hostOrTarget,
+			"Create fuzz target packages for "+arch+"-"+hostOrTarget)
+	}
+
+}
+
+func (s *rustFuzzPackager) MakeVars(ctx android.MakeVarsContext) {
+	packages := s.packages.Strings()
+	sort.Strings(packages)
+
+	ctx.Strict("SOONG_RUST_FUZZ_PACKAGING_ARCH_MODULES", strings.Join(packages, " "))
+
+	// Preallocate the slice of fuzz targets to minimise memory allocations.
+	fuzzTargets := make([]string, 0, len(s.fuzzTargets))
+	for target, _ := range s.fuzzTargets {
+		fuzzTargets = append(fuzzTargets, target)
+	}
+	sort.Strings(fuzzTargets)
+	ctx.Strict("ALL_RUST_FUZZ_TARGETS", strings.Join(fuzzTargets, " "))
+}
+
+func (fuzz *fuzzDecorator) install(ctx ModuleContext) {
+	fuzz.binaryDecorator.baseCompiler.dir = filepath.Join(
+		"fuzz", ctx.Target().Arch.ArchType.String(), ctx.ModuleName())
+	fuzz.binaryDecorator.baseCompiler.dir64 = filepath.Join(
+		"fuzz", ctx.Target().Arch.ArchType.String(), ctx.ModuleName())
+	fuzz.binaryDecorator.baseCompiler.install(ctx)
+
+	if fuzz.Properties.Corpus != nil {
+		fuzz.corpus = android.PathsForModuleSrc(ctx, fuzz.Properties.Corpus)
+	}
+	if fuzz.Properties.Data != nil {
+		fuzz.data = android.PathsForModuleSrc(ctx, fuzz.Properties.Data)
+	}
+	if fuzz.Properties.Dictionary != nil {
+		fuzz.dictionary = android.PathForModuleSrc(ctx, *fuzz.Properties.Dictionary)
+	}
+}
diff --git a/scripts/strip.sh b/scripts/strip.sh
index e3e5273..d09c187 100755
--- a/scripts/strip.sh
+++ b/scripts/strip.sh
@@ -71,7 +71,7 @@
     "${CLANG_BIN}/llvm-objcopy" -w "${infile}" "${outfile}.tmp" ${KEEP_SYMBOLS}
 }
 
-do_strip_keep_mini_debug_info() {
+do_strip_keep_mini_debug_info_darwin() {
     rm -f "${outfile}.dynsyms" "${outfile}.funcsyms" "${outfile}.keep_symbols" "${outfile}.debug" "${outfile}.mini_debuginfo" "${outfile}.mini_debuginfo.xz"
     local fail=
     "${CLANG_BIN}/llvm-strip" --strip-all --keep-section=.ARM.attributes --remove-section=.comment "${infile}" -o "${outfile}.tmp" || fail=true
@@ -92,6 +92,32 @@
     fi
 }
 
+do_strip_keep_mini_debug_info_linux() {
+    rm -f "${outfile}.mini_debuginfo.xz"
+    local fail=
+    "${CLANG_BIN}/llvm-strip" --strip-all --keep-section=.ARM.attributes --remove-section=.comment "${infile}" -o "${outfile}.tmp" || fail=true
+
+    if [ -z $fail ]; then
+        "${CREATE_MINIDEBUGINFO}" "${infile}" "${outfile}.mini_debuginfo.xz"
+        "${CLANG_BIN}/llvm-objcopy" --add-section .gnu_debugdata="${outfile}.mini_debuginfo.xz" "${outfile}.tmp"
+        rm -f "${outfile}.mini_debuginfo.xz"
+    else
+        cp -f "${infile}" "${outfile}.tmp"
+    fi
+}
+
+do_strip_keep_mini_debug_info() {
+  case $(uname) in
+      Linux)
+          do_strip_keep_mini_debug_info_linux
+          ;;
+      Darwin)
+          do_strip_keep_mini_debug_info_darwin
+          ;;
+      *) echo "unknown OS:" $(uname) >&2 && exit 1;;
+  esac
+}
+
 do_add_gnu_debuglink() {
     "${CLANG_BIN}/llvm-objcopy" --add-gnu-debuglink="${infile}" "${outfile}.tmp"
 }