blob: d78ef5a5f1bf5d69c8e9104cafe8e4070a6f9ec0 [file] [log] [blame]
Anushree Ganjamda9c6242023-10-19 14:33:35 -07001#! /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.
Anushree Ganjamdc48e5a2024-05-14 13:49:42 -070015As 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.
Anushree Ganjamda9c6242023-10-19 14:33:35 -070017
18Some examples below:
19
Anushree Ganjamdc48e5a2024-05-14 13:49:42 -070020Flag: 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
Anushree Ganjamda9c6242023-10-19 14:33:35 -070027
Anushree Ganjamdc48e5a2024-05-14 13:49:42 -070028Check the git history for more examples. It's a regex matched field. See go/android-flag-directive for more details on various formats.
Anushree Ganjamda9c6242023-10-19 14:33:35 -070029"""
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=
Adrian Roos81b2c9b2024-05-17 12:04:16 +020055 'REPO_PROJECT in repo upload to determine whether the check should run for this project.')
Anushree Ganjamda9c6242023-10-19 14:33:35 -070056
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'
Anushree Ganjamdc48e5a2024-05-14 13:49:42 -070067 none = 'NONE'
68 testOnly = 'TEST_ONLY'
69 docsOnly = 'DOCS_ONLY'
70 exempt = 'EXEMPT'
71 justification = '<justification>'
Anushree Ganjamda9c6242023-10-19 14:33:35 -070072
Anushree Ganjamdc48e5a2024-05-14 13:49:42 -070073 # Aconfig Flag name format = <packageName>.<flagName>
Anushree Ganjamda9c6242023-10-19 14:33:35 -070074 # package name - Contains only lowercase alphabets + digits + '.' - Ex: com.android.launcher3
Anushree Ganjamdc48e5a2024-05-14 13:49:42 -070075 # 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
Anushree Ganjamda9c6242023-10-19 14:33:35 -070077 #common_typos_disable
Anushree Ganjamdc48e5a2024-05-14 13:49:42 -070078 flagName = '([a-zA-Z0-9.]+)([.]+)([a-zA-Z0-9_.]+)'
Anushree Ganjamda9c6242023-10-19 14:33:35 -070079
Anushree Ganjamdc48e5a2024-05-14 13:49:42 -070080 # None and Exempt needs justification
81 exemptRegex = fr'{exempt}\s*[a-zA-Z]+'
82 noneRegex = fr'{none}\s*[a-zA-Z]+'
Anushree Ganjamda9c6242023-10-19 14:33:35 -070083 #common_typos_enable
84
Anushree Ganjamdc48e5a2024-05-14 13:49:42 -070085 readableRegexMsg = '\n\tFlag: '+none+' '+justification+'\n\tFlag: <packageName>.<flagName>\n\tFlag: ' +exempt+' '+justification+'\n\tFlag: '+testOnly+'\n\tFlag: '+docsOnly
Anushree Ganjamda9c6242023-10-19 14:33:35 -070086
87 flagRegex = fr'^{field}: .*$'
88 check_flag = re.compile(flagRegex) #Flag:
89
90 # Ignore case for flag name format.
Anushree Ganjamdc48e5a2024-05-14 13:49:42 -070091 flagNameRegex = fr'(?i)^{field}:\s*({noneRegex}|{flagName}|{testOnly}|{docsOnly}|{exemptRegex})\s*'
Anushree Ganjamda9c6242023-10-19 14:33:35 -070092 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
Adrian Roos81b2c9b2024-05-17 12:04:16 +0200115def should_run_path(project, files):
Anushree Ganjamda9c6242023-10-19 14:33:35 -0700116 """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 """
Adrian Roos81b2c9b2024-05-17 12:04:16 +0200120 if not project:
Anushree Ganjamda9c6242023-10-19 14:33:35 -0700121 return False
Adrian Roos81b2c9b2024-05-17 12:04:16 +0200122 if project == 'platform/frameworks/base':
Anushree Ganjamda9c6242023-10-19 14:33:35 -0700123 return should_run_files(files)
Adrian Roos81b2c9b2024-05-17 12:04:16 +0200124 # Default case, run for all other projects which calls this script.
Anushree Ganjamda9c6242023-10-19 14:33:35 -0700125 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()