Anushree Ganjam | da9c624 | 2023-10-19 14:33:35 -0700 | [diff] [blame^] | 1 | #! /usr/bin/env python3 |
| 2 | |
| 3 | import sys |
| 4 | import re |
| 5 | import argparse |
| 6 | |
| 7 | # partially copied from tools/repohooks/rh/hooks.py |
| 8 | |
| 9 | TEST_MSG = """Commit message is missing a "Flag:" line. It must match one of the |
| 10 | following case-sensitive regex: |
| 11 | |
| 12 | %s |
| 13 | |
| 14 | The Flag: stanza is regex matched and should describe whether your change is behind a flag or flags. |
| 15 | |
| 16 | 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 |
| 17 | flag in addition to its state (ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|TRUNKFOOD|NEXTFOOD). |
| 18 | |
| 19 | Some examples below: |
| 20 | |
| 21 | Flag: NONE |
| 22 | Flag: NA |
| 23 | Flag: LEGACY ENABLE_ONE_SEARCH DISABLED |
| 24 | Flag: ACONFIG com.android.launcher3.enable_twoline_allapps DEVELOPMENT |
| 25 | Flag: ACONFIG com.android.launcher3.enable_twoline_allapps TRUNKFOOD |
| 26 | |
| 27 | Check the git history for more examples. It's a regex matched field. |
| 28 | """ |
| 29 | |
| 30 | def main(): |
| 31 | """Check the commit message for a 'Flag:' line.""" |
| 32 | parser = argparse.ArgumentParser( |
| 33 | description='Check the commit message for a Flag: line.') |
| 34 | parser.add_argument('--msg', |
| 35 | metavar='msg', |
| 36 | type=str, |
| 37 | nargs='?', |
| 38 | default='HEAD', |
| 39 | help='commit message to process.') |
| 40 | parser.add_argument( |
| 41 | '--files', |
| 42 | metavar='files', |
| 43 | nargs='?', |
| 44 | default='', |
| 45 | help= |
| 46 | 'PREUPLOAD_FILES in repo upload to determine whether the check should run for the files.') |
| 47 | parser.add_argument( |
| 48 | '--project', |
| 49 | metavar='project', |
| 50 | type=str, |
| 51 | nargs='?', |
| 52 | default='', |
| 53 | help= |
| 54 | 'REPO_PATH in repo upload to determine whether the check should run for this project.') |
| 55 | |
| 56 | # Parse the arguments |
| 57 | args = parser.parse_args() |
| 58 | desc = args.msg |
| 59 | files = args.files |
| 60 | project = args.project |
| 61 | |
| 62 | if not should_run_path(project, files): |
| 63 | return |
| 64 | |
| 65 | field = 'Flag' |
| 66 | none = '(NONE|NA|N\/A)' # NONE|NA|N/A |
| 67 | |
| 68 | typeExpression = '\s*(LEGACY|ACONFIG)' # [type:LEGACY|ACONFIG] |
| 69 | |
| 70 | # legacyFlagName contains only uppercase alphabets with '_' - Ex: ENABLE_ONE_SEARCH |
| 71 | # Aconfig Flag name format = "packageName"."flagName" |
| 72 | # package name - Contains only lowercase alphabets + digits + '.' - Ex: com.android.launcher3 |
| 73 | # For now alphabets, digits, "_", "." characters are allowed in flag name and not adding stricter format check. |
| 74 | #common_typos_disable |
| 75 | flagName = '([a-zA-z0-9_.])+' |
| 76 | |
| 77 | #[state:ENABLED|DISABLED|DEVELOPMENT|TEAM*(TEAMFOOD)|TRUNK*(TRUNK_STAGING, TRUNK_FOOD)|NEXT*(NEXTFOOD)] |
| 78 | stateExpression = '\s*(ENABLED|DISABLED|DEVELOPMENT|TEAM[a-zA-z]*|TRUNK[a-zA-z]*|NEXT[a-zA-z]*)' |
| 79 | #common_typos_enable |
| 80 | |
| 81 | readableRegexMsg = '\n\tFlag: (NONE|NA)\n\tFlag: LEGACY|ACONFIG FlagName|packageName.flagName ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|TRUNKFOOD|NEXTFOOD' |
| 82 | |
| 83 | flagRegex = fr'^{field}: .*$' |
| 84 | check_flag = re.compile(flagRegex) #Flag: |
| 85 | |
| 86 | # Ignore case for flag name format. |
| 87 | flagNameRegex = fr'(?i)^{field}:\s*({none}|{typeExpression}\s*{flagName}\s*{stateExpression})\s*' |
| 88 | check_flagName = re.compile(flagNameRegex) #Flag: <flag name format> |
| 89 | |
| 90 | flagError = False |
| 91 | foundFlag = [] |
| 92 | # Check for multiple "Flag:" lines and all lines should match this format |
| 93 | for line in desc.splitlines(): |
| 94 | if check_flag.match(line): |
| 95 | if not check_flagName.match(line): |
| 96 | flagError = True |
| 97 | break |
| 98 | foundFlag.append(line) |
| 99 | |
| 100 | # Throw error if |
| 101 | # 1. No "Flag:" line is found |
| 102 | # 2. "Flag:" doesn't follow right format. |
| 103 | if (not foundFlag) or (flagError): |
| 104 | error = TEST_MSG % (readableRegexMsg) |
| 105 | print(error) |
| 106 | sys.exit(1) |
| 107 | |
| 108 | sys.exit(0) |
| 109 | |
| 110 | |
| 111 | def should_run_path(path, files): |
| 112 | """Returns a boolean if this check should run with these paths. |
| 113 | If you want to check for a particular subdirectory under the path, |
| 114 | add a check here, call should_run_files and check for a specific sub dir path in should_run_files. |
| 115 | """ |
| 116 | if not path: |
| 117 | return False |
| 118 | if path == 'frameworks/base': |
| 119 | return should_run_files(files) |
| 120 | # Default case, run for all other paths which calls this script. |
| 121 | return True |
| 122 | |
| 123 | |
| 124 | def should_run_files(files): |
| 125 | """Returns a boolean if this check should run with these files.""" |
| 126 | if not files: |
| 127 | return False |
| 128 | if 'packages/SystemUI' in files: |
| 129 | return True |
| 130 | return False |
| 131 | |
| 132 | |
| 133 | if __name__ == '__main__': |
| 134 | main() |