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