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