blob: 5943aa7e47c9c838d7e1cb5f97b5c1c013a9b81d [file] [log] [blame]
Simon Glass22ce6412023-11-04 10:25:20 -06001# SPDX-License-Identifier: GPL-2.0+
2#
3# Copyright 2023 Google LLC
4#
5
6"""Handles parsing of buildman arguments
7
8This creates the argument parser and uses it to parse the arguments passed in
9"""
10
11import argparse
12import os
13import pathlib
14import sys
15
Simon Glassba1b3b92025-02-09 14:26:00 -070016from u_boot_pylib import gitutil
Simon Glass44ecdd42025-05-08 06:21:33 +020017from patman import project
Simon Glass22ce6412023-11-04 10:25:20 -060018from patman import settings
19
20PATMAN_DIR = pathlib.Path(__file__).parent
21HAS_TESTS = os.path.exists(PATMAN_DIR / "func_test.py")
22
Simon Glass22ce6412023-11-04 10:25:20 -060023
Simon Glassd9ec7472025-05-10 13:05:07 +020024class ErrorCatchingArgumentParser(argparse.ArgumentParser):
25 def __init__(self, **kwargs):
26 self.exit_state = None
27 self.catch_error = False
28 super().__init__(**kwargs)
29
30 def error(self, message):
31 if self.catch_error:
32 self.message = message
33 else:
34 super().error(message)
35
36 def exit(self, status=0, message=None):
37 if self.catch_error:
38 self.exit_state = True
39 else:
40 super().exit(status, message)
41
42
Simon Glass44ecdd42025-05-08 06:21:33 +020043def add_send_args(par):
44 """Add arguments for the 'send' command
Simon Glass22ce6412023-11-04 10:25:20 -060045
Simon Glass44ecdd42025-05-08 06:21:33 +020046 Arguments:
47 par (ArgumentParser): Parser to add to
Simon Glasse55a11c2025-05-08 06:09:46 +020048 """
Simon Glass44ecdd42025-05-08 06:21:33 +020049 par.add_argument(
50 '-c', '--count', dest='count', type=int, default=-1,
51 help='Automatically create patches from top n commits')
52 par.add_argument(
53 '-e', '--end', type=int, default=0,
54 help='Commits to skip at end of patch list')
55 par.add_argument(
Simon Glasse55a11c2025-05-08 06:09:46 +020056 '-i', '--ignore-errors', action='store_true',
57 dest='ignore_errors', default=False,
58 help='Send patches email even if patch errors are found')
Simon Glass44ecdd42025-05-08 06:21:33 +020059 par.add_argument(
Simon Glasse55a11c2025-05-08 06:09:46 +020060 '-l', '--limit-cc', dest='limit', type=int, default=None,
61 help='Limit the cc list to LIMIT entries [default: %(default)s]')
Simon Glass44ecdd42025-05-08 06:21:33 +020062 par.add_argument(
Simon Glasse55a11c2025-05-08 06:09:46 +020063 '-m', '--no-maintainers', action='store_false',
64 dest='add_maintainers', default=True,
65 help="Don't cc the file maintainers automatically")
Simon Glass0e43cad2025-05-10 13:04:55 +020066 default_arg = None
67 top_level = gitutil.get_top_level()
68 if top_level:
69 default_arg = os.path.join(top_level, 'scripts',
70 'get_maintainer.pl') + ' --norolestats'
Simon Glass44ecdd42025-05-08 06:21:33 +020071 par.add_argument(
Simon Glass22ce6412023-11-04 10:25:20 -060072 '--get-maintainer-script', dest='get_maintainer_script', type=str,
73 action='store',
Simon Glass0e43cad2025-05-10 13:04:55 +020074 default=default_arg,
Simon Glass22ce6412023-11-04 10:25:20 -060075 help='File name of the get_maintainer.pl (or compatible) script.')
Simon Glass44ecdd42025-05-08 06:21:33 +020076 par.add_argument(
77 '-r', '--in-reply-to', type=str, action='store',
78 help="Message ID that this series is in reply to")
79 par.add_argument(
80 '-s', '--start', dest='start', type=int, default=0,
81 help='Commit to start creating patches from (0 = HEAD)')
82 par.add_argument(
83 '-t', '--ignore-bad-tags', action='store_true', default=False,
84 help='Ignore bad tags / aliases (default=warn)')
85 par.add_argument(
86 '--no-binary', action='store_true', dest='ignore_binary',
87 default=False,
88 help="Do not output contents of changes in binary files")
89 par.add_argument(
90 '--no-check', action='store_false', dest='check_patch', default=True,
91 help="Don't check for patch compliance")
92 par.add_argument(
Simon Glass22ce6412023-11-04 10:25:20 -060093 '--tree', dest='check_patch_use_tree', default=False,
94 action='store_true',
95 help=("Set `tree` to True. If `tree` is False then we'll pass "
96 "'--no-tree' to checkpatch (default: tree=%(default)s)"))
Simon Glass44ecdd42025-05-08 06:21:33 +020097 par.add_argument(
98 '--no-tree', dest='check_patch_use_tree', action='store_false',
99 help="Set `tree` to False")
100 par.add_argument(
Simon Glass22ce6412023-11-04 10:25:20 -0600101 '--no-tags', action='store_false', dest='process_tags', default=True,
102 help="Don't process subject tags as aliases")
Simon Glass44ecdd42025-05-08 06:21:33 +0200103 par.add_argument(
104 '--no-signoff', action='store_false', dest='add_signoff',
105 default=True, help="Don't add Signed-off-by to patches")
106 par.add_argument(
107 '--smtp-server', type=str,
108 help="Specify the SMTP server to 'git send-email'")
109 par.add_argument(
110 '--keep-change-id', action='store_true',
111 help='Preserve Change-Id tags in patches to send.')
Simon Glass22ce6412023-11-04 10:25:20 -0600112
Simon Glass44ecdd42025-05-08 06:21:33 +0200113
Simon Glasse9f14cd2025-05-10 13:05:08 +0200114def _add_show_comments(parser):
115 parser.add_argument('-c', '--show-comments', action='store_true',
116 help='Show comments from each patch')
117
118
Simon Glassbe7e6c02025-05-10 13:05:12 +0200119def add_patchwork_subparser(subparsers):
120 """Add the 'patchwork' subparser
121
122 Args:
123 subparsers (argparse action): Subparser parent
124
125 Return:
126 ArgumentParser: patchwork subparser
127 """
128 patchwork = subparsers.add_parser(
129 'patchwork',
130 help='Manage patchwork connection')
131 patchwork.defaults_cmds = [
132 ['set-project', 'U-Boot'],
133 ]
134 patchwork_subparsers = patchwork.add_subparsers(dest='subcmd')
135 patchwork_subparsers.add_parser('get-project')
136 uset = patchwork_subparsers.add_parser('set-project')
137 uset.add_argument(
138 'project_name', help="Patchwork project name, e.g. 'U-Boot'")
139 return patchwork
140
141
Simon Glass44ecdd42025-05-08 06:21:33 +0200142def add_send_subparser(subparsers):
143 """Add the 'send' subparser
144
145 Args:
146 subparsers (argparse action): Subparser parent
147
148 Return:
149 ArgumentParser: send subparser
150 """
151 send = subparsers.add_parser(
152 'send', help='Format, check and email patches (default command)')
153 send.add_argument(
154 '-b', '--branch', type=str,
155 help="Branch to process (by default, the current branch)")
156 send.add_argument(
157 '-n', '--dry-run', action='store_true', dest='dry_run',
158 default=False, help="Do a dry run (create but don't email patches)")
159 send.add_argument(
160 '--cc-cmd', dest='cc_cmd', type=str, action='store',
161 default=None, help='Output cc list for patch file (used by git)')
162 add_send_args(send)
Simon Glass22ce6412023-11-04 10:25:20 -0600163 send.add_argument('patchfiles', nargs='*')
Simon Glasse55a11c2025-05-08 06:09:46 +0200164 return send
Simon Glass22ce6412023-11-04 10:25:20 -0600165
Simon Glasse55a11c2025-05-08 06:09:46 +0200166
167def add_status_subparser(subparsers):
168 """Add the 'status' subparser
169
170 Args:
171 subparsers (argparse action): Subparser parent
Simon Glass22ce6412023-11-04 10:25:20 -0600172
Simon Glasse55a11c2025-05-08 06:09:46 +0200173 Return:
174 ArgumentParser: status subparser
175 """
Simon Glass22ce6412023-11-04 10:25:20 -0600176 status = subparsers.add_parser('status',
177 help='Check status of patches in patchwork')
Simon Glasse9f14cd2025-05-10 13:05:08 +0200178 _add_show_comments(status)
Simon Glass22ce6412023-11-04 10:25:20 -0600179 status.add_argument(
180 '-d', '--dest-branch', type=str,
181 help='Name of branch to create with collected responses')
182 status.add_argument('-f', '--force', action='store_true',
183 help='Force overwriting an existing branch')
Simon Glass802eeea2025-04-29 07:22:25 -0600184 status.add_argument('-T', '--single-thread', action='store_true',
185 help='Disable multithreading when reading patchwork')
Simon Glasse55a11c2025-05-08 06:09:46 +0200186 return status
187
188
189def setup_parser():
190 """Set up command-line parser
191
192 Returns:
193 argparse.Parser object
194 """
195 epilog = '''Create patches from commits in a branch, check them and email
196 them as specified by tags you place in the commits. Use -n to do a dry
197 run first.'''
198
Simon Glassd9ec7472025-05-10 13:05:07 +0200199 parser = ErrorCatchingArgumentParser(epilog=epilog)
Simon Glass44ecdd42025-05-08 06:21:33 +0200200 parser.add_argument(
201 '-D', '--debug', action='store_true',
Simon Glasse55a11c2025-05-08 06:09:46 +0200202 help='Enabling debugging (provides a full traceback on error)')
203 parser.add_argument(
204 '-N', '--no-capture', action='store_true',
205 help='Disable capturing of console output in tests')
206 parser.add_argument('-p', '--project', default=project.detect_project(),
207 help="Project name; affects default option values and "
208 "aliases [default: %(default)s]")
209 parser.add_argument('-P', '--patchwork-url',
210 default='https://patchwork.ozlabs.org',
211 help='URL of patchwork server [default: %(default)s]')
Simon Glass44ecdd42025-05-08 06:21:33 +0200212 parser.add_argument(
213 '-T', '--thread', action='store_true', dest='thread',
214 default=False, help='Create patches as a single thread')
Simon Glasse55a11c2025-05-08 06:09:46 +0200215 parser.add_argument(
216 '-v', '--verbose', action='store_true', dest='verbose', default=False,
217 help='Verbose output of errors and warnings')
218 parser.add_argument(
219 '-X', '--test-preserve-dirs', action='store_true',
220 help='Preserve and display test-created directories')
221 parser.add_argument(
222 '-H', '--full-help', action='store_true', dest='full_help',
223 default=False, help='Display the README file')
224
225 subparsers = parser.add_subparsers(dest='cmd')
226 add_send_subparser(subparsers)
Simon Glassbe7e6c02025-05-10 13:05:12 +0200227 patchwork = add_patchwork_subparser(subparsers)
Simon Glasse55a11c2025-05-08 06:09:46 +0200228 add_status_subparser(subparsers)
229
230 # Only add the 'test' action if the test data files are available.
231 if HAS_TESTS:
232 test_parser = subparsers.add_parser('test', help='Run tests')
233 test_parser.add_argument('testname', type=str, default=None, nargs='?',
234 help="Specify the test to run")
235
Simon Glass65a45792025-05-10 13:05:09 +0200236 parsers = {
237 'main': parser,
Simon Glassbe7e6c02025-05-10 13:05:12 +0200238 'patchwork': patchwork,
Simon Glass65a45792025-05-10 13:05:09 +0200239 }
240 return parsers
Simon Glass8b521cf2025-05-08 05:58:10 +0200241
242
Simon Glass65a45792025-05-10 13:05:09 +0200243def parse_args(argv=None, config_fname=None, parsers=None):
Simon Glass8b521cf2025-05-08 05:58:10 +0200244 """Parse command line arguments from sys.argv[]
245
246 Args:
247 argv (str or None): Arguments to process, or None to use sys.argv[1:]
248 config_fname (str): Config file to read, or None for default, or False
249 for an empty config
250
251 Returns:
252 tuple containing:
253 options: command line options
254 args: command lin arguments
255 """
Simon Glass65a45792025-05-10 13:05:09 +0200256 if not parsers:
257 parsers = setup_parser()
258 parser = parsers['main']
Simon Glass22ce6412023-11-04 10:25:20 -0600259
260 # Parse options twice: first to get the project and second to handle
261 # defaults properly (which depends on project)
262 # Use parse_known_args() in case 'cmd' is omitted
Simon Glass8b521cf2025-05-08 05:58:10 +0200263 if not argv:
264 argv = sys.argv[1:]
265
Simon Glass22ce6412023-11-04 10:25:20 -0600266 args, rest = parser.parse_known_args(argv)
267 if hasattr(args, 'project'):
Simon Glass8b521cf2025-05-08 05:58:10 +0200268 settings.Setup(parser, args.project, argv, config_fname)
Simon Glass22ce6412023-11-04 10:25:20 -0600269 args, rest = parser.parse_known_args(argv)
270
271 # If we have a command, it is safe to parse all arguments
272 if args.cmd:
273 args = parser.parse_args(argv)
Simon Glassb8a14f92025-05-08 06:30:14 +0200274 elif not args.full_help:
Simon Glass22ce6412023-11-04 10:25:20 -0600275 # No command, so insert it after the known arguments and before the ones
276 # that presumably relate to the 'send' subcommand
277 nargs = len(rest)
278 argv = argv[:-nargs] + ['send'] + rest
279 args = parser.parse_args(argv)
280
281 return args