Script to automatically update copyright notices in C/C++ source code.

Created to implement http://codereview.appspot.com/4816058/ ('Automatic update of all copyright notices to reflect new license terms.')

We can also use this to periodically clean up our code headers.
Review URL: http://codereview.appspot.com/4800055

git-svn-id: http://skia.googlecode.com/svn/trunk@1983 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
epoger@google.com 2011-07-28 14:29:58 +00:00
parent ec3ed6a5eb
commit 935d94500d
2 changed files with 199 additions and 0 deletions

View File

@ -0,0 +1,92 @@
'''
Copyright 2011 Google Inc.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
'''
import datetime
import re
def CreateParser(filepath):
"""Returns a Parser as appropriate for the file at this filepath.
"""
if (filepath.endswith('.cpp') or
filepath.endswith('.h') or
filepath.endswith('.c')):
return CParser()
else:
return None
class Parser(object):
"""Base class for all language-specific parsers.
"""
def __init__(self):
self._copyright_pattern = re.compile('copyright', re.IGNORECASE)
self._attribute_pattern = re.compile(
'copyright.*\D(\d{4})\W*(\w.*[\w.])', re.IGNORECASE)
def FindCopyrightBlock(self, comment_blocks):
"""Given a list of comment block strings, return the one that seems
like the most likely copyright block.
Returns None if comment_blocks was empty, or if we couldn't find
a comment block that contains copyright info."""
if not comment_blocks:
return None
for block in comment_blocks:
if self._copyright_pattern.search(block):
return block
def GetCopyrightBlockAttributes(self, comment_block):
"""Given a comment block, return a tuple of attributes: (year, holder).
If comment_block is None, or none of the attributes are found,
this will return (None, None)."""
if not comment_block:
return (None, None)
matches = self._attribute_pattern.findall(comment_block)
if not matches:
return (None, None)
first_match = matches[0]
return (first_match[0], first_match[1])
class CParser(Parser):
"""Parser that knows how to parse C/C++ files.
"""
DEFAULT_YEAR = datetime.date.today().year
DEFAULT_HOLDER = 'Google Inc.'
COPYRIGHT_BLOCK_FORMAT = '''
/*
* Copyright %s %s
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
'''
def __init__(self):
super(CParser, self).__init__()
self._comment_pattern = re.compile('/\*.*?\*/', re.DOTALL)
def FindAllCommentBlocks(self, file_contents):
"""Returns a list of all comment blocks within these file contents.
"""
return self._comment_pattern.findall(file_contents)
def CreateCopyrightBlock(self, year, holder):
"""Returns a copyright block suitable for this language, with the
given attributes.
@param year year in which to hold copyright (defaults to DEFAULT_YEAR)
@param holder holder of copyright (defaults to DEFAULT_HOLDER)
"""
if not year:
year = self.DEFAULT_YEAR
if not holder:
holder = self.DEFAULT_HOLDER
return self.COPYRIGHT_BLOCK_FORMAT % (year, holder)

107
tools/copyright/main.py Normal file
View File

@ -0,0 +1,107 @@
'''
Copyright 2011 Google Inc.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
'''
'''
Updates all copyright headers within our code:
- For files that already have a copyright header, the header is modified
while keeping the year and holder intact.
- For files that don't have a copyright header, we add one with the current
year and default holder.
@author: epoger@google.com
'''
import os
import sys
import fileparser
# Only modify copyright stanzas if the copyright holder is one of these.
ALLOWED_COPYRIGHT_HOLDERS = [
'Google Inc.',
'Skia',
'The Android Open Source Project',
]
def Main(root_directory):
"""Run everything.
@param root_directory root directory within which to modify all files
"""
filepaths = GetAllFilepaths(root_directory)
for filepath in filepaths:
parser = fileparser.CreateParser(filepath)
if not parser:
ReportWarning('cannot find a parser for file %s, skipping...' %
filepath)
continue
old_file_contents = ReadFileIntoString(filepath)
comment_blocks = parser.FindAllCommentBlocks(old_file_contents)
if not comment_blocks:
ReportWarning('cannot find any comment blocks in file %s' %
filepath)
old_copyright_block = parser.FindCopyrightBlock(comment_blocks)
if not old_copyright_block:
ReportWarning('cannot find copyright block in file %s' % filepath)
(year, holder) = parser.GetCopyrightBlockAttributes(old_copyright_block)
if holder and not ConfirmAllowedCopyrightHolder(holder):
ReportWarning(
'unrecognized copyright holder "%s" in file %s, skipping...' % (
holder, filepath))
continue
new_copyright_block = parser.CreateCopyrightBlock(year, holder)
if old_copyright_block:
new_file_contents = old_file_contents.replace(
old_copyright_block, new_copyright_block, 1)
else:
new_file_contents = new_copyright_block + old_file_contents
WriteStringToFile(new_file_contents, filepath)
def GetAllFilepaths(root_directory):
"""Return a list of all files (absolute path for each one) within a tree.
@param root_directory root directory within which to find all files
"""
path_list = []
for dirpath, dirnames, filenames in os.walk(root_directory):
for filename in filenames:
path_list.append(os.path.abspath(os.path.join(dirpath, filename)))
return path_list
def ReportWarning(text):
"""Report a warning, but continue.
"""
print 'warning: %s' % text
def ReportError(text):
"""Report an error and raise an exception.
"""
raise IOError(text)
def ReadFileIntoString(filepath):
"""Returns the full contents of this file as a string.
"""
with open(filepath, 'r') as file_handle:
contents = file_handle.read()
return contents
def WriteStringToFile(string, filepath):
"""Writes this string out to filepath, replacing the file if it already
exists.
"""
with open(filepath, 'w') as file_handle:
file_handle.write(string)
def ConfirmAllowedCopyrightHolder(holder):
"""Returns True if this is one of our allowed copyright holders.
@param holder copyright holder as a string
"""
return holder in ALLOWED_COPYRIGHT_HOLDERS
Main(sys.argv[1])