From 32f820bcaf385f00fdce5d00281073838031db18 Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Wed, 29 May 2013 17:12:52 -0400 Subject: [PATCH] add check that python files have Apache 2.0 license stanza I just had to -1 a proposed commit to Tempest which was missing this, and found that 4 files in our tree didn't have it. We should enforce that all the python files have this header when they are in tree. Skip files < 10 lines, which is needed for empty file support, and to not bail on our Okay doctests. Updated to include a check for an Apache looking LICENSE file as a precondition. Change-Id: Id3f9b6099301f998ce3185e9fd0ed6907e6e71f0 --- hacking/core.py | 82 +++++++++++++++++++++++++++++++++++++++++++++++++ setup.cfg | 4 +-- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/hacking/core.py b/hacking/core.py index 920e99c2..3d7c079e 100755 --- a/hacking/core.py +++ b/hacking/core.py @@ -104,6 +104,88 @@ def hacking_todo_format(physical_line, tokens): return pos, "H101: Use TODO(NAME)" +def _check_for_apache(start, lines): + """Check for the Apache 2.0 license header. + + We strip all the newlines and extra spaces so this license string + should work regardless of indentation in the file. + """ + APACHE2 = """ +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.""" + + # out of all the formatting I've seen, a 12 line version seems to be the + # longest in the source tree. So just take the 12 lines starting with where + # the Apache starting words were found, strip all the '#' and collapse the + # spaces. + content = ''.join(lines[start:(start + 12)]) + content = re.sub('\#', '', content) + content = re.sub('\s+', ' ', content) + stripped_apache2 = re.sub('\s+', ' ', APACHE2) + + if stripped_apache2 in content: + return True + else: + print "License '%s' != '%s'" % (stripped_apache2, content) + return False + + +def _project_is_apache(): + """Determine if a project is Apache. + + Look for a key string in a set of possible license files to figure out + if a project looks to be Apache. This is used as a precondition for + enforcing license headers. + """ + + license_files = ["LICENSE"] + for filename in license_files: + try: + with open(filename, "r") as file: + for line in file: + if re.search('Apache License', line): + return True + except IOError: + pass + return False + + +@flake8ext +def hacking_has_license(physical_line, filename, lines, line_number): + """Check for Apache 2.0 license. + + H102 + """ + # don't work about init files for now + # TODO(sdague): enforce license in init file if it's not empty of content + license_found = False + + # skip files that are < 10 lines, which isn't enough for a license to fit + # this allows us to handle empty files, as well as not fail on the Okay + # doctests. + if _project_is_apache() and not line_number > 1 and len(lines) > 10: + for idx, line in enumerate(lines): + # if it's more than 10 characters in, it's probably not in the + # header + if 0 < line.find('Licensed under the Apache License') < 10: + if _check_for_apache(idx, lines): + license_found = True + else: + return (idx, "H102: Apache 2.0 license header not found") + + if not license_found: + return (0, "H102: Apache 2.0 license header not found") + + @flake8ext def hacking_except_format(logical_line): r"""Check for 'except:'. diff --git a/setup.cfg b/setup.cfg index 9f34b471..19226bb5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,6 +28,7 @@ setup-hooks = flake8.extension = H000 = hacking.core:ProxyChecks H101 = hacking.core:hacking_todo_format + H102 = hacking.core:hacking_has_license H201 = hacking.core:hacking_except_format H202 = hacking.core:hacking_except_format_assert H301 = hacking.core:hacking_import_rules @@ -44,7 +45,6 @@ flake8.extension = H902 = hacking.core:hacking_not_in [egg_info] -tag_build = +tag_build = tag_date = 0 tag_svn_revision = 0 -