| 1 | #! /usr/bin/env python3 |
| 2 | |
| 3 | # Copyright (C) 2011-2020 Free Software Foundation, Inc. |
| 4 | # |
| 5 | # This file is part of GDB. |
| 6 | # |
| 7 | # This program is free software; you can redistribute it and/or modify |
| 8 | # it under the terms of the GNU General Public License as published by |
| 9 | # the Free Software Foundation; either version 3 of the License, or |
| 10 | # (at your option) any later version. |
| 11 | # |
| 12 | # This program is distributed in the hope that it will be useful, |
| 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 15 | # GNU General Public License for more details. |
| 16 | # |
| 17 | # You should have received a copy of the GNU General Public License |
| 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 19 | |
| 20 | """copyright.py |
| 21 | |
| 22 | This script updates the list of years in the copyright notices in |
| 23 | most files maintained by the GDB project. |
| 24 | |
| 25 | Usage: cd src/gdb && python copyright.py |
| 26 | |
| 27 | Always review the output of this script before committing it! |
| 28 | A useful command to review the output is: |
| 29 | % filterdiff -x \*.c -x \*.cc -x \*.h -x \*.exp updates.diff |
| 30 | This removes the bulk of the changes which are most likely to be correct. |
| 31 | """ |
| 32 | |
| 33 | import datetime |
| 34 | import locale |
| 35 | import os |
| 36 | import os.path |
| 37 | import subprocess |
| 38 | import sys |
| 39 | |
| 40 | |
| 41 | def get_update_list(): |
| 42 | """Return the list of files to update. |
| 43 | |
| 44 | Assumes that the current working directory when called is the root |
| 45 | of the GDB source tree (NOT the gdb/ subdirectory!). The names of |
| 46 | the files are relative to that root directory. |
| 47 | """ |
| 48 | result = [] |
| 49 | for gdb_dir in ('gdb', 'gnulib', 'sim', 'include/gdb'): |
| 50 | for root, dirs, files in os.walk(gdb_dir, topdown=True): |
| 51 | for dirname in dirs: |
| 52 | reldirname = "%s/%s" % (root, dirname) |
| 53 | if (dirname in EXCLUDE_ALL_LIST |
| 54 | or reldirname in EXCLUDE_LIST |
| 55 | or reldirname in NOT_FSF_LIST |
| 56 | or reldirname in BY_HAND): |
| 57 | # Prune this directory from our search list. |
| 58 | dirs.remove(dirname) |
| 59 | for filename in files: |
| 60 | relpath = "%s/%s" % (root, filename) |
| 61 | if (filename in EXCLUDE_ALL_LIST |
| 62 | or relpath in EXCLUDE_LIST |
| 63 | or relpath in NOT_FSF_LIST |
| 64 | or relpath in BY_HAND): |
| 65 | # Ignore this file. |
| 66 | pass |
| 67 | else: |
| 68 | result.append(relpath) |
| 69 | return result |
| 70 | |
| 71 | |
| 72 | def update_files(update_list): |
| 73 | """Update the copyright header of the files in the given list. |
| 74 | |
| 75 | We use gnulib's update-copyright script for that. |
| 76 | """ |
| 77 | # We want to use year intervals in the copyright notices, and |
| 78 | # all years should be collapsed to one single year interval, |
| 79 | # even if there are "holes" in the list of years found in the |
| 80 | # original copyright notice (OK'ed by the FSF, case [gnu.org #719834]). |
| 81 | os.environ['UPDATE_COPYRIGHT_USE_INTERVALS'] = '2' |
| 82 | |
| 83 | # Perform the update, and save the output in a string. |
| 84 | update_cmd = ['bash', 'gnulib/import/extra/update-copyright'] |
| 85 | update_cmd += update_list |
| 86 | |
| 87 | p = subprocess.Popen(update_cmd, stdout=subprocess.PIPE, |
| 88 | stderr=subprocess.STDOUT, |
| 89 | encoding=locale.getpreferredencoding()) |
| 90 | update_out = p.communicate()[0] |
| 91 | |
| 92 | # Process the output. Typically, a lot of files do not have |
| 93 | # a copyright notice :-(. The update-copyright script prints |
| 94 | # a well defined warning when it did not find the copyright notice. |
| 95 | # For each of those, do a sanity check and see if they may in fact |
| 96 | # have one. For the files that are found not to have one, we filter |
| 97 | # the line out from the output, since there is nothing more to do, |
| 98 | # short of looking at each file and seeing which notice is appropriate. |
| 99 | # Too much work! (~4,000 files listed as of 2012-01-03). |
| 100 | update_out = update_out.splitlines(keepends=False) |
| 101 | warning_string = ': warning: copyright statement not found' |
| 102 | warning_len = len(warning_string) |
| 103 | |
| 104 | for line in update_out: |
| 105 | if line.endswith(warning_string): |
| 106 | filename = line[:-warning_len] |
| 107 | if may_have_copyright_notice(filename): |
| 108 | print(line) |
| 109 | else: |
| 110 | # Unrecognized file format. !?! |
| 111 | print("*** " + line) |
| 112 | |
| 113 | |
| 114 | def may_have_copyright_notice(filename): |
| 115 | """Check that the given file does not seem to have a copyright notice. |
| 116 | |
| 117 | The filename is relative to the root directory. |
| 118 | This function assumes that the current working directory is that root |
| 119 | directory. |
| 120 | |
| 121 | The algorigthm is fairly crude, meaning that it might return |
| 122 | some false positives. I do not think it will return any false |
| 123 | negatives... We might improve this function to handle more |
| 124 | complex cases later... |
| 125 | """ |
| 126 | # For now, it may have a copyright notice if we find the word |
| 127 | # "Copyright" at the (reasonable) start of the given file, say |
| 128 | # 50 lines... |
| 129 | MAX_LINES = 50 |
| 130 | |
| 131 | # We don't really know what encoding each file might be following, |
| 132 | # so just open the file as a byte stream. We only need to search |
| 133 | # for a pattern that should be the same regardless of encoding, |
| 134 | # so that should be good enough. |
| 135 | fd = open(filename, 'rb') |
| 136 | |
| 137 | lineno = 1 |
| 138 | for line in fd: |
| 139 | if b'Copyright' in line: |
| 140 | return True |
| 141 | lineno += 1 |
| 142 | if lineno > 50: |
| 143 | return False |
| 144 | return False |
| 145 | |
| 146 | |
| 147 | def main (): |
| 148 | """The main subprogram.""" |
| 149 | root_dir = os.path.dirname(os.getcwd()) |
| 150 | os.chdir(root_dir) |
| 151 | |
| 152 | if not (os.path.isdir('gdb') and |
| 153 | os.path.isfile("gnulib/import/extra/update-copyright")): |
| 154 | print("Error: This script must be called from the gdb directory.") |
| 155 | sys.exit(1) |
| 156 | |
| 157 | update_list = get_update_list() |
| 158 | update_files (update_list) |
| 159 | |
| 160 | # Remind the user that some files need to be updated by HAND... |
| 161 | |
| 162 | if MULTIPLE_COPYRIGHT_HEADERS: |
| 163 | print() |
| 164 | print("\033[31m" |
| 165 | "REMINDER: Multiple copyright headers must be updated by hand:" |
| 166 | "\033[0m") |
| 167 | for filename in MULTIPLE_COPYRIGHT_HEADERS: |
| 168 | print(" ", filename) |
| 169 | |
| 170 | if BY_HAND: |
| 171 | print() |
| 172 | print("\033[31mREMINDER: The following files must be updated by hand." \ |
| 173 | "\033[0m") |
| 174 | for filename in BY_HAND: |
| 175 | print(" ", filename) |
| 176 | |
| 177 | ############################################################################ |
| 178 | # |
| 179 | # Some constants, placed at the end because they take up a lot of room. |
| 180 | # The actual value of these constants is not significant to the understanding |
| 181 | # of the script. |
| 182 | # |
| 183 | ############################################################################ |
| 184 | |
| 185 | # Files which should not be modified, either because they are |
| 186 | # generated, non-FSF, or otherwise special (e.g. license text, |
| 187 | # or test cases which must be sensitive to line numbering). |
| 188 | # |
| 189 | # Filenames are relative to the root directory. |
| 190 | EXCLUDE_LIST = ( |
| 191 | 'gdb/nat/glibc_thread_db.h', |
| 192 | 'gdb/CONTRIBUTE', |
| 193 | 'gnulib/import', |
| 194 | 'gnulib/config.in', |
| 195 | 'gnulib/Makefile.in', |
| 196 | ) |
| 197 | |
| 198 | # Files which should not be modified, either because they are |
| 199 | # generated, non-FSF, or otherwise special (e.g. license text, |
| 200 | # or test cases which must be sensitive to line numbering). |
| 201 | # |
| 202 | # Matches any file or directory name anywhere. Use with caution. |
| 203 | # This is mostly for files that can be found in multiple directories. |
| 204 | # Eg: We want all files named COPYING to be left untouched. |
| 205 | |
| 206 | EXCLUDE_ALL_LIST = ( |
| 207 | "COPYING", "COPYING.LIB", "CVS", "configure", "copying.c", |
| 208 | "fdl.texi", "gpl.texi", "aclocal.m4", |
| 209 | ) |
| 210 | |
| 211 | # The list of files to update by hand. |
| 212 | BY_HAND = ( |
| 213 | # Nothing at the moment :-). |
| 214 | ) |
| 215 | |
| 216 | # Files containing multiple copyright headers. This script is only |
| 217 | # fixing the first one it finds, so we need to finish the update |
| 218 | # by hand. |
| 219 | MULTIPLE_COPYRIGHT_HEADERS = ( |
| 220 | "gdb/doc/gdb.texinfo", |
| 221 | "gdb/doc/refcard.tex", |
| 222 | "gdb/gdbarch.sh", |
| 223 | ) |
| 224 | |
| 225 | # The list of file which have a copyright, but not head by the FSF. |
| 226 | # Filenames are relative to the root directory. |
| 227 | NOT_FSF_LIST = ( |
| 228 | "gdb/exc_request.defs", |
| 229 | "gdb/gdbtk", |
| 230 | "gdb/testsuite/gdb.gdbtk/", |
| 231 | "sim/arm/armemu.h", "sim/arm/armos.c", "sim/arm/gdbhost.c", |
| 232 | "sim/arm/dbg_hif.h", "sim/arm/dbg_conf.h", "sim/arm/communicate.h", |
| 233 | "sim/arm/armos.h", "sim/arm/armcopro.c", "sim/arm/armemu.c", |
| 234 | "sim/arm/kid.c", "sim/arm/thumbemu.c", "sim/arm/armdefs.h", |
| 235 | "sim/arm/armopts.h", "sim/arm/dbg_cp.h", "sim/arm/dbg_rdi.h", |
| 236 | "sim/arm/parent.c", "sim/arm/armsupp.c", "sim/arm/armrdi.c", |
| 237 | "sim/arm/bag.c", "sim/arm/armvirt.c", "sim/arm/main.c", "sim/arm/bag.h", |
| 238 | "sim/arm/communicate.c", "sim/arm/gdbhost.h", "sim/arm/armfpe.h", |
| 239 | "sim/arm/arminit.c", |
| 240 | "sim/common/cgen-fpu.c", "sim/common/cgen-fpu.h", |
| 241 | "sim/common/cgen-accfp.c", |
| 242 | "sim/mips/m16run.c", "sim/mips/sim-main.c", |
| 243 | "sim/moxie/moxie-gdb.dts", |
| 244 | # Not a single file in sim/ppc/ appears to be copyright FSF :-(. |
| 245 | "sim/ppc/filter.h", "sim/ppc/gen-support.h", "sim/ppc/ld-insn.h", |
| 246 | "sim/ppc/hw_sem.c", "sim/ppc/hw_disk.c", "sim/ppc/idecode_branch.h", |
| 247 | "sim/ppc/sim-endian.h", "sim/ppc/table.c", "sim/ppc/hw_core.c", |
| 248 | "sim/ppc/gen-support.c", "sim/ppc/gen-semantics.h", "sim/ppc/cpu.h", |
| 249 | "sim/ppc/sim_callbacks.h", "sim/ppc/RUN", "sim/ppc/Makefile.in", |
| 250 | "sim/ppc/emul_chirp.c", "sim/ppc/hw_nvram.c", "sim/ppc/dc-test.01", |
| 251 | "sim/ppc/hw_phb.c", "sim/ppc/hw_eeprom.c", "sim/ppc/bits.h", |
| 252 | "sim/ppc/hw_vm.c", "sim/ppc/cap.h", "sim/ppc/os_emul.h", |
| 253 | "sim/ppc/options.h", "sim/ppc/gen-idecode.c", "sim/ppc/filter.c", |
| 254 | "sim/ppc/corefile-n.h", "sim/ppc/std-config.h", "sim/ppc/ld-decode.h", |
| 255 | "sim/ppc/filter_filename.h", "sim/ppc/hw_shm.c", |
| 256 | "sim/ppc/pk_disklabel.c", "sim/ppc/dc-simple", "sim/ppc/misc.h", |
| 257 | "sim/ppc/device_table.h", "sim/ppc/ld-insn.c", "sim/ppc/inline.c", |
| 258 | "sim/ppc/emul_bugapi.h", "sim/ppc/hw_cpu.h", "sim/ppc/debug.h", |
| 259 | "sim/ppc/hw_ide.c", "sim/ppc/debug.c", "sim/ppc/gen-itable.h", |
| 260 | "sim/ppc/interrupts.c", "sim/ppc/hw_glue.c", "sim/ppc/emul_unix.c", |
| 261 | "sim/ppc/sim_calls.c", "sim/ppc/dc-complex", "sim/ppc/ld-cache.c", |
| 262 | "sim/ppc/registers.h", "sim/ppc/dc-test.02", "sim/ppc/options.c", |
| 263 | "sim/ppc/igen.h", "sim/ppc/registers.c", "sim/ppc/device.h", |
| 264 | "sim/ppc/emul_chirp.h", "sim/ppc/hw_register.c", "sim/ppc/hw_init.c", |
| 265 | "sim/ppc/sim-endian-n.h", "sim/ppc/filter_filename.c", |
| 266 | "sim/ppc/bits.c", "sim/ppc/idecode_fields.h", "sim/ppc/hw_memory.c", |
| 267 | "sim/ppc/misc.c", "sim/ppc/double.c", "sim/ppc/psim.h", |
| 268 | "sim/ppc/hw_trace.c", "sim/ppc/emul_netbsd.h", "sim/ppc/psim.c", |
| 269 | "sim/ppc/ppc-instructions", "sim/ppc/tree.h", "sim/ppc/README", |
| 270 | "sim/ppc/gen-icache.h", "sim/ppc/gen-model.h", "sim/ppc/ld-cache.h", |
| 271 | "sim/ppc/mon.c", "sim/ppc/corefile.h", "sim/ppc/vm.c", |
| 272 | "sim/ppc/INSTALL", "sim/ppc/gen-model.c", "sim/ppc/hw_cpu.c", |
| 273 | "sim/ppc/corefile.c", "sim/ppc/hw_opic.c", "sim/ppc/gen-icache.c", |
| 274 | "sim/ppc/events.h", "sim/ppc/os_emul.c", "sim/ppc/emul_generic.c", |
| 275 | "sim/ppc/main.c", "sim/ppc/hw_com.c", "sim/ppc/gen-semantics.c", |
| 276 | "sim/ppc/emul_bugapi.c", "sim/ppc/device.c", "sim/ppc/emul_generic.h", |
| 277 | "sim/ppc/tree.c", "sim/ppc/mon.h", "sim/ppc/interrupts.h", |
| 278 | "sim/ppc/cap.c", "sim/ppc/cpu.c", "sim/ppc/hw_phb.h", |
| 279 | "sim/ppc/device_table.c", "sim/ppc/lf.c", "sim/ppc/lf.c", |
| 280 | "sim/ppc/dc-stupid", "sim/ppc/hw_pal.c", "sim/ppc/ppc-spr-table", |
| 281 | "sim/ppc/emul_unix.h", "sim/ppc/words.h", "sim/ppc/basics.h", |
| 282 | "sim/ppc/hw_htab.c", "sim/ppc/lf.h", "sim/ppc/ld-decode.c", |
| 283 | "sim/ppc/sim-endian.c", "sim/ppc/gen-itable.c", |
| 284 | "sim/ppc/idecode_expression.h", "sim/ppc/table.h", "sim/ppc/dgen.c", |
| 285 | "sim/ppc/events.c", "sim/ppc/gen-idecode.h", "sim/ppc/emul_netbsd.c", |
| 286 | "sim/ppc/igen.c", "sim/ppc/vm_n.h", "sim/ppc/vm.h", |
| 287 | "sim/ppc/hw_iobus.c", "sim/ppc/inline.h", |
| 288 | "sim/testsuite/sim/bfin/s21.s", "sim/testsuite/sim/mips/mips32-dsp2.s", |
| 289 | ) |
| 290 | |
| 291 | if __name__ == "__main__": |
| 292 | main() |
| 293 | |