update-copyright.py revision 1.1.1.11 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