Apilint updates

  - Make apilint not print in color when added to file.
  - Make apilint able to filter for classes or packages
  - Add frameworks/base/tools/apilint/apilint script that
    has convenient commandline interface for the main
    common use cases.

Bug: 132198274
Test: apilint_test.py
Test: manual
Change-Id: I78341f42b0fdf4b73a724423b14545b1861a3293
diff --git a/tools/apilint/apilint b/tools/apilint/apilint
new file mode 100755
index 0000000..e42857f
--- /dev/null
+++ b/tools/apilint/apilint
@@ -0,0 +1,147 @@
+#!/bin/bash
+
+# Copyright (C) 2019 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.
+
+if [ "$1" == "--help" -o "$1" == "-h" ]; then
+echo "Usage: apilint [FILTERS...]"
+echo "  Shows lint from currently open files (as diffed from HEAD), i.e. errors"
+echo "  you will receive if you upload this CL."
+echo
+echo "Usage: apilint --all [FILTERS...]"
+echo "  Shows all lint errors present in the current working directory, regardless"
+echo "  of when they were added."
+echo
+echo "Usage: apilint --level API_LEVEL [FILTERS...]"
+echo "  Shows lint as it stands in API_LEVEL"
+echo
+echo "Usage: apilint --shal SHA [FILTERS...]"
+echo "  Shows lint from locally commited git change SHA."
+echo
+echo "Usage: apilint --unreleased [FILTERS...]"
+echo "  Shows all lint errors in the current working directory directory added since"
+echo "  the last released SDK version."
+echo
+echo "FILTERS"
+echo "  List of class or package names by which to filter the results."
+echo
+exit
+fi
+
+if [ \( -z "$ANDROID_BUILD_TOP" \) \
+           -a \( ! -f frameworks/base/api/current.txt \) \
+           -a \( ! -f frameworks/base/api/system-current.txt \) \
+        ]; then
+    echo "apilint must be run either with ANDROID_BUILD_TOP set or from the" 1>&2
+    echo "root of the android source tree" 1>&2
+    exit 1
+fi
+
+if [ ${ANDROID_BUILD_TOP:0:1} != "/" ]; then
+    echo "ANDROID_BUILD_TOP must be an absolute path, not: $ANDROID_BUILD_TOP" 1>&2
+    exit 1
+fi
+
+if [ -z "$ANDROID_BUILD_TOP" ]; then
+    ANDROID_BUILD_TOP=$(pwd)
+fi
+
+FW_BASE=$ANDROID_BUILD_TOP/frameworks/base
+
+MODE=open
+
+OPTIONS=$(getopt -n apilint -o "" -l "all,sha:,unreleased" -- "$@")
+
+[ $? -eq 0 ] || { 
+    exit 1
+}
+
+eval set -- "$OPTIONS"
+while true; do
+    case "$1" in
+    --all)
+        MODE=all
+        ;;
+    --sha)
+        shift; # The arg is next in position args
+        MODE=sha
+        SHA=$1
+        ;;
+    --unreleased)
+        MODE=unreleased
+        ;;
+    --)
+        shift
+        break
+        ;;
+    esac
+    shift
+done
+FILTERS=
+for var in "$@"
+do
+    FILTERS="$FILTERS --filter $var"
+done
+
+if [ $MODE = "all" ]; then
+    python2.7 -B $ANDROID_BUILD_TOP/frameworks/base/tools/apilint/apilint.py \
+            --title "SDK" \
+            $FILTERS \
+            $ANDROID_BUILD_TOP/frameworks/base/api/current.txt
+    python2.7 -B $ANDROID_BUILD_TOP/frameworks/base/tools/apilint/apilint.py \
+            --title "SystemApi" \
+            $FILTERS \
+            --base-current $ANDROID_BUILD_TOP/frameworks/base/api/current.txt \
+            $ANDROID_BUILD_TOP/frameworks/base/api/system-current.txt
+elif [ $MODE = "open" ]; then
+    python2.7 -B $ANDROID_BUILD_TOP/frameworks/base/tools/apilint/apilint.py \
+            --title "SDK" \
+            $FILTERS \
+            $ANDROID_BUILD_TOP/frameworks/base/api/current.txt \
+            <(cd $FW_BASE ; git show HEAD:api/current.txt)
+    python2.7 -B $ANDROID_BUILD_TOP/frameworks/base/tools/apilint/apilint.py \
+            --title "SystemApi" \
+            $FILTERS \
+            --base-current $ANDROID_BUILD_TOP/frameworks/base/api/current.txt \
+            --base-previous <(cd $FW_BASE ; git show HEAD:api/current.txt) \
+            $ANDROID_BUILD_TOP/frameworks/base/api/system-current.txt \
+            <(cd $FW_BASE ; git show HEAD:api/system-current.txt)
+elif [ $MODE = "sha" ]; then
+    python2.7 -B $ANDROID_BUILD_TOP/frameworks/base/tools/apilint/apilint.py \
+            --title "SDK" \
+            $FILTERS \
+            <(cd $FW_BASE ; git show $SHA:api/current.txt) \
+            <(cd $FW_BASE ; git show $SHA^:api/current.txt)
+    python2.7 -B $ANDROID_BUILD_TOP/frameworks/base/tools/apilint/apilint.py \
+            --title "SystemApi" \
+            $FILTERS \
+            --base-current <(cd $FW_BASE ; git show $SHA:api/current.txt) \
+            --base-previous <(cd $FW_BASE ; git show $SHA^:api/current.txt) \
+            <(cd $FW_BASE ; git show $SHA:api/system-current.txt) \
+            <(cd $FW_BASE ; git show $SHA^:api/system-current.txt)
+elif [ $MODE = "unreleased" ]; then
+    LAST_SDK=$(ls $ANDROID_BUILD_TOP/prebuilts/sdk | grep "^[0-9][0-9]*$" | sort -n | tail -n 1)
+    python2.7 -B $ANDROID_BUILD_TOP/frameworks/base/tools/apilint/apilint.py \
+            --title "SDK" \
+            $FILTERS \
+            $ANDROID_BUILD_TOP/frameworks/base/api/current.txt \
+            $ANDROID_BUILD_TOP/prebuilts/sdk/$LAST_SDK/public/api/android.txt
+    python2.7 -B $ANDROID_BUILD_TOP/frameworks/base/tools/apilint/apilint.py \
+            --title "SystemApi" \
+            $FILTERS \
+            --base-current $ANDROID_BUILD_TOP/frameworks/base/api/current.txt \
+            --base-previous $ANDROID_BUILD_TOP/prebuilts/sdk/$LAST_SDK/public/api/android.txt \
+            $ANDROID_BUILD_TOP/frameworks/base/api/system-current.txt \
+            $ANDROID_BUILD_TOP/prebuilts/sdk/$LAST_SDK/system/api/android.txt
+fi
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index f41426d..9e42c04 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -707,6 +707,7 @@
 
 class Failure():
     def __init__(self, sig, clazz, detail, error, rule, msg):
