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