logo

qmk_firmware

custom branch of QMK firmware git clone https://anongit.hacktivis.me/git/qmk_firmware.git

license_check.py (4860B)


  1. # Copyright 2023 Nick Brassel (@tzarc)
  2. # SPDX-License-Identifier: GPL-2.0-or-later
  3. import re
  4. from milc import cli
  5. from qmk.constants import LICENSE_TEXTS
  6. from qmk.path import normpath
  7. L_PAREN = re.compile(r'\(\[\{\<')
  8. R_PAREN = re.compile(r'\)\]\}\>')
  9. PUNCTUATION = re.compile(r'[\.,;:]+')
  10. TRASH_PREFIX = re.compile(r'^(\s|/|\*|#)+')
  11. TRASH_SUFFIX = re.compile(r'(\s|/|\*|#|\\)+$')
  12. SPACE = re.compile(r'\s+')
  13. SUFFIXES = ['.c', '.h', '.cpp', '.cxx', '.hpp', '.hxx']
  14. def _simplify_text(input):
  15. lines = input.lower().split('\n')
  16. lines = [PUNCTUATION.sub('', line) for line in lines]
  17. lines = [TRASH_PREFIX.sub('', line) for line in lines]
  18. lines = [TRASH_SUFFIX.sub('', line) for line in lines]
  19. lines = [SPACE.sub(' ', line) for line in lines]
  20. lines = [L_PAREN.sub('(', line) for line in lines]
  21. lines = [R_PAREN.sub(')', line) for line in lines]
  22. lines = [line.strip() for line in lines]
  23. lines = [line for line in lines if line is not None and line != '']
  24. return ' '.join(lines)
  25. def _preformat_license_texts():
  26. # Pre-format all the licenses
  27. for _, long_licenses in LICENSE_TEXTS:
  28. for i in range(len(long_licenses)):
  29. long_licenses[i] = _simplify_text(long_licenses[i])
  30. def _determine_suffix_condition(extensions):
  31. def _default_suffix_condition(s):
  32. return s in SUFFIXES
  33. conditional = _default_suffix_condition
  34. if extensions is not None and len(extensions) > 0:
  35. suffixes = [f'.{s}' if not s.startswith('.') else s for s in extensions]
  36. def _specific_suffix_condition(s):
  37. return s in suffixes
  38. conditional = _specific_suffix_condition
  39. return conditional
  40. def _determine_file_list(inputs, conditional):
  41. check_list = set()
  42. for filename in inputs:
  43. if filename.is_dir():
  44. for file in sorted(filename.rglob('*')):
  45. if file.is_file() and conditional(file.suffix):
  46. check_list.add(file)
  47. elif filename.is_file():
  48. if conditional(filename.suffix):
  49. check_list.add(filename)
  50. return list(sorted(check_list))
  51. def _detect_license_from_file_contents(filename, absolute=False, short=False):
  52. data = filename.read_text(encoding='utf-8', errors='ignore')
  53. filename_out = str(filename.absolute()) if absolute else str(filename)
  54. if 'SPDX-License-Identifier:' in data:
  55. res = data.split('SPDX-License-Identifier:')
  56. license = re.split(r'\s|//|\*', res[1].strip())[0].strip()
  57. found = False
  58. for short_license, _ in LICENSE_TEXTS:
  59. if license.lower() == short_license.lower():
  60. license = short_license
  61. found = True
  62. break
  63. if not found:
  64. if short:
  65. print(f'{filename_out} UNKNOWN')
  66. else:
  67. cli.log.error(f'{{fg_cyan}}{filename_out}{{fg_reset}} -- unknown license, or no license detected!')
  68. return False
  69. if short:
  70. print(f'{filename_out} {license}')
  71. else:
  72. cli.log.info(f'{{fg_cyan}}{filename_out}{{fg_reset}} -- license detected: {license} (SPDX License Identifier)')
  73. return True
  74. else:
  75. simple_text = _simplify_text(data)
  76. for short_license, long_licenses in LICENSE_TEXTS:
  77. for long_license in long_licenses:
  78. if long_license in simple_text:
  79. if short:
  80. print(f'{filename_out} {short_license}')
  81. else:
  82. cli.log.info(f'{{fg_cyan}}{filename_out}{{fg_reset}} -- license detected: {short_license} (Full text)')
  83. return True
  84. if short:
  85. print(f'{filename_out} UNKNOWN')
  86. else:
  87. cli.log.error(f'{{fg_cyan}}{filename_out}{{fg_reset}} -- unknown license, or no license detected!')
  88. return False
  89. @cli.argument('inputs', nargs='*', arg_only=True, type=normpath, help='List of input files or directories.')
  90. @cli.argument('-s', '--short', action='store_true', help='Short output.')
  91. @cli.argument('-a', '--absolute', action='store_true', help='Print absolute paths.')
  92. @cli.argument('-e', '--extension', arg_only=True, action='append', default=[], help='Override list of extensions. Can be specified multiple times for multiple extensions.')
  93. @cli.subcommand('File license check.', hidden=False if cli.config.user.developer else True)
  94. def license_check(cli):
  95. _preformat_license_texts()
  96. conditional = _determine_suffix_condition(cli.args.extension)
  97. check_list = _determine_file_list(cli.args.inputs, conditional)
  98. failed = False
  99. for filename in sorted(check_list):
  100. if not _detect_license_from_file_contents(filename, absolute=cli.args.absolute, short=cli.args.short):
  101. failed = True
  102. if failed:
  103. return False