1fd4e5da5Sopenharmony_ci#!/usr/bin/env python 2fd4e5da5Sopenharmony_ci# coding=utf-8 3fd4e5da5Sopenharmony_ci# Copyright (c) 2016 Google Inc. 4fd4e5da5Sopenharmony_ci# 5fd4e5da5Sopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License"); 6fd4e5da5Sopenharmony_ci# you may not use this file except in compliance with the License. 7fd4e5da5Sopenharmony_ci# You may obtain a copy of the License at 8fd4e5da5Sopenharmony_ci# 9fd4e5da5Sopenharmony_ci# http://www.apache.org/licenses/LICENSE-2.0 10fd4e5da5Sopenharmony_ci# 11fd4e5da5Sopenharmony_ci# Unless required by applicable law or agreed to in writing, software 12fd4e5da5Sopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS, 13fd4e5da5Sopenharmony_ci# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14fd4e5da5Sopenharmony_ci# See the License for the specific language governing permissions and 15fd4e5da5Sopenharmony_ci# limitations under the License. 16fd4e5da5Sopenharmony_ci"""Checks for copyright notices in all the files that need them under the 17fd4e5da5Sopenharmony_ci 18fd4e5da5Sopenharmony_cicurrent directory. Optionally insert them. When inserting, replaces 19fd4e5da5Sopenharmony_cian MIT or Khronos free use license with Apache 2. 20fd4e5da5Sopenharmony_ci""" 21fd4e5da5Sopenharmony_ci 22fd4e5da5Sopenharmony_ciimport argparse 23fd4e5da5Sopenharmony_ciimport fileinput 24fd4e5da5Sopenharmony_ciimport fnmatch 25fd4e5da5Sopenharmony_ciimport inspect 26fd4e5da5Sopenharmony_ciimport os 27fd4e5da5Sopenharmony_ciimport re 28fd4e5da5Sopenharmony_ciimport sys 29fd4e5da5Sopenharmony_ci 30fd4e5da5Sopenharmony_ci# List of designated copyright owners. 31fd4e5da5Sopenharmony_ciAUTHORS = ['The Khronos Group Inc.', 32fd4e5da5Sopenharmony_ci 'LunarG Inc.', 33fd4e5da5Sopenharmony_ci 'Google Inc.', 34fd4e5da5Sopenharmony_ci 'Google LLC', 35fd4e5da5Sopenharmony_ci 'Pierre Moreau', 36fd4e5da5Sopenharmony_ci 'Samsung Inc', 37fd4e5da5Sopenharmony_ci 'André Perez Maselco', 38fd4e5da5Sopenharmony_ci 'Vasyl Teliman', 39fd4e5da5Sopenharmony_ci 'Advanced Micro Devices, Inc.', 40fd4e5da5Sopenharmony_ci 'Stefano Milizia', 41fd4e5da5Sopenharmony_ci 'Alastair F. Donaldson', 42fd4e5da5Sopenharmony_ci 'Mostafa Ashraf', 43fd4e5da5Sopenharmony_ci 'Shiyu Liu', 44fd4e5da5Sopenharmony_ci 'ZHOU He', 45fd4e5da5Sopenharmony_ci 'Nintendo'] 46fd4e5da5Sopenharmony_ciCURRENT_YEAR = 2023 47fd4e5da5Sopenharmony_ci 48fd4e5da5Sopenharmony_ciFIRST_YEAR = 2014 49fd4e5da5Sopenharmony_ciFINAL_YEAR = CURRENT_YEAR + 5 50fd4e5da5Sopenharmony_ci# A regular expression to match the valid years in the copyright information. 51fd4e5da5Sopenharmony_ciYEAR_REGEX = '(' + '|'.join( 52fd4e5da5Sopenharmony_ci str(year) for year in range(FIRST_YEAR, FINAL_YEAR + 1)) + ')' 53fd4e5da5Sopenharmony_ci 54fd4e5da5Sopenharmony_ci# A regular expression to make a range of years in the form <year1>-<year2>. 55fd4e5da5Sopenharmony_ciYEAR_RANGE_REGEX = '(' 56fd4e5da5Sopenharmony_cifor year1 in range(FIRST_YEAR, FINAL_YEAR + 1): 57fd4e5da5Sopenharmony_ci for year2 in range(year1 + 1, FINAL_YEAR + 1): 58fd4e5da5Sopenharmony_ci YEAR_RANGE_REGEX += str(year1) + '-' + str(year2) + '|' 59fd4e5da5Sopenharmony_ciYEAR_RANGE_REGEX = YEAR_RANGE_REGEX[:-1] + ')' 60fd4e5da5Sopenharmony_ci 61fd4e5da5Sopenharmony_ci# In the copyright info, the year can be a single year or a range. This is a 62fd4e5da5Sopenharmony_ci# regex to make sure it matches one of them. 63fd4e5da5Sopenharmony_ciYEAR_OR_RANGE_REGEX = '(' + YEAR_REGEX + '|' + YEAR_RANGE_REGEX + ')' 64fd4e5da5Sopenharmony_ci 65fd4e5da5Sopenharmony_ci# The final regular expression to match a valid copyright line. 66fd4e5da5Sopenharmony_ciCOPYRIGHT_RE = re.compile('Copyright \(c\) {} ({})'.format( 67fd4e5da5Sopenharmony_ci YEAR_OR_RANGE_REGEX, '|'.join(AUTHORS))) 68fd4e5da5Sopenharmony_ci 69fd4e5da5Sopenharmony_ciMIT_BEGIN_RE = re.compile('Permission is hereby granted, ' 70fd4e5da5Sopenharmony_ci 'free of charge, to any person obtaining a') 71fd4e5da5Sopenharmony_ciMIT_END_RE = re.compile('MATERIALS OR THE USE OR OTHER DEALINGS IN ' 72fd4e5da5Sopenharmony_ci 'THE MATERIALS.') 73fd4e5da5Sopenharmony_ciAPACHE2_BEGIN_RE = re.compile('Licensed under the Apache License, ' 74fd4e5da5Sopenharmony_ci 'Version 2.0 \(the "License"\);') 75fd4e5da5Sopenharmony_ciAPACHE2_END_RE = re.compile('limitations under the License.') 76fd4e5da5Sopenharmony_ci 77fd4e5da5Sopenharmony_ciLICENSED = """Licensed under the Apache License, Version 2.0 (the "License"); 78fd4e5da5Sopenharmony_ciyou may not use this file except in compliance with the License. 79fd4e5da5Sopenharmony_ciYou may obtain a copy of the License at 80fd4e5da5Sopenharmony_ci 81fd4e5da5Sopenharmony_ci http://www.apache.org/licenses/LICENSE-2.0 82fd4e5da5Sopenharmony_ci 83fd4e5da5Sopenharmony_ciUnless required by applicable law or agreed to in writing, software 84fd4e5da5Sopenharmony_cidistributed under the License is distributed on an "AS IS" BASIS, 85fd4e5da5Sopenharmony_ciWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 86fd4e5da5Sopenharmony_ciSee the License for the specific language governing permissions and 87fd4e5da5Sopenharmony_cilimitations under the License.""" 88fd4e5da5Sopenharmony_ciLICENSED_LEN = 10 # Number of lines in LICENSED 89fd4e5da5Sopenharmony_ci 90fd4e5da5Sopenharmony_ci 91fd4e5da5Sopenharmony_cidef find(top, filename_glob, skip_glob_dir_list, skip_glob_files_list): 92fd4e5da5Sopenharmony_ci """Returns files in the tree rooted at top matching filename_glob but not 93fd4e5da5Sopenharmony_ci in directories matching skip_glob_dir_list nor files matching 94fd4e5da5Sopenharmony_ci skip_glob_dir_list.""" 95fd4e5da5Sopenharmony_ci 96fd4e5da5Sopenharmony_ci file_list = [] 97fd4e5da5Sopenharmony_ci for path, dirs, files in os.walk(top): 98fd4e5da5Sopenharmony_ci for glob in skip_glob_dir_list: 99fd4e5da5Sopenharmony_ci for match in fnmatch.filter(dirs, glob): 100fd4e5da5Sopenharmony_ci dirs.remove(match) 101fd4e5da5Sopenharmony_ci for filename in fnmatch.filter(files, filename_glob): 102fd4e5da5Sopenharmony_ci full_file = os.path.join(path, filename) 103fd4e5da5Sopenharmony_ci if full_file not in skip_glob_files_list: 104fd4e5da5Sopenharmony_ci file_list.append(full_file) 105fd4e5da5Sopenharmony_ci return file_list 106fd4e5da5Sopenharmony_ci 107fd4e5da5Sopenharmony_ci 108fd4e5da5Sopenharmony_cidef filtered_descendants(glob): 109fd4e5da5Sopenharmony_ci """Returns glob-matching filenames under the current directory, but skips 110fd4e5da5Sopenharmony_ci some irrelevant paths.""" 111fd4e5da5Sopenharmony_ci return find('.', glob, ['third_party', 'external', 'CompilerIdCXX', 112fd4e5da5Sopenharmony_ci 'build*', 'out*'], ['./utils/clang-format-diff.py']) 113fd4e5da5Sopenharmony_ci 114fd4e5da5Sopenharmony_ci 115fd4e5da5Sopenharmony_cidef skip(line): 116fd4e5da5Sopenharmony_ci """Returns true if line is all whitespace or shebang.""" 117fd4e5da5Sopenharmony_ci stripped = line.lstrip() 118fd4e5da5Sopenharmony_ci return stripped == '' or stripped.startswith('#!') 119fd4e5da5Sopenharmony_ci 120fd4e5da5Sopenharmony_ci 121fd4e5da5Sopenharmony_cidef comment(text, prefix): 122fd4e5da5Sopenharmony_ci """Returns commented-out text. 123fd4e5da5Sopenharmony_ci 124fd4e5da5Sopenharmony_ci Each line of text will be prefixed by prefix and a space character. Any 125fd4e5da5Sopenharmony_ci trailing whitespace will be trimmed. 126fd4e5da5Sopenharmony_ci """ 127fd4e5da5Sopenharmony_ci accum = ['{} {}'.format(prefix, line).rstrip() for line in text.split('\n')] 128fd4e5da5Sopenharmony_ci return '\n'.join(accum) 129fd4e5da5Sopenharmony_ci 130fd4e5da5Sopenharmony_ci 131fd4e5da5Sopenharmony_cidef insert_copyright(author, glob, comment_prefix): 132fd4e5da5Sopenharmony_ci """Finds all glob-matching files under the current directory and inserts the 133fd4e5da5Sopenharmony_ci copyright message, and license notice. An MIT license or Khronos free 134fd4e5da5Sopenharmony_ci use license (modified MIT) is replaced with an Apache 2 license. 135fd4e5da5Sopenharmony_ci 136fd4e5da5Sopenharmony_ci The copyright message goes into the first non-whitespace, non-shebang line 137fd4e5da5Sopenharmony_ci in a file. The license notice follows it. Both are prefixed on each line 138fd4e5da5Sopenharmony_ci by comment_prefix and a space. 139fd4e5da5Sopenharmony_ci """ 140fd4e5da5Sopenharmony_ci 141fd4e5da5Sopenharmony_ci copyright = comment('Copyright (c) {} {}'.format(CURRENT_YEAR, author), 142fd4e5da5Sopenharmony_ci comment_prefix) + '\n\n' 143fd4e5da5Sopenharmony_ci licensed = comment(LICENSED, comment_prefix) + '\n\n' 144fd4e5da5Sopenharmony_ci for file in filtered_descendants(glob): 145fd4e5da5Sopenharmony_ci # Parsing states are: 146fd4e5da5Sopenharmony_ci # 0 Initial: Have not seen a copyright declaration. 147fd4e5da5Sopenharmony_ci # 1 Seen a copyright line and no other interesting lines 148fd4e5da5Sopenharmony_ci # 2 In the middle of an MIT or Khronos free use license 149fd4e5da5Sopenharmony_ci # 9 Exited any of the above 150fd4e5da5Sopenharmony_ci state = 0 151fd4e5da5Sopenharmony_ci update_file = False 152fd4e5da5Sopenharmony_ci for line in fileinput.input(file, inplace=1): 153fd4e5da5Sopenharmony_ci emit = True 154fd4e5da5Sopenharmony_ci if state == 0: 155fd4e5da5Sopenharmony_ci if COPYRIGHT_RE.search(line): 156fd4e5da5Sopenharmony_ci state = 1 157fd4e5da5Sopenharmony_ci elif skip(line): 158fd4e5da5Sopenharmony_ci pass 159fd4e5da5Sopenharmony_ci else: 160fd4e5da5Sopenharmony_ci # Didn't see a copyright. Inject copyright and license. 161fd4e5da5Sopenharmony_ci sys.stdout.write(copyright) 162fd4e5da5Sopenharmony_ci sys.stdout.write(licensed) 163fd4e5da5Sopenharmony_ci # Assume there isn't a previous license notice. 164fd4e5da5Sopenharmony_ci state = 1 165fd4e5da5Sopenharmony_ci elif state == 1: 166fd4e5da5Sopenharmony_ci if MIT_BEGIN_RE.search(line): 167fd4e5da5Sopenharmony_ci state = 2 168fd4e5da5Sopenharmony_ci emit = False 169fd4e5da5Sopenharmony_ci elif APACHE2_BEGIN_RE.search(line): 170fd4e5da5Sopenharmony_ci # Assume an Apache license is preceded by a copyright 171fd4e5da5Sopenharmony_ci # notice. So just emit it like the rest of the file. 172fd4e5da5Sopenharmony_ci state = 9 173fd4e5da5Sopenharmony_ci elif state == 2: 174fd4e5da5Sopenharmony_ci # Replace the MIT license with Apache 2 175fd4e5da5Sopenharmony_ci emit = False 176fd4e5da5Sopenharmony_ci if MIT_END_RE.search(line): 177fd4e5da5Sopenharmony_ci state = 9 178fd4e5da5Sopenharmony_ci sys.stdout.write(licensed) 179fd4e5da5Sopenharmony_ci if emit: 180fd4e5da5Sopenharmony_ci sys.stdout.write(line) 181fd4e5da5Sopenharmony_ci 182fd4e5da5Sopenharmony_ci 183fd4e5da5Sopenharmony_cidef alert_if_no_copyright(glob, comment_prefix): 184fd4e5da5Sopenharmony_ci """Prints names of all files missing either a copyright or Apache 2 license. 185fd4e5da5Sopenharmony_ci 186fd4e5da5Sopenharmony_ci Finds all glob-matching files under the current directory and checks if they 187fd4e5da5Sopenharmony_ci contain the copyright message and license notice. Prints the names of all the 188fd4e5da5Sopenharmony_ci files that don't meet both criteria. 189fd4e5da5Sopenharmony_ci 190fd4e5da5Sopenharmony_ci Returns the total number of file names printed. 191fd4e5da5Sopenharmony_ci """ 192fd4e5da5Sopenharmony_ci printed_count = 0 193fd4e5da5Sopenharmony_ci for file in filtered_descendants(glob): 194fd4e5da5Sopenharmony_ci has_copyright = False 195fd4e5da5Sopenharmony_ci has_apache2 = False 196fd4e5da5Sopenharmony_ci line_num = 0 197fd4e5da5Sopenharmony_ci apache_expected_end = 0 198fd4e5da5Sopenharmony_ci with open(file, encoding='utf-8') as contents: 199fd4e5da5Sopenharmony_ci for line in contents: 200fd4e5da5Sopenharmony_ci line_num += 1 201fd4e5da5Sopenharmony_ci if COPYRIGHT_RE.search(line): 202fd4e5da5Sopenharmony_ci has_copyright = True 203fd4e5da5Sopenharmony_ci if APACHE2_BEGIN_RE.search(line): 204fd4e5da5Sopenharmony_ci apache_expected_end = line_num + LICENSED_LEN 205fd4e5da5Sopenharmony_ci if (line_num is apache_expected_end) and APACHE2_END_RE.search(line): 206fd4e5da5Sopenharmony_ci has_apache2 = True 207fd4e5da5Sopenharmony_ci if not (has_copyright and has_apache2): 208fd4e5da5Sopenharmony_ci message = file 209fd4e5da5Sopenharmony_ci if not has_copyright: 210fd4e5da5Sopenharmony_ci message += ' has no copyright' 211fd4e5da5Sopenharmony_ci if not has_apache2: 212fd4e5da5Sopenharmony_ci message += ' has no Apache 2 license notice' 213fd4e5da5Sopenharmony_ci print(message) 214fd4e5da5Sopenharmony_ci printed_count += 1 215fd4e5da5Sopenharmony_ci return printed_count 216fd4e5da5Sopenharmony_ci 217fd4e5da5Sopenharmony_ci 218fd4e5da5Sopenharmony_ciclass ArgParser(argparse.ArgumentParser): 219fd4e5da5Sopenharmony_ci def __init__(self): 220fd4e5da5Sopenharmony_ci super(ArgParser, self).__init__( 221fd4e5da5Sopenharmony_ci description=inspect.getdoc(sys.modules[__name__])) 222fd4e5da5Sopenharmony_ci self.add_argument('--update', dest='author', action='store', 223fd4e5da5Sopenharmony_ci help='For files missing a copyright notice, insert ' 224fd4e5da5Sopenharmony_ci 'one for the given author, and add a license ' 225fd4e5da5Sopenharmony_ci 'notice. The author must be in the AUTHORS ' 226fd4e5da5Sopenharmony_ci 'list in the script.') 227fd4e5da5Sopenharmony_ci 228fd4e5da5Sopenharmony_ci 229fd4e5da5Sopenharmony_cidef main(): 230fd4e5da5Sopenharmony_ci glob_comment_pairs = [('*.h', '//'), ('*.hpp', '//'), ('*.sh', '#'), 231fd4e5da5Sopenharmony_ci ('*.py', '#'), ('*.cpp', '//'), 232fd4e5da5Sopenharmony_ci ('CMakeLists.txt', '#')] 233fd4e5da5Sopenharmony_ci argparser = ArgParser() 234fd4e5da5Sopenharmony_ci args = argparser.parse_args() 235fd4e5da5Sopenharmony_ci 236fd4e5da5Sopenharmony_ci if args.author: 237fd4e5da5Sopenharmony_ci if args.author not in AUTHORS: 238fd4e5da5Sopenharmony_ci print('error: --update argument must be in the AUTHORS list in ' 239fd4e5da5Sopenharmony_ci 'check_copyright.py: {}'.format(AUTHORS)) 240fd4e5da5Sopenharmony_ci sys.exit(1) 241fd4e5da5Sopenharmony_ci for pair in glob_comment_pairs: 242fd4e5da5Sopenharmony_ci insert_copyright(args.author, *pair) 243fd4e5da5Sopenharmony_ci sys.exit(0) 244fd4e5da5Sopenharmony_ci else: 245fd4e5da5Sopenharmony_ci count = sum([alert_if_no_copyright(*p) for p in glob_comment_pairs]) 246fd4e5da5Sopenharmony_ci sys.exit(count > 0) 247fd4e5da5Sopenharmony_ci 248fd4e5da5Sopenharmony_ci 249fd4e5da5Sopenharmony_ciif __name__ == '__main__': 250fd4e5da5Sopenharmony_ci main() 251