+        self.clazz = clazz
         self.sig = sig
         self.error = error
         self.rule = rule
@@ -2126,6 +2127,15 @@
     return failures
 
 
+def match_filter(filters, fullname):
+    for f in filters:
+        if fullname == f:
+            return True
+        if fullname.startswith(f + '.'):
+            return True
+    return False
+
+
 def show_deprecations_at_birth(cur, prev):
     """Show API deprecations at birth."""
     global failures
@@ -2199,12 +2209,9 @@
     print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
 
 
-if __name__ == "__main__":
+def main():
     parser = argparse.ArgumentParser(description="Enforces common Android public API design \
             patterns. It ignores lint messages from a previous API level, if provided.")
-    parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
-    parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
-            help="previous.txt")
     parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
             help="The base current.txt to use when examining system-current.txt or"
                  " test-current.txt")
@@ -2213,6 +2220,8 @@
                  " test-previous.txt")
     parser.add_argument("--no-color", action='store_const', const=True,
             help="Disable terminal colors")
+    parser.add_argument("--color", action='store_const', const=True,
+            help="Use terminal colors")
     parser.add_argument("--allow-google", action='store_const', const=True,
             help="Allow references to Google")
     parser.add_argument("--show-noticed", action='store_const', const=True,
@@ -2221,10 +2230,21 @@
             help="Show API deprecations at birth")
     parser.add_argument("--show-stats", action='store_const', const=True,
             help="Show API stats")
