diff --git a/tools/README b/tools/README
index 6035c03c2dca09f087af7bd3588ef582da6f9299..5e340a03d87e2606654fd9b51f288cbc70646582 100644
--- a/tools/README
+++ b/tools/README
@@ -3,6 +3,15 @@ which are used in building and validating the policy and others are
 available for help in auditing and analyzing policy.  The tools are
 described further below.
 
+build_policies.sh
+   A tool to build SELinux policy for multiple targets in parallel.
+   This is useful for quickly testing a new test or neverallow rule
+   on multiple targets.
+
+   Usage:
+   ./build_policies.sh ~/android/master ~/tmp/build_policies
+   ./build_policies.sh ~/android/master ~/tmp/build_policies sailfish-eng walleye-eng
+
 checkfc
    A utility for checking the validity of a file_contexts or a
    property_contexts configuration file.  Used as part of the policy
diff --git a/tools/build_policies.sh b/tools/build_policies.sh
new file mode 100644
index 0000000000000000000000000000000000000000..77f0fc6ca3e4f96b6dd64760f1a499cc4549a6f1
--- /dev/null
+++ b/tools/build_policies.sh
@@ -0,0 +1,90 @@
+#!/bin/bash
+
+# Ensure that GNU parallel is installed.
+# We use this to build multiple targets at the same time.
+if [[ -z $(command -v parallel) ]]; then
+  echo "Please install GNU Parallel."
+  exit
+fi
+
+if [[ $# -lt 2 ]]; then
+  echo "Usage: $0 <Android root directory> <output directory> [specific targets to build]"
+  exit
+fi
+
+android_root_dir=$1
+export out_dir=$2
+shift 2
+all_targets="$@"
+
+echo "Android tree: $android_root_dir"
+echo "Output directory: $out_dir"
+
+mkdir -p $out_dir
+
+cd $android_root_dir
+source build/envsetup.sh > /dev/null
+
+# Collect the list of targets by parsing the output of lunch.
+# TODO: This misses some targets.
+if [[ "$all_targets" = "" ]]; then
+  all_targets=`lunch 2>/dev/null <<< _ | grep "[0-9]" | sed 's/^.* //'`
+fi
+
+# Clean up targets by replacing eng with userdebug using non-aosp variants.
+declare -A targets_map
+for target in $all_targets; do
+  targets_map[$target]=$target
+done
+targets=""
+for target in $all_targets; do
+  clean_target=$(echo $target | sed 's/-eng/-userdebug/' | sed 's/aosp_//')
+  if [[ $clean_target != $target ]] && [[ ${targets_map[$clean_target]} == $clean_target ]]; then
+    echo "Ignoring $target in favor of $clean_target"
+  else
+    if [[ -z $targets ]]; then
+      targets=$target
+    else
+      targets="$targets $target"
+    fi
+  fi
+done
+
+# Calculate the number of targets to build at once.
+# This heuristic could probably be improved.
+cores=$(nproc --all)
+num_targets=$(echo "$targets" | sed 's/ /\n/g' | wc -l)
+parallel_jobs=$(expr $cores / 2)
+if [[ $num_targets -lt $parallel_jobs ]]; then
+  export mmma_jobs=$(expr $cores / $num_targets \* 2)
+else
+  export mmma_jobs=4
+fi
+
+echo "$num_targets target(s): $(echo $targets | paste -sd' ')"
+
+compile_target () {
+  target=$1
+  source build/envsetup.sh > /dev/null
+  lunch $target &> /dev/null
+  # Some targets can't lunch properly.
+  if [ $? -ne 0 ]; then
+    echo "$target cannot be lunched"
+    return 1
+  fi
+  my_out_file="$out_dir/log.$target"
+  rm -f $my_out_file
+  # Build the policy.
+  OUT_DIR=$out_dir/out.$target mmma -j$mmma_jobs system/sepolicy &>> $my_out_file
+  if [ $? -ne 0 ]; then
+    echo "$target failed to build"
+    return 2
+  fi
+  return 0
+}
+export -f compile_target
+
+parallel --no-notice -j $parallel_jobs --bar --joblog $out_dir/joblog compile_target ::: $targets
+
+echo "Failed to lunch: $(grep "\s1\s0\scompile_target" $out_dir/joblog | sed 's/^.* //' | sort | paste -sd' ')"
+echo "Failed to build: $(grep "\s2\s0\scompile_target" $out_dir/joblog | sed 's/^.* //' | sort | paste -sd' ')"