Home | History | Annotate | Line # | Download | only in contrib
update-copyright.py revision 1.1.1.3
      1      1.1  mrg #!/usr/bin/python
      2      1.1  mrg #
      3  1.1.1.3  mrg # Copyright (C) 2013-2016 Free Software Foundation, Inc.
      4      1.1  mrg #
      5      1.1  mrg # This script is free software; you can redistribute it and/or modify
      6      1.1  mrg # it under the terms of the GNU General Public License as published by
      7      1.1  mrg # the Free Software Foundation; either version 3, or (at your option)
      8      1.1  mrg # any later version.
      9      1.1  mrg 
     10      1.1  mrg # This script adjusts the copyright notices at the top of source files
     11      1.1  mrg # so that they have the form:
     12      1.1  mrg #
     13      1.1  mrg #   Copyright XXXX-YYYY Free Software Foundation, Inc.
     14      1.1  mrg #
     15      1.1  mrg # It doesn't change code that is known to be maintained elsewhere or
     16      1.1  mrg # that carries a non-FSF copyright.
     17      1.1  mrg #
     18      1.1  mrg # The script also doesn't change testsuite files, except those in
     19      1.1  mrg # libstdc++-v3.  This is because libstdc++-v3 has a conformance testsuite,
     20      1.1  mrg # while most tests in other directories are just things that failed at some
     21      1.1  mrg # point in the past.
     22      1.1  mrg #
     23      1.1  mrg # Pass --this-year to the script if you want it to add the current year
     24      1.1  mrg # to all applicable notices.  Pass --quilt if you are using quilt and
     25      1.1  mrg # want files to be added to the quilt before being changed.
     26      1.1  mrg #
     27      1.1  mrg # By default the script will update all directories for which the
     28      1.1  mrg # output has been vetted.  You can instead pass the names of individual
     29      1.1  mrg # directories, including those that haven't been approved.  So:
     30      1.1  mrg #
     31      1.1  mrg #    update-copyright.pl --this-year
     32      1.1  mrg #
     33      1.1  mrg # is the command that would be used at the beginning of a year to update
     34      1.1  mrg # all copyright notices (and possibly at other times to check whether
     35      1.1  mrg # new files have been added with old years).  On the other hand:
     36      1.1  mrg #
     37      1.1  mrg #    update-copyright.pl --this-year libjava
     38      1.1  mrg #
     39      1.1  mrg # would run the script on just libjava/.
     40      1.1  mrg #
     41      1.1  mrg # Note that things like --version output strings must be updated before
     42      1.1  mrg # this script is run.  There's already a separate procedure for that.
     43      1.1  mrg 
     44      1.1  mrg import os
     45      1.1  mrg import re
     46      1.1  mrg import sys
     47      1.1  mrg import time
     48      1.1  mrg import subprocess
     49      1.1  mrg 
     50      1.1  mrg class Errors:
     51      1.1  mrg     def __init__ (self):
     52      1.1  mrg         self.num_errors = 0
     53      1.1  mrg 
     54      1.1  mrg     def report (self, filename, string):
     55      1.1  mrg         if filename:
     56      1.1  mrg             string = filename + ': ' + string
     57      1.1  mrg         sys.stderr.write (string + '\n')
     58      1.1  mrg         self.num_errors += 1
     59      1.1  mrg 
     60      1.1  mrg     def ok (self):
     61      1.1  mrg         return self.num_errors == 0
     62      1.1  mrg 
     63      1.1  mrg class GenericFilter:
     64      1.1  mrg     def __init__ (self):
     65      1.1  mrg         self.skip_files = set()
     66      1.1  mrg         self.skip_dirs = set()
     67      1.1  mrg         self.skip_extensions = set()
     68      1.1  mrg         self.fossilised_files = set()
     69      1.1  mrg         self.own_files = set()
     70      1.1  mrg 
     71      1.1  mrg         self.skip_files |= set ([
     72      1.1  mrg                 # Skip licence files.
     73      1.1  mrg                 'COPYING',
     74      1.1  mrg                 'COPYING.LIB',
     75      1.1  mrg                 'COPYING3',
     76      1.1  mrg                 'COPYING3.LIB',
     77      1.1  mrg                 'LICENSE',
     78      1.1  mrg                 'fdl.texi',
     79      1.1  mrg                 'gpl_v3.texi',
     80      1.1  mrg                 'fdl-1.3.xml',
     81      1.1  mrg                 'gpl-3.0.xml',
     82      1.1  mrg 
     83      1.1  mrg                 # Skip auto- and libtool-related files
     84      1.1  mrg                 'aclocal.m4',
     85      1.1  mrg                 'compile',
     86      1.1  mrg                 'config.guess',
     87      1.1  mrg                 'config.sub',
     88      1.1  mrg                 'depcomp',
     89      1.1  mrg                 'install-sh',
     90      1.1  mrg                 'libtool.m4',
     91      1.1  mrg                 'ltmain.sh',
     92      1.1  mrg                 'ltoptions.m4',
     93      1.1  mrg                 'ltsugar.m4',
     94      1.1  mrg                 'ltversion.m4',
     95      1.1  mrg                 'lt~obsolete.m4',
     96      1.1  mrg                 'missing',
     97      1.1  mrg                 'mkdep',
     98      1.1  mrg                 'mkinstalldirs',
     99      1.1  mrg                 'move-if-change',
    100      1.1  mrg                 'shlibpath.m4',
    101      1.1  mrg                 'symlink-tree',
    102      1.1  mrg                 'ylwrap',
    103      1.1  mrg 
    104      1.1  mrg                 # Skip FSF mission statement, etc.
    105      1.1  mrg                 'gnu.texi',
    106      1.1  mrg                 'funding.texi',
    107      1.1  mrg                 'appendix_free.xml',
    108      1.1  mrg 
    109      1.1  mrg                 # Skip imported texinfo files.
    110      1.1  mrg                 'texinfo.tex',
    111      1.1  mrg                 ])
    112      1.1  mrg 
    113      1.1  mrg 
    114      1.1  mrg     def get_line_filter (self, dir, filename):
    115      1.1  mrg         if filename.startswith ('ChangeLog'):
    116      1.1  mrg             # Ignore references to copyright in changelog entries.
    117      1.1  mrg             return re.compile ('\t')
    118      1.1  mrg 
    119      1.1  mrg         return None
    120      1.1  mrg 
    121      1.1  mrg     def skip_file (self, dir, filename):
    122      1.1  mrg         if filename in self.skip_files:
    123      1.1  mrg             return True
    124      1.1  mrg 
    125      1.1  mrg         (base, extension) = os.path.splitext (os.path.join (dir, filename))
    126      1.1  mrg         if extension in self.skip_extensions:
    127      1.1  mrg             return True
    128      1.1  mrg 
    129      1.1  mrg         if extension == '.in':
    130      1.1  mrg             # Skip .in files produced by automake.
    131      1.1  mrg             if os.path.exists (base + '.am'):
    132      1.1  mrg                 return True
    133      1.1  mrg 
    134      1.1  mrg             # Skip files produced by autogen
    135      1.1  mrg             if (os.path.exists (base + '.def')
    136      1.1  mrg                 and os.path.exists (base + '.tpl')):
    137      1.1  mrg                 return True
    138      1.1  mrg 
    139      1.1  mrg         # Skip configure files produced by autoconf
    140      1.1  mrg         if filename == 'configure':
    141      1.1  mrg             if os.path.exists (base + '.ac'):
    142      1.1  mrg                 return True
    143      1.1  mrg             if os.path.exists (base + '.in'):
    144      1.1  mrg                 return True
    145      1.1  mrg 
    146      1.1  mrg         return False
    147      1.1  mrg 
    148      1.1  mrg     def skip_dir (self, dir, subdir):
    149      1.1  mrg         return subdir in self.skip_dirs
    150      1.1  mrg 
    151      1.1  mrg     def is_fossilised_file (self, dir, filename):
    152      1.1  mrg         if filename in self.fossilised_files:
    153      1.1  mrg             return True
    154      1.1  mrg         # Only touch current current ChangeLogs.
    155      1.1  mrg         if filename != 'ChangeLog' and filename.find ('ChangeLog') >= 0:
    156      1.1  mrg             return True
    157      1.1  mrg         return False
    158      1.1  mrg 
    159      1.1  mrg     def by_package_author (self, dir, filename):
    160      1.1  mrg         return filename in self.own_files
    161      1.1  mrg 
    162      1.1  mrg class Copyright:
    163      1.1  mrg     def __init__ (self, errors):
    164      1.1  mrg         self.errors = errors
    165      1.1  mrg 
    166      1.1  mrg         # Characters in a range of years.  Include '.' for typos.
    167      1.1  mrg         ranges = '[0-9](?:[-0-9.,\s]|\s+and\s+)*[0-9]'
    168      1.1  mrg 
    169      1.1  mrg         # Non-whitespace characters in a copyright holder's name.
    170      1.1  mrg         name = '[\w.,-]'
    171      1.1  mrg 
    172      1.1  mrg         # Matches one year.
    173      1.1  mrg         self.year_re = re.compile ('[0-9]+')
    174      1.1  mrg 
    175      1.1  mrg         # Matches part of a year or copyright holder.
    176      1.1  mrg         self.continuation_re = re.compile (ranges + '|' + name)
    177      1.1  mrg 
    178      1.1  mrg         # Matches a full copyright notice:
    179      1.1  mrg         self.copyright_re = re.compile (
    180      1.1  mrg             # 1: 'Copyright (C)', etc.
    181      1.1  mrg             '([Cc]opyright'
    182      1.1  mrg             '|[Cc]opyright\s+\([Cc]\)'
    183      1.1  mrg             '|[Cc]opyright\s+%s'
    184      1.1  mrg             '|[Cc]opyright\s+©'
    185      1.1  mrg             '|[Cc]opyright\s+@copyright{}'
    186  1.1.1.2  mrg             '|copyright = u\''
    187      1.1  mrg             '|@set\s+copyright[\w-]+)'
    188      1.1  mrg 
    189      1.1  mrg             # 2: the years.  Include the whitespace in the year, so that
    190      1.1  mrg             # we can remove any excess.
    191      1.1  mrg             '(\s*(?:' + ranges + ',?'
    192      1.1  mrg             '|@value\{[^{}]*\})\s*)'
    193      1.1  mrg 
    194      1.1  mrg             # 3: 'by ', if used
    195      1.1  mrg             '(by\s+)?'
    196      1.1  mrg 
    197      1.1  mrg             # 4: the copyright holder.  Don't allow multiple consecutive
    198      1.1  mrg             # spaces, so that right-margin gloss doesn't get caught
    199      1.1  mrg             # (e.g. gnat_ugn.texi).
    200      1.1  mrg             '(' + name + '(?:\s?' + name + ')*)?')
    201      1.1  mrg 
    202      1.1  mrg         # A regexp for notices that might have slipped by.  Just matching
    203      1.1  mrg         # 'copyright' is too noisy, and 'copyright.*[0-9]' falls foul of
    204      1.1  mrg         # HTML header markers, so check for 'copyright' and two digits.
    205      1.1  mrg         self.other_copyright_re = re.compile ('copyright.*[0-9][0-9]',
    206      1.1  mrg                                               re.IGNORECASE)
    207      1.1  mrg         self.comment_re = re.compile('#+|[*]+|;+|%+|//+|@c |dnl ')
    208      1.1  mrg         self.holders = { '@copying': '@copying' }
    209      1.1  mrg         self.holder_prefixes = set()
    210      1.1  mrg 
    211      1.1  mrg         # True to 'quilt add' files before changing them.
    212      1.1  mrg         self.use_quilt = False
    213      1.1  mrg 
    214      1.1  mrg         # If set, force all notices to include this year.
    215      1.1  mrg         self.max_year = None
    216      1.1  mrg 
    217      1.1  mrg         # Goes after the year(s).  Could be ', '.
    218      1.1  mrg         self.separator = ' '
    219      1.1  mrg 
    220      1.1  mrg     def add_package_author (self, holder, canon_form = None):
    221      1.1  mrg         if not canon_form:
    222      1.1  mrg             canon_form = holder
    223      1.1  mrg         self.holders[holder] = canon_form
    224      1.1  mrg         index = holder.find (' ')
    225      1.1  mrg         while index >= 0:
    226      1.1  mrg             self.holder_prefixes.add (holder[:index])
    227      1.1  mrg             index = holder.find (' ', index + 1)
    228      1.1  mrg 
    229      1.1  mrg     def add_external_author (self, holder):
    230      1.1  mrg         self.holders[holder] = None
    231      1.1  mrg 
    232      1.1  mrg     class BadYear():
    233      1.1  mrg         def __init__ (self, year):
    234      1.1  mrg             self.year = year
    235      1.1  mrg 
    236      1.1  mrg         def __str__ (self):
    237      1.1  mrg             return 'unrecognised year: ' + self.year
    238      1.1  mrg 
    239      1.1  mrg     def parse_year (self, string):
    240      1.1  mrg         year = int (string)
    241      1.1  mrg         if len (string) == 2:
    242      1.1  mrg             if year > 70:
    243      1.1  mrg                 return year + 1900
    244      1.1  mrg         elif len (string) == 4:
    245      1.1  mrg             return year
    246      1.1  mrg         raise self.BadYear (string)
    247      1.1  mrg 
    248      1.1  mrg     def year_range (self, years):
    249      1.1  mrg         year_list = [self.parse_year (year)
    250      1.1  mrg                      for year in self.year_re.findall (years)]
    251      1.1  mrg         assert len (year_list) > 0
    252      1.1  mrg         return (min (year_list), max (year_list))
    253      1.1  mrg 
    254      1.1  mrg     def set_use_quilt (self, use_quilt):
    255      1.1  mrg         self.use_quilt = use_quilt
    256      1.1  mrg 
    257      1.1  mrg     def include_year (self, year):
    258      1.1  mrg         assert not self.max_year
    259      1.1  mrg         self.max_year = year
    260      1.1  mrg 
    261      1.1  mrg     def canonicalise_years (self, dir, filename, filter, years):
    262      1.1  mrg         # Leave texinfo variables alone.
    263      1.1  mrg         if years.startswith ('@value'):
    264      1.1  mrg             return years
    265      1.1  mrg 
    266      1.1  mrg         (min_year, max_year) = self.year_range (years)
    267      1.1  mrg 
    268      1.1  mrg         # Update the upper bound, if enabled.
    269      1.1  mrg         if self.max_year and not filter.is_fossilised_file (dir, filename):
    270      1.1  mrg             max_year = max (max_year, self.max_year)
    271      1.1  mrg 
    272      1.1  mrg         # Use a range.
    273      1.1  mrg         if min_year == max_year:
    274      1.1  mrg             return '%d' % min_year
    275      1.1  mrg         else:
    276      1.1  mrg             return '%d-%d' % (min_year, max_year)
    277      1.1  mrg 
    278      1.1  mrg     def strip_continuation (self, line):
    279      1.1  mrg         line = line.lstrip()
    280      1.1  mrg         match = self.comment_re.match (line)
    281      1.1  mrg         if match:
    282      1.1  mrg             line = line[match.end():].lstrip()
    283      1.1  mrg         return line
    284      1.1  mrg 
    285      1.1  mrg     def is_complete (self, match):
    286      1.1  mrg         holder = match.group (4)
    287      1.1  mrg         return (holder
    288      1.1  mrg                 and (holder not in self.holder_prefixes
    289      1.1  mrg                      or holder in self.holders))
    290      1.1  mrg 
    291      1.1  mrg     def update_copyright (self, dir, filename, filter, file, line, match):
    292      1.1  mrg         orig_line = line
    293      1.1  mrg         next_line = None
    294      1.1  mrg         pathname = os.path.join (dir, filename)
    295      1.1  mrg 
    296      1.1  mrg         intro = match.group (1)
    297      1.1  mrg         if intro.startswith ('@set'):
    298      1.1  mrg             # Texinfo year variables should always be on one line
    299      1.1  mrg             after_years = line[match.end (2):].strip()
    300      1.1  mrg             if after_years != '':
    301      1.1  mrg                 self.errors.report (pathname,
    302      1.1  mrg                                     'trailing characters in @set: '
    303      1.1  mrg                                     + after_years)
    304      1.1  mrg                 return (False, orig_line, next_line)
    305      1.1  mrg         else:
    306      1.1  mrg             # If it looks like the copyright is incomplete, add the next line.
    307      1.1  mrg             while not self.is_complete (match):
    308      1.1  mrg                 try:
    309      1.1  mrg                     next_line = file.next()
    310      1.1  mrg                 except StopIteration:
    311      1.1  mrg                     break
    312      1.1  mrg 
    313      1.1  mrg                 # If the next line doesn't look like a proper continuation,
    314      1.1  mrg                 # assume that what we've got is complete.
    315      1.1  mrg                 continuation = self.strip_continuation (next_line)
    316      1.1  mrg                 if not self.continuation_re.match (continuation):
    317      1.1  mrg                     break
    318      1.1  mrg 
    319      1.1  mrg                 # Merge the lines for matching purposes.
    320      1.1  mrg                 orig_line += next_line
    321      1.1  mrg                 line = line.rstrip() + ' ' + continuation
    322      1.1  mrg                 next_line = None
    323      1.1  mrg 
    324      1.1  mrg                 # Rematch with the longer line, at the original position.
    325      1.1  mrg                 match = self.copyright_re.match (line, match.start())
    326      1.1  mrg                 assert match
    327      1.1  mrg 
    328      1.1  mrg             holder = match.group (4)
    329      1.1  mrg 
    330      1.1  mrg             # Use the filter to test cases where markup is getting in the way.
    331      1.1  mrg             if filter.by_package_author (dir, filename):
    332      1.1  mrg                 assert holder not in self.holders
    333      1.1  mrg 
    334      1.1  mrg             elif not holder:
    335      1.1  mrg                 self.errors.report (pathname, 'missing copyright holder')
    336      1.1  mrg                 return (False, orig_line, next_line)
    337      1.1  mrg 
    338      1.1  mrg             elif holder not in self.holders:
    339      1.1  mrg                 self.errors.report (pathname,
    340      1.1  mrg                                     'unrecognised copyright holder: ' + holder)
    341      1.1  mrg                 return (False, orig_line, next_line)
    342      1.1  mrg 
    343      1.1  mrg             else:
    344      1.1  mrg                 # See whether the copyright is associated with the package
    345      1.1  mrg                 # author.
    346      1.1  mrg                 canon_form = self.holders[holder]
    347      1.1  mrg                 if not canon_form:
    348      1.1  mrg                     return (False, orig_line, next_line)
    349      1.1  mrg 
    350      1.1  mrg                 # Make sure the author is given in a consistent way.
    351      1.1  mrg                 line = (line[:match.start (4)]
    352      1.1  mrg                         + canon_form
    353      1.1  mrg                         + line[match.end (4):])
    354      1.1  mrg 
    355      1.1  mrg                 # Remove any 'by'
    356      1.1  mrg                 line = line[:match.start (3)] + line[match.end (3):]
    357      1.1  mrg 
    358      1.1  mrg         # Update the copyright years.
    359      1.1  mrg         years = match.group (2).strip()
    360      1.1  mrg         try:
    361      1.1  mrg             canon_form = self.canonicalise_years (dir, filename, filter, years)
    362      1.1  mrg         except self.BadYear as e:
    363      1.1  mrg             self.errors.report (pathname, str (e))
    364      1.1  mrg             return (False, orig_line, next_line)
    365      1.1  mrg 
    366      1.1  mrg         line = (line[:match.start (2)]
    367  1.1.1.2  mrg                 + ('' if intro.startswith ('copyright = ') else ' ')
    368  1.1.1.2  mrg                 + canon_form + self.separator
    369      1.1  mrg                 + line[match.end (2):])
    370      1.1  mrg 
    371      1.1  mrg         # Use the standard (C) form.
    372      1.1  mrg         if intro.endswith ('right'):
    373      1.1  mrg             intro += ' (C)'
    374      1.1  mrg         elif intro.endswith ('(c)'):
    375      1.1  mrg             intro = intro[:-3] + '(C)'
    376      1.1  mrg         line = line[:match.start (1)] + intro + line[match.end (1):]
    377      1.1  mrg 
    378      1.1  mrg         # Strip trailing whitespace
    379      1.1  mrg         line = line.rstrip() + '\n'
    380      1.1  mrg 
    381      1.1  mrg         return (line != orig_line, line, next_line)
    382      1.1  mrg 
    383      1.1  mrg     def process_file (self, dir, filename, filter):
    384      1.1  mrg         pathname = os.path.join (dir, filename)
    385      1.1  mrg         if filename.endswith ('.tmp'):
    386      1.1  mrg             # Looks like something we tried to create before.
    387      1.1  mrg             try:
    388      1.1  mrg                 os.remove (pathname)
    389      1.1  mrg             except OSError:
    390      1.1  mrg                 pass
    391      1.1  mrg             return
    392      1.1  mrg 
    393      1.1  mrg         lines = []
    394      1.1  mrg         changed = False
    395      1.1  mrg         line_filter = filter.get_line_filter (dir, filename)
    396      1.1  mrg         with open (pathname, 'r') as file:
    397      1.1  mrg             prev = None
    398      1.1  mrg             for line in file:
    399      1.1  mrg                 while line:
    400      1.1  mrg                     next_line = None
    401      1.1  mrg                     # Leave filtered-out lines alone.
    402      1.1  mrg                     if not (line_filter and line_filter.match (line)):
    403      1.1  mrg                         match = self.copyright_re.search (line)
    404      1.1  mrg                         if match:
    405      1.1  mrg                             res = self.update_copyright (dir, filename, filter,
    406      1.1  mrg                                                          file, line, match)
    407      1.1  mrg                             (this_changed, line, next_line) = res
    408      1.1  mrg                             changed = changed or this_changed
    409      1.1  mrg 
    410      1.1  mrg                         # Check for copyright lines that might have slipped by.
    411      1.1  mrg                         elif self.other_copyright_re.search (line):
    412      1.1  mrg                             self.errors.report (pathname,
    413      1.1  mrg                                                 'unrecognised copyright: %s'
    414      1.1  mrg                                                 % line.strip())
    415      1.1  mrg                     lines.append (line)
    416      1.1  mrg                     line = next_line
    417      1.1  mrg 
    418      1.1  mrg         # If something changed, write the new file out.
    419      1.1  mrg         if changed and self.errors.ok():
    420      1.1  mrg             tmp_pathname = pathname + '.tmp'
    421      1.1  mrg             with open (tmp_pathname, 'w') as file:
    422      1.1  mrg                 for line in lines:
    423      1.1  mrg                     file.write (line)
    424      1.1  mrg             if self.use_quilt:
    425      1.1  mrg                 subprocess.call (['quilt', 'add', pathname])
    426      1.1  mrg             os.rename (tmp_pathname, pathname)
    427      1.1  mrg 
    428      1.1  mrg     def process_tree (self, tree, filter):
    429      1.1  mrg         for (dir, subdirs, filenames) in os.walk (tree):
    430      1.1  mrg             # Don't recurse through directories that should be skipped.
    431      1.1  mrg             for i in xrange (len (subdirs) - 1, -1, -1):
    432      1.1  mrg                 if filter.skip_dir (dir, subdirs[i]):
    433      1.1  mrg                     del subdirs[i]
    434      1.1  mrg 
    435      1.1  mrg             # Handle the files in this directory.
    436      1.1  mrg             for filename in filenames:
    437      1.1  mrg                 if filter.skip_file (dir, filename):
    438      1.1  mrg                     sys.stdout.write ('Skipping %s\n'
    439      1.1  mrg                                       % os.path.join (dir, filename))
    440      1.1  mrg                 else:
    441      1.1  mrg                     self.process_file (dir, filename, filter)
    442      1.1  mrg 
    443      1.1  mrg class CmdLine:
    444      1.1  mrg     def __init__ (self, copyright = Copyright):
    445      1.1  mrg         self.errors = Errors()
    446      1.1  mrg         self.copyright = copyright (self.errors)
    447      1.1  mrg         self.dirs = []
    448      1.1  mrg         self.default_dirs = []
    449      1.1  mrg         self.chosen_dirs = []
    450      1.1  mrg         self.option_handlers = dict()
    451      1.1  mrg         self.option_help = []
    452      1.1  mrg 
    453      1.1  mrg         self.add_option ('--help', 'Print this help', self.o_help)
    454      1.1  mrg         self.add_option ('--quilt', '"quilt add" files before changing them',
    455      1.1  mrg                          self.o_quilt)
    456      1.1  mrg         self.add_option ('--this-year', 'Add the current year to every notice',
    457      1.1  mrg                          self.o_this_year)
    458      1.1  mrg 
    459      1.1  mrg     def add_option (self, name, help, handler):
    460      1.1  mrg         self.option_help.append ((name, help))
    461      1.1  mrg         self.option_handlers[name] = handler
    462      1.1  mrg 
    463      1.1  mrg     def add_dir (self, dir, filter = GenericFilter()):
    464      1.1  mrg         self.dirs.append ((dir, filter))
    465      1.1  mrg 
    466      1.1  mrg     def o_help (self, option = None):
    467      1.1  mrg         sys.stdout.write ('Usage: %s [options] dir1 dir2...\n\n'
    468      1.1  mrg                           'Options:\n' % sys.argv[0])
    469      1.1  mrg         format = '%-15s %s\n'
    470      1.1  mrg         for (what, help) in self.option_help:
    471      1.1  mrg             sys.stdout.write (format % (what, help))
    472      1.1  mrg         sys.stdout.write ('\nDirectories:\n')
    473      1.1  mrg 
    474      1.1  mrg         format = '%-25s'
    475      1.1  mrg         i = 0
    476      1.1  mrg         for (dir, filter) in self.dirs:
    477      1.1  mrg             i += 1
    478      1.1  mrg             if i % 3 == 0 or i == len (self.dirs):
    479      1.1  mrg                 sys.stdout.write (dir + '\n')
    480      1.1  mrg             else:
    481      1.1  mrg                 sys.stdout.write (format % dir)
    482      1.1  mrg         sys.exit (0)
    483      1.1  mrg 
    484      1.1  mrg     def o_quilt (self, option):
    485      1.1  mrg         self.copyright.set_use_quilt (True)
    486      1.1  mrg 
    487      1.1  mrg     def o_this_year (self, option):
    488      1.1  mrg         self.copyright.include_year (time.localtime().tm_year)
    489      1.1  mrg 
    490      1.1  mrg     def main (self):
    491      1.1  mrg         for arg in sys.argv[1:]:
    492      1.1  mrg             if arg[:1] != '-':
    493      1.1  mrg                 self.chosen_dirs.append (arg)
    494      1.1  mrg             elif arg in self.option_handlers:
    495      1.1  mrg                 self.option_handlers[arg] (arg)
    496      1.1  mrg             else:
    497      1.1  mrg                 self.errors.report (None, 'unrecognised option: ' + arg)
    498      1.1  mrg         if self.errors.ok():
    499      1.1  mrg             if len (self.chosen_dirs) == 0:
    500      1.1  mrg                 self.chosen_dirs = self.default_dirs
    501      1.1  mrg             if len (self.chosen_dirs) == 0:
    502      1.1  mrg                 self.o_help()
    503      1.1  mrg             else:
    504      1.1  mrg                 for chosen_dir in self.chosen_dirs:
    505      1.1  mrg                     canon_dir = os.path.join (chosen_dir, '')
    506      1.1  mrg                     count = 0
    507      1.1  mrg                     for (dir, filter) in self.dirs:
    508      1.1  mrg                         if (dir + os.sep).startswith (canon_dir):
    509      1.1  mrg                             count += 1
    510      1.1  mrg                             self.copyright.process_tree (dir, filter)
    511      1.1  mrg                     if count == 0:
    512      1.1  mrg                         self.errors.report (None, 'unrecognised directory: '
    513      1.1  mrg                                             + chosen_dir)
    514      1.1  mrg         sys.exit (0 if self.errors.ok() else 1)
    515      1.1  mrg 
    516      1.1  mrg #----------------------------------------------------------------------------
    517      1.1  mrg 
    518      1.1  mrg class TopLevelFilter (GenericFilter):
    519      1.1  mrg     def skip_dir (self, dir, subdir):
    520      1.1  mrg         return True
    521      1.1  mrg 
    522      1.1  mrg class ConfigFilter (GenericFilter):
    523      1.1  mrg     def __init__ (self):
    524      1.1  mrg         GenericFilter.__init__ (self)
    525      1.1  mrg 
    526      1.1  mrg     def skip_file (self, dir, filename):
    527      1.1  mrg         if filename.endswith ('.m4'):
    528      1.1  mrg             pathname = os.path.join (dir, filename)
    529      1.1  mrg             with open (pathname) as file:
    530      1.1  mrg                 # Skip files imported from gettext.
    531      1.1  mrg                 if file.readline().find ('gettext-') >= 0:
    532      1.1  mrg                     return True
    533      1.1  mrg         return GenericFilter.skip_file (self, dir, filename)
    534      1.1  mrg 
    535      1.1  mrg class GCCFilter (GenericFilter):
    536      1.1  mrg     def __init__ (self):
    537      1.1  mrg         GenericFilter.__init__ (self)
    538      1.1  mrg 
    539      1.1  mrg         self.skip_files |= set ([
    540      1.1  mrg                 # Not part of GCC
    541      1.1  mrg                 'math-68881.h',
    542      1.1  mrg                 ])
    543      1.1  mrg 
    544      1.1  mrg         self.skip_dirs |= set ([
    545      1.1  mrg                 # Better not create a merge nightmare for the GNAT folks.
    546      1.1  mrg                 'ada',
    547      1.1  mrg 
    548      1.1  mrg                 # Handled separately.
    549      1.1  mrg                 'testsuite',
    550      1.1  mrg                 ])
    551      1.1  mrg 
    552      1.1  mrg         self.skip_extensions |= set ([
    553      1.1  mrg                 # Maintained by the translation project.
    554      1.1  mrg                 '.po',
    555      1.1  mrg 
    556      1.1  mrg                 # Automatically-generated.
    557      1.1  mrg                 '.pot',
    558      1.1  mrg                 ])
    559      1.1  mrg 
    560      1.1  mrg         self.fossilised_files |= set ([
    561      1.1  mrg                 # Old news won't be updated.
    562      1.1  mrg                 'ONEWS',
    563      1.1  mrg                 ])
    564      1.1  mrg 
    565      1.1  mrg class TestsuiteFilter (GenericFilter):
    566      1.1  mrg     def __init__ (self):
    567      1.1  mrg         GenericFilter.__init__ (self)
    568      1.1  mrg 
    569      1.1  mrg         self.skip_extensions |= set ([
    570      1.1  mrg                 # Don't change the tests, which could be woend by anyone.
    571      1.1  mrg                 '.c',
    572      1.1  mrg                 '.C',
    573      1.1  mrg                 '.cc',
    574      1.1  mrg                 '.h',
    575      1.1  mrg                 '.hs',
    576      1.1  mrg                 '.f',
    577      1.1  mrg                 '.f90',
    578      1.1  mrg                 '.go',
    579      1.1  mrg                 '.inc',
    580      1.1  mrg                 '.java',
    581      1.1  mrg                 ])
    582      1.1  mrg 
    583      1.1  mrg     def skip_file (self, dir, filename):
    584      1.1  mrg         # g++.niklas/README contains historical copyright information
    585      1.1  mrg         # and isn't updated.
    586      1.1  mrg         if filename == 'README' and os.path.basename (dir) == 'g++.niklas':
    587      1.1  mrg             return True
    588      1.1  mrg         return GenericFilter.skip_file (self, dir, filename)
    589      1.1  mrg 
    590      1.1  mrg class LibCppFilter (GenericFilter):
    591      1.1  mrg     def __init__ (self):
    592      1.1  mrg         GenericFilter.__init__ (self)
    593      1.1  mrg 
    594      1.1  mrg         self.skip_extensions |= set ([
    595      1.1  mrg                 # Maintained by the translation project.
    596      1.1  mrg                 '.po',
    597      1.1  mrg 
    598      1.1  mrg                 # Automatically-generated.
    599      1.1  mrg                 '.pot',
    600      1.1  mrg                 ])
    601      1.1  mrg 
    602      1.1  mrg class LibGCCFilter (GenericFilter):
    603      1.1  mrg     def __init__ (self):
    604      1.1  mrg         GenericFilter.__init__ (self)
    605      1.1  mrg 
    606      1.1  mrg         self.skip_dirs |= set ([
    607      1.1  mrg                 # Imported from GLIBC.
    608      1.1  mrg                 'soft-fp',
    609      1.1  mrg                 ])
    610      1.1  mrg 
    611      1.1  mrg class LibJavaFilter (GenericFilter):
    612      1.1  mrg     def __init__ (self):
    613      1.1  mrg         GenericFilter.__init__ (self)
    614      1.1  mrg 
    615      1.1  mrg         self.skip_dirs |= set ([
    616      1.1  mrg                 # Handled separately.
    617      1.1  mrg                 'testsuite',
    618      1.1  mrg 
    619      1.1  mrg                 # Not really part of the library
    620      1.1  mrg                 'contrib',
    621      1.1  mrg 
    622      1.1  mrg                 # Imported from upstream
    623      1.1  mrg                 'classpath',
    624      1.1  mrg                 'libltdl',
    625      1.1  mrg                 ])
    626      1.1  mrg 
    627      1.1  mrg     def get_line_filter (self, dir, filename):
    628      1.1  mrg         if filename == 'NameDecoder.h':
    629      1.1  mrg             return re.compile ('.*NAME_COPYRIGHT')
    630      1.1  mrg         if filename == 'ICC_Profile.h':
    631      1.1  mrg             return re.compile ('.*icSigCopyrightTag')
    632      1.1  mrg         return GenericFilter.get_line_filter (self, dir, filename)
    633      1.1  mrg 
    634      1.1  mrg class LibMudflapFilter (GenericFilter):
    635      1.1  mrg     def __init__ (self):
    636      1.1  mrg         GenericFilter.__init__ (self)
    637      1.1  mrg 
    638      1.1  mrg         self.skip_dirs |= set ([
    639      1.1  mrg                 # Handled separately.
    640      1.1  mrg                 'testsuite',
    641      1.1  mrg                 ])
    642      1.1  mrg 
    643      1.1  mrg class LibStdCxxFilter (GenericFilter):
    644      1.1  mrg     def __init__ (self):
    645      1.1  mrg         GenericFilter.__init__ (self)
    646      1.1  mrg 
    647      1.1  mrg         self.skip_files |= set ([
    648      1.1  mrg                 # Contains no copyright of its own, but quotes the GPL.
    649      1.1  mrg                 'intro.xml',
    650      1.1  mrg                 ])
    651      1.1  mrg 
    652      1.1  mrg         self.skip_dirs |= set ([
    653      1.1  mrg                 # Contains automatically-generated sources.
    654      1.1  mrg                 'html',
    655      1.1  mrg 
    656      1.1  mrg                 # The testsuite data files shouldn't be changed.
    657      1.1  mrg                 'data',
    658      1.1  mrg 
    659      1.1  mrg                 # Contains imported images
    660      1.1  mrg                 'images',
    661      1.1  mrg                 ])
    662      1.1  mrg 
    663      1.1  mrg         self.own_files |= set ([
    664      1.1  mrg                 # Contains markup around the copyright owner.
    665      1.1  mrg                 'spine.xml',
    666      1.1  mrg                 ])
    667      1.1  mrg 
    668      1.1  mrg     def get_line_filter (self, dir, filename):
    669      1.1  mrg         if filename == 'boost_concept_check.h':
    670      1.1  mrg             return re.compile ('// \(C\) Copyright Jeremy Siek')
    671      1.1  mrg         return GenericFilter.get_line_filter (self, dir, filename)
    672      1.1  mrg 
    673      1.1  mrg class GCCCopyright (Copyright):
    674      1.1  mrg     def __init__ (self, errors):
    675      1.1  mrg         Copyright.__init__ (self, errors)
    676      1.1  mrg 
    677      1.1  mrg         canon_fsf = 'Free Software Foundation, Inc.'
    678      1.1  mrg         self.add_package_author ('Free Software Foundation', canon_fsf)
    679      1.1  mrg         self.add_package_author ('Free Software Foundation.', canon_fsf)
    680      1.1  mrg         self.add_package_author ('Free Software Foundation Inc.', canon_fsf)
    681      1.1  mrg         self.add_package_author ('Free Software Foundation, Inc', canon_fsf)
    682      1.1  mrg         self.add_package_author ('Free Software Foundation, Inc.', canon_fsf)
    683      1.1  mrg         self.add_package_author ('The Free Software Foundation', canon_fsf)
    684      1.1  mrg         self.add_package_author ('The Free Software Foundation, Inc.', canon_fsf)
    685      1.1  mrg         self.add_package_author ('Software Foundation, Inc.', canon_fsf)
    686      1.1  mrg 
    687      1.1  mrg         self.add_external_author ('ARM')
    688      1.1  mrg         self.add_external_author ('AdaCore')
    689      1.1  mrg         self.add_external_author ('Ami Tavory and Vladimir Dreizin, IBM-HRL.')
    690      1.1  mrg         self.add_external_author ('Cavium Networks.')
    691      1.1  mrg         self.add_external_author ('Faraday Technology Corp.')
    692      1.1  mrg         self.add_external_author ('Florida State University')
    693      1.1  mrg         self.add_external_author ('Greg Colvin and Beman Dawes.')
    694      1.1  mrg         self.add_external_author ('Hewlett-Packard Company')
    695      1.1  mrg         self.add_external_author ('Information Technology Industry Council.')
    696      1.1  mrg         self.add_external_author ('James Theiler, Brian Gough')
    697      1.1  mrg         self.add_external_author ('Makoto Matsumoto and Takuji Nishimura,')
    698      1.1  mrg         self.add_external_author ('National Research Council of Canada.')
    699  1.1.1.3  mrg         self.add_external_author ('NVIDIA Corporation')
    700      1.1  mrg         self.add_external_author ('Peter Dimov and Multi Media Ltd.')
    701      1.1  mrg         self.add_external_author ('Peter Dimov')
    702      1.1  mrg         self.add_external_author ('Pipeline Associates, Inc.')
    703      1.1  mrg         self.add_external_author ('Regents of the University of California.')
    704      1.1  mrg         self.add_external_author ('Silicon Graphics Computer Systems, Inc.')
    705      1.1  mrg         self.add_external_author ('Silicon Graphics')
    706      1.1  mrg         self.add_external_author ('Stephen L. Moshier')
    707      1.1  mrg         self.add_external_author ('Sun Microsystems, Inc. All rights reserved.')
    708      1.1  mrg         self.add_external_author ('The Go Authors.  All rights reserved.')
    709      1.1  mrg         self.add_external_author ('The Go Authors. All rights reserved.')
    710      1.1  mrg         self.add_external_author ('The Go Authors.')
    711      1.1  mrg         self.add_external_author ('The Regents of the University of California.')
    712      1.1  mrg         self.add_external_author ('Unicode, Inc.')
    713      1.1  mrg         self.add_external_author ('University of Toronto.')
    714      1.1  mrg 
    715      1.1  mrg class GCCCmdLine (CmdLine):
    716      1.1  mrg     def __init__ (self):
    717      1.1  mrg         CmdLine.__init__ (self, GCCCopyright)
    718      1.1  mrg 
    719      1.1  mrg         self.add_dir ('.', TopLevelFilter())
    720      1.1  mrg         # boehm-gc is imported from upstream.
    721      1.1  mrg         self.add_dir ('config', ConfigFilter())
    722      1.1  mrg         # contrib isn't really part of GCC.
    723      1.1  mrg         self.add_dir ('fixincludes')
    724      1.1  mrg         self.add_dir ('gcc', GCCFilter())
    725      1.1  mrg         self.add_dir (os.path.join ('gcc', 'testsuite'), TestsuiteFilter())
    726      1.1  mrg         self.add_dir ('gnattools')
    727      1.1  mrg         self.add_dir ('include')
    728      1.1  mrg         self.add_dir ('libada')
    729      1.1  mrg         self.add_dir ('libatomic')
    730      1.1  mrg         self.add_dir ('libbacktrace')
    731      1.1  mrg         self.add_dir ('libcpp', LibCppFilter())
    732      1.1  mrg         self.add_dir ('libdecnumber')
    733      1.1  mrg         # libffi is imported from upstream.
    734      1.1  mrg         self.add_dir ('libgcc', LibGCCFilter())
    735      1.1  mrg         self.add_dir ('libgfortran')
    736      1.1  mrg         self.add_dir ('libgomp')
    737      1.1  mrg         self.add_dir ('libiberty')
    738      1.1  mrg         self.add_dir ('libitm')
    739      1.1  mrg         self.add_dir ('libjava', LibJavaFilter())
    740      1.1  mrg         self.add_dir (os.path.join ('libjava', 'testsuite'), TestsuiteFilter())
    741      1.1  mrg         self.add_dir ('libmudflap', LibMudflapFilter())
    742      1.1  mrg         self.add_dir (os.path.join ('libmudflap', 'testsuite'),
    743      1.1  mrg                       TestsuiteFilter())
    744      1.1  mrg         self.add_dir ('libobjc')
    745      1.1  mrg         self.add_dir ('libquadmath')
    746      1.1  mrg         # libsanitiser is imported from upstream.
    747      1.1  mrg         self.add_dir ('libssp')
    748      1.1  mrg         self.add_dir ('libstdc++-v3', LibStdCxxFilter())
    749  1.1.1.3  mrg         self.add_dir ('libvtv')
    750      1.1  mrg         self.add_dir ('lto-plugin')
    751      1.1  mrg         # zlib is imported from upstream.
    752      1.1  mrg 
    753      1.1  mrg         self.default_dirs = [
    754      1.1  mrg             'gcc',
    755      1.1  mrg             'libada',
    756      1.1  mrg             'libatomic',
    757      1.1  mrg             'libbacktrace',
    758      1.1  mrg             'libcpp',
    759      1.1  mrg             'libdecnumber',
    760      1.1  mrg             'libgcc',
    761      1.1  mrg             'libgfortran',
    762      1.1  mrg             'libgomp',
    763      1.1  mrg             'libitm',
    764      1.1  mrg             'libmudflap',
    765      1.1  mrg             'libobjc',
    766      1.1  mrg             'libstdc++-v3',
    767      1.1  mrg             ]
    768      1.1  mrg 
    769      1.1  mrg GCCCmdLine().main()
    770