Allow Java libraries to export and propagate proguard_specs.

It may be the case that a library used by Java clients is also used
by Android clients, but when used for the latter, it requires a particular
Proguard configuration. This change modifies Java library rules to accept
Proguard specs and pass them up to Android rules.

Note that this does not cause Proguard to be used on normal Java binaries.

RELNOTES[NEW]: java_library now supports the proguard_specs attribute for
passing Proguard configuration up to Android (not Java) binaries.

--
MOS_MIGRATED_REVID=104661799
diff --git a/tools/jdk/BUILD b/tools/jdk/BUILD
index 3326081..b8f0419 100644
--- a/tools/jdk/BUILD
+++ b/tools/jdk/BUILD
@@ -103,10 +103,34 @@
 
 filegroup(
     name = "srcs",
-    srcs = ["BUILD"],  # Tools are build from the workspace for tests.
+    srcs = [
+        "BUILD",  # Tools are build from the workspace for tests.
+        "proguard_whitelister.py",
+        "proguard_whitelister_test.py",
+        "proguard_whitelister_test_input.cfg",
+    ],
 )
 
 filegroup(
     name = "package-srcs",
     srcs = glob(["**"]),
 )
+
+py_binary(
+    name = "proguard_whitelister",
+    srcs = [
+        "proguard_whitelister.py",
+    ],
+    deps = [
+        "//third_party/py/gflags",
+    ],
+)
+
+py_test(
+    name = "proguard_whitelister_test",
+    srcs = ["proguard_whitelister_test.py"],
+    data = ["proguard_whitelister_test_input.cfg"],
+    deps = [
+        ":proguard_whitelister",
+    ],
+)
diff --git a/tools/jdk/proguard_whitelister.py b/tools/jdk/proguard_whitelister.py
new file mode 100644
index 0000000..6c95360
--- /dev/null
+++ b/tools/jdk/proguard_whitelister.py
@@ -0,0 +1,87 @@
+# Copyright 2015 The Bazel Authors. 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.
+
+"""Checks for proguard configuration rules that cannot be combined across libs.
+
+The only valid proguard arguments for a library are -keep, -assumenosideeffects,
+and -dontnote and -dontwarn when they are provided with arguments.
+Limiting libraries to using these flags prevents drastic, sweeping effects
+(such as obfuscation being disabled) from being inadvertently applied to a
+binary through a library dependency.
+"""
+
+import re
+import sys
+
+from third_party.py import gflags
+
+gflags.DEFINE_string('path', None, 'Path to the proguard config to validate')
+gflags.DEFINE_string('output', None, 'Where to put the validated config')
+
+FLAGS = gflags.FLAGS
+PROGUARD_COMMENTS_PATTERN = '#.*(\n|$)'
+
+
+class ProguardConfigValidator(object):
+  """Validates a proguard config."""
+
+  # Must be a tuple for str.startswith()
+  _VALID_ARGS = ('keep', 'assumenosideeffects')
+
+  def __init__(self, config_path, outconfig_path):
+    self._config_path = config_path
+    self._outconfig_path = outconfig_path
+
+  def ValidateAndWriteOutput(self):
+    with open(self._config_path) as config:
+      config_string = config.read()
+      invalid_configs = self._Validate(config_string)
+      if invalid_configs:
+        raise RuntimeError(
+            'Invalid proguard config parameters: ' + str(invalid_configs))
+    with open(self._outconfig_path, 'w+') as outconfig:
+      config_string = '# Merged from %s \n%s' % (
+          self._config_path, config_string)
+      outconfig.write(config_string)
+
+  def _Validate(self, config):
+    """Checks the config for illegal arguments."""
+    config = re.sub(PROGUARD_COMMENTS_PATTERN, '', config)
+    args = config.split('-')
+    invalid_configs = []
+    for arg in args:
+      arg = arg.strip()
+      if not arg or self._ValidateArg(arg):
+        continue
+      invalid_configs.append('-' + arg.split()[0])
+
+    return invalid_configs
+
+  def _ValidateArg(self, arg):
+    if arg.startswith(ProguardConfigValidator._VALID_ARGS):
+      return True
+    elif arg.split()[0] in ['dontnote', 'dontwarn']:
+      if len(arg.split()) > 1:
+        return True
+    return False
+
+
+def main():
+  validator = ProguardConfigValidator(FLAGS.path, FLAGS.output)
+  validator.ValidateAndWriteOutput()
+
+
+if __name__ == '__main__':
+  FLAGS(sys.argv)
+  main()
diff --git a/tools/jdk/proguard_whitelister_test.py b/tools/jdk/proguard_whitelister_test.py
new file mode 100644
index 0000000..6a24501
--- /dev/null
+++ b/tools/jdk/proguard_whitelister_test.py
@@ -0,0 +1,73 @@
+# Copyright 2015 The Bazel Authors. 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.
+
+import os
+import unittest
+
+from tools.jdk import proguard_whitelister
+
+
+class ProguardConfigValidatorTest(unittest.TestCase):
+
+  def _CreateValidator(self, input_path, output_path):
+    return proguard_whitelister.ProguardConfigValidator(input_path, output_path)
+
+  def testValidConfig(self):
+    input_path = os.path.join(
+        os.path.dirname(__file__), "proguard_whitelister_test_input.cfg")
+    tmpdir = os.environ["TEST_TMPDIR"]
+    output_path = os.path.join(tmpdir, "proguard_whitelister_test_output.cfg")
+    # This will raise an exception if the config is invalid.
+    self._CreateValidator(input_path, output_path).ValidateAndWriteOutput()
+    with file(output_path) as output:
+      self.assertTrue(("# Merged from %s" % input_path) in output.read())
+
+  def _TestInvalidConfig(self, invalid_args, config):
+    tmpdir = os.environ["TEST_TMPDIR"]
+    input_path = os.path.join(tmpdir, "proguard_whitelister_test_input.cfg")
+    with open(input_path, "w") as f:
+      f.write(config)
+    output_path = os.path.join(tmpdir, "proguard_whitelister_test_output.cfg")
+    validator = self._CreateValidator(input_path, output_path)
+    try:
+      validator.ValidateAndWriteOutput()
+      self.fail()
+    except RuntimeError as e:
+      for invalid_arg in invalid_args:
+        self.assertTrue(invalid_arg in str(e))
+
+  def testInvalidNoteConfig(self):
+    self._TestInvalidConfig(["-dontnote"], """\
+# We don"t want libraries disabling notes globally.
+-dontnote""")
+
+  def testInvalidWarnConfig(self):
+    self._TestInvalidConfig(["-dontwarn"], """\
+# We don"t want libraries disabling warnings globally.
+-dontwarn""")
+
+  def testInvalidOptimizationConfig(self):
+    self._TestInvalidConfig(["-optimizations"], """\
+# We don"t want libraries disabling global optimizations.
+-optimizations !class/merging/*,!code/allocation/variable""")
+
+  def testMultipleInvalidArgs(self):
+    self._TestInvalidConfig(["-optimizations", "-dontnote"], """\
+# We don"t want libraries disabling global optimizations.
+-optimizations !class/merging/*,!code/allocation/variable
+-dontnote""")
+
+
+if __name__ == "__main__":
+  unittest.main()
diff --git a/tools/jdk/proguard_whitelister_test_input.cfg b/tools/jdk/proguard_whitelister_test_input.cfg
new file mode 100644
index 0000000..ceb3709
--- /dev/null
+++ b/tools/jdk/proguard_whitelister_test_input.cfg
@@ -0,0 +1,48 @@
+# Copyright 2015 The Bazel Authors. 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.
+
+# Note: put any environment specific flags in their respective proguard files:
+# proguard_test.flags, proguard_dev.flags, proguard_release.flags, etc
+
+# These classes reference resources that don't exist in pre-v11 builds
+-dontwarn com.google.android.apps.testapp.TestActivity
+
+# References to a hidden class
+-dontnote android.os.SystemProperties
+-keep class android.os.SystemProperties { *** get(...);}
+
+# Keep all classes extended from com.google.android.apps.testapp.MyBaseClass
+# because, e.g., we use reflection.
+-keep class * extends com.google.android.apps.testapp.MyBaseClass {
+   @com.google.android.apps.testapp.MyBaseClass$Inner <fields>;
+}
+
+# Needed because this field is accessed reflectively, and it exists in generated code.
+-keepclassmembers class * extends com.google.protobuf.nano.MessageNano {
+    *** apiHeader;
+}
+
+# Extensions are deserialized with a reflective call to newInstance().
+-keepclassmembers class * extends com.google.protobuf.nano.Extension {
+    <init>();
+}
+
+-dontnote android.support.v?.app.Fragment
+
+-keepnames class com.google.android.testapp.** extends com.google.android.testapp.resources.Resource { *; }
+
+-keepclasseswithmembers class derp.foo { bar;}  -keepattributes *
+
+# This is a comment, so this should not cause problems -dontobfuscate
+# This is a comment, so # this should not cause problems -dontnote