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