pre-upload: Add AOSP license check.

The Apache2 license header used in AOSP can be enabled adding a
PRESUBMIT.cfg file with the following contents:

[Hook Overrides]
cros_license_check: false
aosp_license_check: true

BUG=None
TEST=Tested commits on update_engine repo.

Change-Id: Ida6ed02904517a45c728d3eb96d1d7d440dee7e5
Reviewed-on: https://chromium-review.googlesource.com/295454
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Commit-Queue: Alex Deymo <deymo@chromium.org>
Trybot-Ready: Alex Deymo <deymo@chromium.org>
Tested-by: Alex Deymo <deymo@chromium.org>
diff --git a/pre-upload.py b/pre-upload.py
index 5e34329..5432d40 100755
--- a/pre-upload.py
+++ b/pre-upload.py
@@ -902,8 +902,8 @@
                        MAX_FIRST_LINE_LEN)
 
 
-def _check_license(_project, commit):
-  """Verifies the license/copyright header.
+def _check_cros_license(_project, commit):
+  """Verifies the Chromium OS license/copyright header.
 
   Should be following the spec:
   http://dev.chromium.org/developers/coding-style#TOC-File-headers
@@ -954,6 +954,53 @@
     return HookFailure(msg, bad_copyright_files)
 
 
+def _check_aosp_license(_project, commit):
+  """Verifies the AOSP license/copyright header.
+
+  AOSP uses the Apache2 License:
+  https://source.android.com/source/licenses.html
+  """
+  LICENSE_HEADER = (
+      r"""^[#/\*]*
+[#/\*]* ?Copyright( \([cC]\))? 20[-0-9]{2,7} 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 """
+      r"""implied\.
+[#/\*]* ?See the License for the specific language governing permissions and
+[#/\*]* ?limitations under the License\.
+[#/\*]*$
+"""
+  )
+  license_re = re.compile(LICENSE_HEADER, re.MULTILINE)
+
+  files = _filter_files(_get_affected_files(commit, relative=True),
+                        COMMON_INCLUDED_PATHS,
+                        COMMON_EXCLUDED_PATHS)
+
+  bad_files = []
+  for f in files:
+    contents = _get_file_content(f, commit)
+    if not contents:
+      # Ignore empty files.
+      continue
+
+    if not license_re.search(contents):
+      bad_files.append(f)
+
+  if bad_files:
+    msg = ('License must match:\n%s\nFound a bad header in these files:' %
+           license_re.pattern)
+    return HookFailure(msg, bad_files)
+
+
 def _check_layout_conf(_project, commit):
   """Verifies the metadata/layout.conf file."""
   repo_name = 'profiles/repo_name'
@@ -1192,7 +1239,7 @@
     _check_for_uprev,
     _check_gofmt,
     _check_layout_conf,
-    _check_license,
+    _check_cros_license,
     _check_no_long_lines,
     _check_no_stray_whitespace,
     _check_no_tabs,
@@ -1216,7 +1263,8 @@
     'stray_whitespace_check': _check_no_stray_whitespace,
     'json_check': _run_json_check,
     'long_line_check': _check_no_long_lines,
-    'cros_license_check': _check_license,
+    'cros_license_check': _check_cros_license,
+    'aosp_license_check': _check_aosp_license,
     'tab_check': _check_no_tabs,
     'branch_check': _check_change_has_branch_field,
     'signoff_check': _check_change_has_signoff_field,
diff --git a/pre-upload_unittest.py b/pre-upload_unittest.py
index 57642fd..890a3e5 100755
--- a/pre-upload_unittest.py
+++ b/pre-upload_unittest.py
@@ -448,8 +448,8 @@
     self.assertTrue(isinstance(ret, errors.HookFailure))
 
 
-class CheckLicenseCopyrightHeader(cros_test_lib.MockTestCase):
-  """Tests for _check_license."""
+class CheckCrosLicenseCopyrightHeader(cros_test_lib.MockTestCase):
+  """Tests for _check_cros_license."""
 
   def setUp(self):
     self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
@@ -471,7 +471,7 @@
     self.file_mock.return_value = ['file']
     for header in HEADERS:
       self.content_mock.return_value = header
-      self.assertEqual(None, pre_upload._check_license('proj', 'sha1'))
+      self.assertEqual(None, pre_upload._check_cros_license('proj', 'sha1'))
 
   def testRejectC(self):
     """Reject the (c) in newer headers."""
@@ -488,7 +488,79 @@
     self.file_mock.return_value = ['file']
     for header in HEADERS:
       self.content_mock.return_value = header
-      self.assertNotEqual(None, pre_upload._check_license('proj', 'sha1'))
+      self.assertNotEqual(None, pre_upload._check_cros_license('proj', 'sha1'))
+
+
+class CheckAOSPLicenseCopyrightHeader(cros_test_lib.MockTestCase):
+  """Tests for _check_aosp_license."""
+
+  def setUp(self):
+    self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
+    self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
+
+  def testHeaders(self):
+    """Accept old header styles."""
+    HEADERS = (
+        """//
+// Copyright (C) 2011 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.
+//
+""",
+        """#
+# Copyright (c) 2015 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.
+#
+""",
+    )
+    self.file_mock.return_value = ['file']
+    for header in HEADERS:
+      self.content_mock.return_value = header
+      self.assertEqual(None, pre_upload._check_aosp_license('proj', 'sha1'))
+
+  def testRejectNoLinesAround(self):
+    """Reject headers missing the empty lines before/after the license."""
+    HEADERS = (
+        """# Copyright (c) 2015 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.
+""",
+    )
+    self.file_mock.return_value = ['file']
+    for header in HEADERS:
+      self.content_mock.return_value = header
+      self.assertNotEqual(None, pre_upload._check_aosp_license('proj', 'sha1'))
 
 
 class CheckLayoutConfTestCase(cros_test_lib.MockTestCase):