+    parser.add_argument("--title", action='store', default=None,
+            help="Title to put in for display purposes")
+    parser.add_argument("--filter", action="append",
+            help="If provided, only show lint for the given packages or classes.")
+    parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
+    parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
+            help="previous.txt")
     args = vars(parser.parse_args())
 
     if args['no_color']:
         USE_COLOR = False
+    elif args['color']:
+        USE_COLOR = True
+    else:
+        USE_COLOR = sys.stdout.isatty()
 
     if args['allow_google']:
         ALLOW_GOOGLE = True
@@ -2233,6 +2253,12 @@
     base_current_file = args['base_current']
     previous_file = args['previous.txt']
     base_previous_file = args['base_previous']
+    filters = args['filter']
+    if not filters:
+        filters = []
+    title = args['title']
+    if not title:
+        title = current_file.name
 
     if args['show_deprecations_at_birth']:
         with current_file as f:
@@ -2290,6 +2316,11 @@
             print
         """
 
+    # ignore everything but the given filters, if provided
+    if filters:
+        cur_fail = dict([(key, failure) for key, failure in cur_fail.iteritems()
+                if match_filter(filters, failure.clazz.fullname)])
+
     if args['show_noticed'] and len(cur_noticed) != 0:
         print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
         for f in sorted(cur_noticed.keys()):
@@ -2297,8 +2328,20 @@
         print
 
     if len(cur_fail) != 0:
-        print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
+        print "%s API style issues: %s %s" % ((format(fg=WHITE, bg=BLUE, bold=True),
+                    title, format(reset=True)))
+        for f in filters:
+            print "%s   filter: %s %s" % ((format(fg=WHITE, bg=BLUE, bold=True),
+                        f, format(reset=True)))
+        print
         for f in sorted(cur_fail):
             print cur_fail[f]
             print
+        print "%d errors" % len(cur_fail)
         sys.exit(77)
+
+if __name__ == "__main__":
+    try:
+        main()
+    except KeyboardInterrupt:
+        sys.exit(1)
diff --git a/tools/apilint/apilint_test.py b/tools/apilint/apilint_test.py
index 5cb43db..811cb9a 100644
--- a/tools/apilint/apilint_test.py
+++ b/tools/apilint/apilint_test.py
@@ -392,5 +392,23 @@
         p = self._package("package @Rt(a.b.L_G_P) @RestrictTo(a.b.C) an.pref.int {")
         self.assertEquals('an.pref.int', p.name)
 
+class FilterTests(unittest.TestCase):
+    def test_filter_match_prefix(self):
+        self.assertTrue(apilint.match_filter(["a"], "a.B"))
+        self.assertTrue(apilint.match_filter(["a.B"], "a.B.C"))
+
+    def test_filter_dont_match_prefix(self):
+        self.assertFalse(apilint.match_filter(["c"], "a.B"))
+        self.assertFalse(apilint.match_filter(["a."], "a.B"))
+        self.assertFalse(apilint.match_filter(["a.B."], "a.B.C"))
+
+    def test_filter_match_exact(self):
+        self.assertTrue(apilint.match_filter(["a.B"], "a.B"))
+
+    def test_filter_dont_match_exact(self):
+        self.assertFalse(apilint.match_filter([""], "a.B"))
+        self.assertFalse(apilint.match_filter(["a.C"], "a.B"))
+        self.assertFalse(apilint.match_filter(["a.C"], "a.B"))
+        
 if __name__ == "__main__":
     unittest.main()