| #! /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. |
| For legacy flags use EXEMPT with your flag name. |
| |
| Some examples below: |
| |
| Flag: NONE Repohook Update |
| Flag: TEST_ONLY |
| Flag: EXEMPT resource only update |
| Flag: EXEMPT bugfix |
| Flag: EXEMPT refactor |
| Flag: com.android.launcher3.enable_twoline_allapps |
| Flag: com.google.android.apps.nexuslauncher.zero_state_web_data_loader |
| |
| Check the git history for more examples. It's a regex matched field. See go/android-flag-directive for more details on various formats. |
| """ |
| |
| 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_PROJECT 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' |
| testOnly = 'TEST_ONLY' |
| docsOnly = 'DOCS_ONLY' |
| exempt = 'EXEMPT' |
| justification = '<justification>' |
| |
| # 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. |
| # Checks if there is "one dot" between packageName and flagName and not adding stricter format check |
| #common_typos_disable |
| flagName = '([a-zA-Z0-9.]+)([.]+)([a-zA-Z0-9_.]+)' |
| |
| # None and Exempt needs justification |
| exemptRegex = fr'{exempt}\s*[a-zA-Z]+' |
| noneRegex = fr'{none}\s*[a-zA-Z]+' |
| #common_typos_enable |
| |
| readableRegexMsg = '\n\tFlag: '+none+' '+justification+'\n\tFlag: <packageName>.<flagName>\n\tFlag: ' +exempt+' '+justification+'\n\tFlag: '+testOnly+'\n\tFlag: '+docsOnly |
| |
| flagRegex = fr'^{field}: .*$' |
| check_flag = re.compile(flagRegex) #Flag: |
| |
| # Ignore case for flag name format. |
| flagNameRegex = fr'(?i)^{field}:\s*({noneRegex}|{flagName}|{testOnly}|{docsOnly}|{exemptRegex})\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(project, 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 project: |
| return False |
| if project == 'platform/frameworks/base': |
| return should_run_files(files) |
| # Default case, run for all other projects 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() |