| #! /usr/bin/env python3 | |
| import sys | |
| import re | |
| import argparse | |
| # partially copied from tools/repohooks/rh/hooks.py | |
| TEST_MSG = """Commit message is missing a "Flag:" line. It must match one of the | |
| following case-sensitive regex: | |
| %s | |
| The Flag: stanza is regex matched and should describe whether your change is behind a flag or flags. | |
| As 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 in addition to its state (ENABLED|DISABLED|DEVELOPMENT|STAGING|TEAMFOOD|TRUNKFOOD|NEXTFOOD). | |
| Some examples below: | |
| Flag: NONE | |
| Flag: NA | |
| Flag: LEGACY ENABLE_ONE_SEARCH DISABLED | |
| Flag: ACONFIG com.android.launcher3.enable_twoline_allapps DEVELOPMENT | |
| Flag: ACONFIG com.android.launcher3.enable_twoline_allapps TRUNKFOOD | |
| Check the git history for more examples. It's a regex matched field. | |
| """ | |
| def main(): | |
| """Check the commit message for a 'Flag:' line.""" | |
| parser = argparse.ArgumentParser( | |
| description='Check the commit message for a Flag: line.') | |
| parser.add_argument('--msg', | |
| metavar='msg', | |
| type=str, | |
| nargs='?', | |
| default='HEAD', | |
| help='commit message to process.') | |
| parser.add_argument( | |
| '--files', | |
| metavar='files', | |
| nargs='?', | |
| default='', | |
| help= | |
| 'PREUPLOAD_FILES in repo upload to determine whether the check should run for the files.') | |
| parser.add_argument( | |
| '--project', | |
| metavar='project', | |
| type=str, | |
| nargs='?', | |
| default='', | |
| help= | |
| 'REPO_PATH in repo upload to determine whether the check should run for this project.') | |
| # Parse the arguments | |
| args = parser.parse_args() | |
| desc = args.msg | |
| files = args.files | |
| project = args.project | |
| if not should_run_path(project, files): | |
| return | |
| field = 'Flag' | |
| none = '(NONE|NA|N\/A)' # NONE|NA|N/A | |
| typeExpression = '\s*(LEGACY|ACONFIG)' # [type:LEGACY|ACONFIG] | |
| # legacyFlagName contains only uppercase alphabets with '_' - Ex: ENABLE_ONE_SEARCH | |
| # Aconfig Flag name format = "packageName"."flagName" | |
| # package name - Contains only lowercase alphabets + digits + '.' - Ex: com.android.launcher3 | |
| # For now alphabets, digits, "_", "." characters are allowed in flag name and not adding stricter format check. | |
| #common_typos_disable | |
| flagName = '([a-zA-z0-9_.])+' | |
| #[state:ENABLED|DISABLED|DEVELOPMENT|TEAM*(TEAMFOOD)|STAGING|TRUNK*(TRUNK_STAGING, TRUNK_FOOD)|NEXT*(NEXTFOOD)] | |
| stateExpression = '\s*(ENABLED|DISABLED|DEVELOPMENT|TEAM[a-zA-z]*|STAGING|TRUNK[a-zA-z]*|NEXT[a-zA-z]*)' | |
| #common_typos_enable | |
| readableRegexMsg = '\n\tFlag: (NONE|NA)\n\tFlag: LEGACY|ACONFIG FlagName|packageName.flagName ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|STAGING|TRUNKFOOD|NEXTFOOD' | |
| flagRegex = fr'^{field}: .*$' | |
| check_flag = re.compile(flagRegex) #Flag: | |
| # Ignore case for flag name format. | |
| flagNameRegex = fr'(?i)^{field}:\s*({none}|{typeExpression}\s*{flagName}\s*{stateExpression})\s*' | |
| check_flagName = re.compile(flagNameRegex) #Flag: <flag name format> | |
| flagError = False | |
| foundFlag = [] | |
| # Check for multiple "Flag:" lines and all lines should match this format | |
| for line in desc.splitlines(): | |
| if check_flag.match(line): | |
| if not check_flagName.match(line): | |
| flagError = True | |
| break | |
| foundFlag.append(line) | |
| # Throw error if | |
| # 1. No "Flag:" line is found | |
| # 2. "Flag:" doesn't follow right format. | |
| if (not foundFlag) or (flagError): | |
| error = TEST_MSG % (readableRegexMsg) | |
| print(error) | |
| sys.exit(1) | |
| sys.exit(0) | |
| def should_run_path(path, files): | |
| """Returns a boolean if this check should run with these paths. | |
| If you want to check for a particular subdirectory under the path, | |
| add a check here, call should_run_files and check for a specific sub dir path in should_run_files. | |
| """ | |
| if not path: | |
| return False | |
| if path == 'frameworks/base': | |
| return should_run_files(files) | |
| # Default case, run for all other paths which calls this script. | |
| return True | |
| def should_run_files(files): | |
| """Returns a boolean if this check should run with these files.""" | |
| if not files: | |
| return False | |
| if 'packages/SystemUI' in files: | |
| return True | |
| return False | |
| if __name__ == '__main__': | |
| main() |