1#! /usr/bin/env python3 2 3import sys 4import re 5import argparse 6 7# partially copied from tools/repohooks/rh/hooks.py 8 9TEST_MSG = """Commit message is missing a "Flag:" line. It must match one of the 10following case-sensitive regex: 11 12 %s 13 14The Flag: stanza is regex matched and should describe whether your change is behind a flag or flags. 15As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the flag. 16For legacy flags use EXEMPT with your flag name. 17 18Some examples below: 19 20Flag: NONE Repohook Update 21Flag: TEST_ONLY 22Flag: EXEMPT resource only update 23Flag: EXEMPT bugfix 24Flag: EXEMPT refactor 25Flag: com.android.launcher3.enable_twoline_allapps 26Flag: com.google.android.apps.nexuslauncher.zero_state_web_data_loader 27 28Check the git history for more examples. It's a regex matched field. See go/android-flag-directive for more details on various formats. 29""" 30 31def main(): 32 """Check the commit message for a 'Flag:' line.""" 33 parser = argparse.ArgumentParser( 34 description='Check the commit message for a Flag: line.') 35 parser.add_argument('--msg', 36 metavar='msg', 37 type=str, 38 nargs='?', 39 default='HEAD', 40 help='commit message to process.') 41 parser.add_argument( 42 '--files', 43 metavar='files', 44 nargs='?', 45 default='', 46 help= 47 'PREUPLOAD_FILES in repo upload to determine whether the check should run for the files.') 48 parser.add_argument( 49 '--project', 50 metavar='project', 51 type=str, 52 nargs='?', 53 default='', 54 help= 55 'REPO_PROJECT in repo upload to determine whether the check should run for this project.') 56 57 # Parse the arguments 58 args = parser.parse_args() 59 desc = args.msg 60 files = args.files 61 project = args.project 62 63 if not should_run_path(project, files): 64 return 65 66 field = 'Flag' 67 none = 'NONE' 68 testOnly = 'TEST_ONLY' 69 docsOnly = 'DOCS_ONLY' 70 exempt = 'EXEMPT' 71 justification = '<justification>' 72 73 # Aconfig Flag name format = <packageName>.<flagName> 74 # package name - Contains only lowercase alphabets + digits + '.' - Ex: com.android.launcher3 75 # For now alphabets, digits, "_", "." characters are allowed in flag name. 76 # Checks if there is "one dot" between packageName and flagName and not adding stricter format check 77 #common_typos_disable 78 flagName = '([a-zA-Z0-9.]+)([.]+)([a-zA-Z0-9_.]+)' 79 80 # None and Exempt needs justification 81 exemptRegex = fr'{exempt}\s*[a-zA-Z]+' 82 noneRegex = fr'{none}\s*[a-zA-Z]+' 83 #common_typos_enable 84 85 readableRegexMsg = '\n\tFlag: '+none+' '+justification+'\n\tFlag: <packageName>.<flagName>\n\tFlag: ' +exempt+' '+justification+'\n\tFlag: '+testOnly+'\n\tFlag: '+docsOnly 86 87 flagRegex = fr'^{field}: .*$' 88 check_flag = re.compile(flagRegex) #Flag: 89 90 # Ignore case for flag name format. 91 flagNameRegex = fr'(?i)^{field}:\s*({noneRegex}|{flagName}|{testOnly}|{docsOnly}|{exemptRegex})\s*' 92 check_flagName = re.compile(flagNameRegex) #Flag: <flag name format> 93 94 flagError = False 95 foundFlag = [] 96 # Check for multiple "Flag:" lines and all lines should match this format 97 for line in desc.splitlines(): 98 if check_flag.match(line): 99 if not check_flagName.match(line): 100 flagError = True 101 break 102 foundFlag.append(line) 103 104 # Throw error if 105 # 1. No "Flag:" line is found 106 # 2. "Flag:" doesn't follow right format. 107 if (not foundFlag) or (flagError): 108 error = TEST_MSG % (readableRegexMsg) 109 print(error) 110 sys.exit(1) 111 112 sys.exit(0) 113 114 115def should_run_path(project, files): 116 """Returns a boolean if this check should run with these paths. 117 If you want to check for a particular subdirectory under the path, 118 add a check here, call should_run_files and check for a specific sub dir path in should_run_files. 119 """ 120 if not project: 121 return False 122 if project == 'platform/frameworks/base': 123 return should_run_files(files) 124 # Default case, run for all other projects which calls this script. 125 return True 126 127 128def should_run_files(files): 129 """Returns a boolean if this check should run with these files.""" 130 if not files: 131 return False 132 if 'packages/SystemUI' in files: 133 return True 134 return False 135 136 137if __name__ == '__main__': 138 main() 139