Merge tag 'dm-pull-15nov23' of https://source.denx.de/u-boot/custodians/u-boot-dm
authorTom Rini <trini@konsulko.com>
Wed, 15 Nov 2023 19:15:21 +0000 (14:15 -0500)
committerTom Rini <trini@konsulko.com>
Wed, 15 Nov 2023 19:15:21 +0000 (14:15 -0500)
patman correct import of u_boot_pylib
correct long-standing EFI framebuffer bug
minor test refactor

12 files changed:
arch/sandbox/cpu/os.c
boot/expo.c
cmd/bootflow.c
common/bootstage.c
doc/usage/cmd/bootflow.rst
drivers/core/Kconfig
include/dm/util.h
include/video.h
lib/efi_loader/efi_gop.c
test/boot/bootflow.c
tools/patman/__main__.py
tools/patman/cmdline.py [new file with mode: 0644]

index 85d0d6a..95c26d8 100644 (file)
@@ -219,7 +219,7 @@ int os_map_file(const char *pathname, int os_flags, void **bufp, int *sizep)
 {
        void *ptr;
        off_t size;
-       int ifd;
+       int ifd, ret = 0;
 
        ifd = os_open(pathname, os_flags);
        if (ifd < 0) {
@@ -229,23 +229,28 @@ int os_map_file(const char *pathname, int os_flags, void **bufp, int *sizep)
        size = os_filesize(ifd);
        if (size < 0) {
                printf("Cannot get file size of '%s'\n", pathname);
-               return -EIO;
+               ret = -EIO;
+               goto out;
        }
        if ((unsigned long long)size > (unsigned long long)SIZE_MAX) {
                printf("File '%s' too large to map\n", pathname);
-               return -EIO;
+               ret = -EIO;
+               goto out;
        }
 
        ptr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, ifd, 0);
        if (ptr == MAP_FAILED) {
                printf("Can't map file '%s': %s\n", pathname, strerror(errno));
-               return -EPERM;
+               ret = -EPERM;
+               goto out;
        }
 
        *bufp = ptr;
        *sizep = size;
 
-       return 0;
+out:
+       os_close(ifd);
+       return ret;
 }
 
 int os_unmap(void *buf, int size)
index 139d684..cadb6a0 100644 (file)
@@ -190,10 +190,12 @@ int expo_render(struct expo *exp)
        struct udevice *dev = exp->display;
        struct video_priv *vid_priv = dev_get_uclass_priv(dev);
        struct scene *scn = NULL;
+       enum colour_idx back;
        u32 colour;
        int ret;
 
-       colour = video_index_to_colour(vid_priv, VID_WHITE);
+       back = CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK) ? VID_BLACK : VID_WHITE;
+       colour = video_index_to_colour(vid_priv, back);
        ret = video_fill(dev, colour);
        if (ret)
                return log_msg_ret("fill", ret);
index ad39ebe..3aeb40d 100644 (file)
@@ -89,6 +89,44 @@ static void show_footer(int count, int num_valid)
               num_valid);
 }
 
+/**
+ * bootflow_handle_menu() - Handle running the menu and updating cur bootflow
+ *
+ * This shows the menu, allows the user to select something and then prints
+ * what happened
+ *
+ * @std: bootstd information
+ * @text_mode: true to run the menu in text mode
+ * @bflowp: Returns selected bootflow, on success
+ * Return: 0 on success (a bootflow was selected), -EAGAIN if nothing was
+ *     chosen, other -ve value on other error
+ */
+__maybe_unused static int bootflow_handle_menu(struct bootstd_priv *std,
+                                              bool text_mode,
+                                              struct bootflow **bflowp)
+{
+       struct bootflow *bflow;
+       int ret;
+
+       ret = bootflow_menu_run(std, text_mode, &bflow);
+       if (ret) {
+               if (ret == -EAGAIN) {
+                       printf("Nothing chosen\n");
+                       std->cur_bootflow = NULL;
+               } else {
+                       printf("Menu failed (err=%d)\n", ret);
+               }
+
+               return ret;
+       }
+
+       printf("Selected: %s\n", bflow->os_name ? bflow->os_name : bflow->name);
+       std->cur_bootflow = bflow;
+       *bflowp = bflow;
+
+       return 0;
+}
+
 static int do_bootflow_scan(struct cmd_tbl *cmdtp, int flag, int argc,
                            char *const argv[])
 {
@@ -455,18 +493,9 @@ static int do_bootflow_menu(struct cmd_tbl *cmdtp, int flag, int argc,
        if (ret)
                return CMD_RET_FAILURE;
 
-       ret = bootflow_menu_run(std, text_mode, &bflow);
-       if (ret) {
-               if (ret == -EAGAIN)
-                       printf("Nothing chosen\n");
-               else {
-                       printf("Menu failed (err=%d)\n", ret);
-                       return CMD_RET_FAILURE;
-               }
-       }
-
-       printf("Selected: %s\n", bflow->os_name ? bflow->os_name : bflow->name);
-       std->cur_bootflow = bflow;
+       ret = bootflow_handle_menu(std, text_mode, &bflow);
+       if (ret)
+               return CMD_RET_FAILURE;
 
        return 0;
 }
index a68d883..0e6d807 100644 (file)
@@ -137,7 +137,7 @@ ulong bootstage_add_record(enum bootstage_id id, const char *name,
                        rec->flags = flags;
                        rec->id = id;
                } else {
-                       log_warning("Bootstage space exhasuted\n");
+                       log_warning("Bootstage space exhausted\n");
                }
        }
 
index 9c5ea9c..2198ff6 100644 (file)
@@ -15,6 +15,7 @@ Synopis
     bootflow read
     bootflow boot
     bootflow cmdline [set|get|clear|delete|auto] <param> [<value>]
+    bootfloe menu [-t]
 
 Description
 -----------
@@ -24,6 +25,9 @@ locate bootflows, list them and boot them.
 
 See :doc:`../../develop/bootstd` for more information.
 
+Note that `CONFIG_BOOTSTD_FULL` (which enables `CONFIG_CMD_BOOTFLOW_FULL) must
+be enabled to obtain full functionality with this command. Otherwise, it only
+supports `bootflow scan` which scans and boots the first available bootflow.
 
 bootflow scan
 ~~~~~~~~~~~~~
@@ -247,6 +251,16 @@ can be used to set the early console (or console) to a suitable value so that
 output appears on the serial port. This is only supported by the 16550 serial
 driver so far.
 
+bootflow menu
+~~~~~~~~~~~~~
+
+This shows a menu with available bootflows. The user can select a particular
+bootflow, which then becomes the current one.
+
+The `-t` flag requests a text menu. Otherwise, if a display is available, a
+graphical menu is shown.
+
+
 Example
 -------
 
@@ -658,6 +672,56 @@ Now the buffer can be accessed::
     77b7e4e0: 320fc000 08e8ba0f c031300f b8d0000f  ...2.....01.....
     77b7e4f0: 00000020 6ad8000f 00858d10 50000002   ......j.......P
 
+This shows using a text menu to boot an OS::
+
+    => bootflow scan
+    => bootfl list
+    => bootfl menu -t
+    U-Boot    :    Boot Menu
+
+    UP and DOWN to choose, ENTER to select
+
+      >    0  mmc1        mmc1.bootdev.whole
+           1  mmc1        Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)
+           2  mmc1        mmc1.bootdev.part_1
+           3  mmc4        mmc4.bootdev.whole
+           4  mmc4        Armbian
+           5  mmc4        mmc4.bootdev.part_1
+           6  mmc5        mmc5.bootdev.whole
+           7  mmc5        ChromeOS
+           8  mmc5        ChromeOS
+    U-Boot    :    Boot Menu
+
+    UP and DOWN to choose, ENTER to select
+
+           0  mmc1        mmc1.bootdev.whole
+      >    1  mmc1        Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)
+           2  mmc1        mmc1.bootdev.part_1
+           3  mmc4        mmc4.bootdev.whole
+           4  mmc4        Armbian
+           5  mmc4        mmc4.bootdev.part_1
+           6  mmc5        mmc5.bootdev.whole
+           7  mmc5        ChromeOS
+           8  mmc5        ChromeOS
+    U-Boot    :    Boot Menu
+
+    Selected: Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)
+    => bootfl boot
+    ** Booting bootflow 'mmc1.bootdev.part_1' with extlinux
+    Ignoring unknown command: ui
+    Ignoring malformed menu command:  autoboot
+    Ignoring malformed menu command:  hidden
+    Ignoring unknown command: totaltimeout
+    Fedora-Workstation-armhfp-31-1.9 Boot Options.
+    1: Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)
+    Enter choice: 1
+    1: Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)
+    Retrieving file: /vmlinuz-5.3.7-301.fc31.armv7hl
+    Retrieving file: /initramfs-5.3.7-301.fc31.armv7hl.img
+    append: ro root=UUID=9732b35b-4cd5-458b-9b91-80f7047e0b8a rhgb quiet LANG=en_US.UTF-8 cma=192MB cma=256MB
+    Retrieving file: /dtb-5.3.7-301.fc31.armv7hl/sandbox.dtb
+    ...
+
 
 Return value
 ------------
@@ -667,6 +731,9 @@ return to U-Boot. If something about the U-Boot processing fails, then the
 return value $? is 1. If the boot succeeds but for some reason the Operating
 System returns, then $? is 0, indicating success.
 
+For `bootflow menu` the return value is $? is 0 (true) if an option was choses,
+else 1.
+
 For other subcommands, the return value $? is always 0 (true).
 
 
index fe5c41d..737d459 100644 (file)
@@ -48,7 +48,6 @@ config VPL_DM
 config DM_WARN
        bool "Enable warnings in driver model"
        depends on DM
-       default y
        help
          Enable this to see warnings related to driver model.
 
index 89206cc..95c3527 100644 (file)
@@ -11,9 +11,7 @@ struct dm_stats;
 #if CONFIG_IS_ENABLED(DM_WARN)
 #define dm_warn(fmt...) log(LOGC_DM, LOGL_WARNING, ##fmt)
 #else
-static inline void dm_warn(const char *fmt, ...)
-{
-}
+#define dm_warn(fmt...) log(LOGC_DM, LOGL_DEBUG, ##fmt)
 #endif
 
 struct list_head;
index 5048116..4d8df9b 100644 (file)
@@ -21,9 +21,12 @@ struct udevice;
  * @align: Frame-buffer alignment, indicating the memory boundary the frame
  *     buffer should start on. If 0, 1MB is assumed
  * @size: Frame-buffer size, in bytes
- * @base: Base address of frame buffer, 0 if not yet known
- * @copy_base: Base address of a hardware copy of the frame buffer. See
- *     CONFIG_VIDEO_COPY.
+ * @base: Base address of frame buffer, 0 if not yet known. If CONFIG_VIDEO_COPY
+ *     is enabled, this is the software copy, so writes to this will not be
+ *     visible until vidconsole_sync_copy() is called. If CONFIG_VIDEO_COPY is
+ *     disabled, this is the hardware framebuffer.
+ * @copy_base: Base address of a hardware copy of the frame buffer. If
+ *     CONFIG_VIDEO_COPY is disabled, this is not used.
  * @copy_size: Size of copy framebuffer, used if @size is 0
  * @hide_logo: Hide the logo (used for testing)
  */
index 778b693..a09db31 100644 (file)
@@ -10,6 +10,7 @@
 #include <efi_loader.h>
 #include <log.h>
 #include <malloc.h>
+#include <mapmem.h>
 #include <video.h>
 #include <asm/global_data.h>
 
@@ -467,10 +468,10 @@ efi_status_t efi_gop_register(void)
        struct efi_gop_obj *gopobj;
        u32 bpix, format, col, row;
        u64 fb_base, fb_size;
-       void *fb;
        efi_status_t ret;
        struct udevice *vdev;
        struct video_priv *priv;
+       struct video_uc_plat *plat;
 
        /* We only support a single video output device for now */
        if (uclass_first_device_err(UCLASS_VIDEO, &vdev)) {
@@ -483,9 +484,10 @@ efi_status_t efi_gop_register(void)
        format = priv->format;
        col = video_get_xsize(vdev);
        row = video_get_ysize(vdev);
-       fb_base = (uintptr_t)priv->fb;
-       fb_size = priv->fb_size;
-       fb = priv->fb;
+
+       plat = dev_get_uclass_plat(vdev);
+       fb_base = IS_ENABLED(CONFIG_VIDEO_COPY) ? plat->copy_base : plat->base;
+       fb_size = plat->size;
 
        switch (bpix) {
        case VIDEO_BPP16:
@@ -547,7 +549,7 @@ efi_status_t efi_gop_register(void)
        }
        gopobj->info.pixels_per_scanline = col;
        gopobj->bpix = bpix;
-       gopobj->fb = fb;
+       gopobj->fb = map_sysmem(fb_base, fb_size);
 
        return EFI_SUCCESS;
 }
index f640db8..b97c566 100644 (file)
@@ -511,19 +511,27 @@ BOOTSTD_TEST(bootflow_cmd_boot, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
 /**
  * prep_mmc_bootdev() - Set up an mmc bootdev so we can access other distros
  *
+ * After calling this function, set std->bootdev_order to *@old_orderp to
+ * restore normal operation of bootstd (i.e. with the original bootdev order)
+ *
  * @uts: Unit test state
- * @mmc_dev: MMC device to use, e.g. "mmc4"
+ * @mmc_dev: MMC device to use, e.g. "mmc4". Note that this must remain valid
+ *     in the caller until
+ * @bind_cros: true to bind the ChromiumOS bootmeth
+ * @old_orderp: Returns the original bootdev order, which must be restored
  * Returns 0 on success, -ve on failure
  */
 static int prep_mmc_bootdev(struct unit_test_state *uts, const char *mmc_dev,
-                           bool bind_cros)
+                           bool bind_cros, const char ***old_orderp)
 {
-       const char *order[] = {"mmc2", "mmc1", mmc_dev, NULL};
+       static const char *order[] = {"mmc2", "mmc1", NULL, NULL};
        struct udevice *dev, *bootstd;
        struct bootstd_priv *std;
        const char **old_order;
        ofnode root, node;
 
+       order[2] = mmc_dev;
+
        /* Enable the mmc4 node since we need a second bootflow */
        root = oftree_root(oftree_default());
        node = ofnode_find_subnode(root, mmc_dev);
@@ -546,26 +554,49 @@ static int prep_mmc_bootdev(struct unit_test_state *uts, const char *mmc_dev,
        std = dev_get_priv(bootstd);
        old_order = std->bootdev_order;
        std->bootdev_order = order;
+       *old_orderp = old_order;
+
+       return 0;
+}
+
+/**
+ * scan_mmc_bootdev() - Set up an mmc bootdev so we can access other distros
+ *
+ * @uts: Unit test state
+ * @mmc_dev: MMC device to use, e.g. "mmc4"
+ * @bind_cros: true to bind the ChromiumOS bootmeth
+ * Returns 0 on success, -ve on failure
+ */
+static int scan_mmc_bootdev(struct unit_test_state *uts, const char *mmc_dev,
+                           bool bind_cros)
+{
+       struct bootstd_priv *std;
+       struct udevice *bootstd;
+       const char **old_order;
+
+       ut_assertok(prep_mmc_bootdev(uts, mmc_dev, bind_cros, &old_order));
 
        console_record_reset_enable();
        ut_assertok(run_command("bootflow scan", 0));
        ut_assert_console_end();
 
        /* Restore the order used by the device tree */
+       ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd));
+       std = dev_get_priv(bootstd);
        std->bootdev_order = old_order;
 
        return 0;
 }
 
 /**
- * prep_mmc4_bootdev() - Set up the mmc4 bootdev so we can access a fake Armbian
+ * scan_mmc4_bootdev() - Set up the mmc4 bootdev so we can access a fake Armbian
  *
  * @uts: Unit test state
  * Returns 0 on success, -ve on failure
  */
-static int prep_mmc4_bootdev(struct unit_test_state *uts)
+static int scan_mmc4_bootdev(struct unit_test_state *uts)
 {
-       ut_assertok(prep_mmc_bootdev(uts, "mmc4", false));
+       ut_assertok(scan_mmc_bootdev(uts, "mmc4", false));
 
        return 0;
 }
@@ -573,9 +604,13 @@ static int prep_mmc4_bootdev(struct unit_test_state *uts)
 /* Check 'bootflow menu' to select a bootflow */
 static int bootflow_cmd_menu(struct unit_test_state *uts)
 {
+       struct bootstd_priv *std;
        char prev[3];
 
-       ut_assertok(prep_mmc4_bootdev(uts));
+       /* get access to the current bootflow */
+       ut_assertok(bootstd_get_priv(&std));
+
+       ut_assertok(scan_mmc4_bootdev(uts));
 
        /* Add keypresses to move to and select the second one in the list */
        prev[0] = CTL_CH('n');
@@ -585,6 +620,17 @@ static int bootflow_cmd_menu(struct unit_test_state *uts)
 
        ut_assertok(run_command("bootflow menu", 0));
        ut_assert_nextline("Selected: Armbian");
+       ut_assertnonnull(std->cur_bootflow);
+       ut_assert_console_end();
+
+       /* Check not selecting anything */
+       prev[0] = '\e';
+       prev[1] = '\0';
+       ut_asserteq(1, console_in_puts(prev));
+
+       ut_asserteq(1, run_command("bootflow menu", 0));
+       ut_assertnull(std->cur_bootflow);
+       ut_assert_nextline("Nothing chosen");
        ut_assert_console_end();
 
        return 0;
@@ -681,7 +727,7 @@ static int bootflow_menu_theme(struct unit_test_state *uts)
        ofnode node;
        int i;
 
-       ut_assertok(prep_mmc4_bootdev(uts));
+       ut_assertok(scan_mmc4_bootdev(uts));
 
        ut_assertok(bootflow_menu_new(&exp));
        node = ofnode_path("/bootstd/theme");
@@ -996,7 +1042,7 @@ BOOTSTD_TEST(bootflow_cmdline_special, 0);
 /* Test ChromiumOS bootmeth */
 static int bootflow_cros(struct unit_test_state *uts)
 {
-       ut_assertok(prep_mmc_bootdev(uts, "mmc5", true));
+       ut_assertok(scan_mmc_bootdev(uts, "mmc5", true));
        ut_assertok(run_command("bootflow list", 0));
 
        ut_assert_nextlinen("Showing all");
index 197ac1a..f645b38 100755 (executable)
 
 """See README for more information"""
 
-from argparse import ArgumentParser
 try:
-    import importlib.resources
+    from importlib import resources
 except ImportError:
     # for Python 3.6
-    import importlib_resources
+    import importlib_resources as resources
 import os
 import re
 import sys
 import traceback
 
-if __name__ == "__main__":
-    # Allow 'from patman import xxx to work'
-    our_path = os.path.dirname(os.path.realpath(__file__))
-    sys.path.append(os.path.join(our_path, '..'))
+# Allow 'from patman import xxx to work'
+# pylint: disable=C0413
+our_path = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(our_path, '..'))
 
 # Our modules
+from patman import cmdline
 from patman import control
-from patman import func_test
-from patman import gitutil
-from patman import project
-from patman import settings
 from u_boot_pylib import terminal
 from u_boot_pylib import test_util
 from u_boot_pylib import tools
 
-epilog = '''Create patches from commits in a branch, check them and email them
-as specified by tags you place in the commits. Use -n to do a dry run first.'''
-
-parser = ArgumentParser(epilog=epilog)
-parser.add_argument('-b', '--branch', type=str,
-    help="Branch to process (by default, the current branch)")
-parser.add_argument('-c', '--count', dest='count', type=int,
-    default=-1, help='Automatically create patches from top n commits')
-parser.add_argument('-e', '--end', type=int, default=0,
-    help='Commits to skip at end of patch list')
-parser.add_argument('-D', '--debug', action='store_true',
-    help='Enabling debugging (provides a full traceback on error)')
-parser.add_argument('-p', '--project', default=project.detect_project(),
-                    help="Project name; affects default option values and "
-                    "aliases [default: %(default)s]")
-parser.add_argument('-P', '--patchwork-url',
-                    default='https://patchwork.ozlabs.org',
-                    help='URL of patchwork server [default: %(default)s]')
-parser.add_argument('-s', '--start', dest='start', type=int,
-    default=0, help='Commit to start creating patches from (0 = HEAD)')
-parser.add_argument('-v', '--verbose', action='store_true', dest='verbose',
-                    default=False, help='Verbose output of errors and warnings')
-parser.add_argument('-H', '--full-help', action='store_true', dest='full_help',
-                    default=False, help='Display the README file')
-
-subparsers = parser.add_subparsers(dest='cmd')
-send = subparsers.add_parser(
-    'send', help='Format, check and email patches (default command)')
-send.add_argument('-i', '--ignore-errors', action='store_true',
-       dest='ignore_errors', default=False,
-       help='Send patches email even if patch errors are found')
-send.add_argument('-l', '--limit-cc', dest='limit', type=int, default=None,
-       help='Limit the cc list to LIMIT entries [default: %(default)s]')
-send.add_argument('-m', '--no-maintainers', action='store_false',
-       dest='add_maintainers', default=True,
-       help="Don't cc the file maintainers automatically")
-send.add_argument(
-    '--get-maintainer-script', dest='get_maintainer_script', type=str,
-    action='store',
-    default=os.path.join(gitutil.get_top_level(), 'scripts',
-                         'get_maintainer.pl') + ' --norolestats',
-    help='File name of the get_maintainer.pl (or compatible) script.')
-send.add_argument('-n', '--dry-run', action='store_true', dest='dry_run',
-       default=False, help="Do a dry run (create but don't email patches)")
-send.add_argument('-r', '--in-reply-to', type=str, action='store',
-                  help="Message ID that this series is in reply to")
-send.add_argument('-t', '--ignore-bad-tags', action='store_true',
-                  default=False,
-                  help='Ignore bad tags / aliases (default=warn)')
-send.add_argument('-T', '--thread', action='store_true', dest='thread',
-                  default=False, help='Create patches as a single thread')
-send.add_argument('--cc-cmd', dest='cc_cmd', type=str, action='store',
-       default=None, help='Output cc list for patch file (used by git)')
-send.add_argument('--no-binary', action='store_true', dest='ignore_binary',
-                  default=False,
-                  help="Do not output contents of changes in binary files")
-send.add_argument('--no-check', action='store_false', dest='check_patch',
-                  default=True,
-                  help="Don't check for patch compliance")
-send.add_argument('--tree', dest='check_patch_use_tree', default=False,
-                  action='store_true',
-                  help=("Set `tree` to True. If `tree` is False then we'll "
-                  "pass '--no-tree' to checkpatch (default: tree=%(default)s)"))
-send.add_argument('--no-tree', dest='check_patch_use_tree',
-                  action='store_false', help="Set `tree` to False")
-send.add_argument('--no-tags', action='store_false', dest='process_tags',
-                  default=True, help="Don't process subject tags as aliases")
-send.add_argument('--no-signoff', action='store_false', dest='add_signoff',
-                  default=True, help="Don't add Signed-off-by to patches")
-send.add_argument('--smtp-server', type=str,
-                  help="Specify the SMTP server to 'git send-email'")
-send.add_argument('--keep-change-id', action='store_true',
-                  help='Preserve Change-Id tags in patches to send.')
-
-send.add_argument('patchfiles', nargs='*')
-
-# Only add the 'test' action if the test data files are available.
-if os.path.exists(func_test.TEST_DATA_DIR):
-    test_parser = subparsers.add_parser('test', help='Run tests')
-    test_parser.add_argument('testname', type=str, default=None, nargs='?',
-                             help="Specify the test to run")
-
-status = subparsers.add_parser('status',
-                               help='Check status of patches in patchwork')
-status.add_argument('-C', '--show-comments', action='store_true',
-                    help='Show comments from each patch')
-status.add_argument('-d', '--dest-branch', type=str,
-                    help='Name of branch to create with collected responses')
-status.add_argument('-f', '--force', action='store_true',
-                    help='Force overwriting an existing branch')
-
-# Parse options twice: first to get the project and second to handle
-# defaults properly (which depends on project)
-# Use parse_known_args() in case 'cmd' is omitted
-argv = sys.argv[1:]
-args, rest = parser.parse_known_args(argv)
-if hasattr(args, 'project'):
-    settings.Setup(parser, args.project)
-    args, rest = parser.parse_known_args(argv)
-
-# If we have a command, it is safe to parse all arguments
-if args.cmd:
-    args = parser.parse_args(argv)
-else:
-    # No command, so insert it after the known arguments and before the ones
-    # that presumably relate to the 'send' subcommand
-    nargs = len(rest)
-    argv = argv[:-nargs] + ['send'] + rest
-    args = parser.parse_args(argv)
-
-if __name__ != "__main__":
-    pass
-
-if not args.debug:
-    sys.tracebacklimit = 0
-
-# Run our meagre tests
-if args.cmd == 'test':
-    from patman import func_test
-    from patman import test_checkpatch
-
-    result = test_util.run_test_suites(
-        'patman', False, False, False, None, None, None,
-        [test_checkpatch.TestPatch, func_test.TestFunctional,
-         'gitutil', 'settings'])
-
-    sys.exit(0 if result.wasSuccessful() else 1)
-
-# Process commits, produce patches files, check them, email them
-elif args.cmd == 'send':
-    # Called from git with a patch filename as argument
-    # Printout a list of additional CC recipients for this patch
-    if args.cc_cmd:
-        fd = open(args.cc_cmd, 'r')
-        re_line = re.compile('(\S*) (.*)')
-        for line in fd.readlines():
-            match = re_line.match(line)
-            if match and match.group(1) == args.patchfiles[0]:
-                for cc in match.group(2).split('\0'):
-                    cc = cc.strip()
-                    if cc:
-                        print(cc)
-        fd.close()
-
-    elif args.full_help:
-        with importlib.resources.path('patman', 'README.rst') as readme:
-            tools.print_full_help(str(readme))
-    else:
-        # If we are not processing tags, no need to warning about bad ones
-        if not args.process_tags:
-            args.ignore_bad_tags = True
-        control.send(args)
-
-# Check status of patches in patchwork
-elif args.cmd == 'status':
-    ret_code = 0
-    try:
-        control.patchwork_status(args.branch, args.count, args.start, args.end,
-                                 args.dest_branch, args.force,
-                                 args.show_comments, args.patchwork_url)
-    except Exception as e:
-        terminal.tprint('patman: %s: %s' % (type(e).__name__, e),
-                        colour=terminal.Color.RED)
-        if args.debug:
-            print()
-            traceback.print_exc()
-        ret_code = 1
-    sys.exit(ret_code)
+
+def run_patman():
+    """Run patamn
+
+    This is the main program. It collects arguments and runs either the tests or
+    the control module.
+    """
+    args = cmdline.parse_args()
+
+    if not args.debug:
+        sys.tracebacklimit = 0
+
+    # Run our meagre tests
+    if args.cmd == 'test':
+        # pylint: disable=C0415
+        from patman import func_test
+        from patman import test_checkpatch
+
+        result = test_util.run_test_suites(
+            'patman', False, False, False, None, None, None,
+            [test_checkpatch.TestPatch, func_test.TestFunctional,
+             'gitutil', 'settings'])
+
+        sys.exit(0 if result.wasSuccessful() else 1)
+
+    # Process commits, produce patches files, check them, email them
+    elif args.cmd == 'send':
+        # Called from git with a patch filename as argument
+        # Printout a list of additional CC recipients for this patch
+        if args.cc_cmd:
+            re_line = re.compile(r'(\S*) (.*)')
+            with open(args.cc_cmd, 'r', encoding='utf-8') as inf:
+                for line in inf.readlines():
+                    match = re_line.match(line)
+                    if match and match.group(1) == args.patchfiles[0]:
+                        for cca in match.group(2).split('\0'):
+                            cca = cca.strip()
+                            if cca:
+                                print(cca)
+
+        elif args.full_help:
+            with resources.path('patman', 'README.rst') as readme:
+                tools.print_full_help(str(readme))
+        else:
+            # If we are not processing tags, no need to warning about bad ones
+            if not args.process_tags:
+                args.ignore_bad_tags = True
+            control.send(args)
+
+    # Check status of patches in patchwork
+    elif args.cmd == 'status':
+        ret_code = 0
+        try:
+            control.patchwork_status(args.branch, args.count, args.start, args.end,
+                                     args.dest_branch, args.force,
+                                     args.show_comments, args.patchwork_url)
+        except Exception as exc:
+            terminal.tprint(f'patman: {type(exc).__name__}: {exc}',
+                            colour=terminal.Color.RED)
+            if args.debug:
+                print()
+                traceback.print_exc()
+            ret_code = 1
+        sys.exit(ret_code)
+
+
+if __name__ == "__main__":
+    sys.exit(run_patman())
diff --git a/tools/patman/cmdline.py b/tools/patman/cmdline.py
new file mode 100644 (file)
index 0000000..d6496c0
--- /dev/null
@@ -0,0 +1,147 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright 2023 Google LLC
+#
+
+"""Handles parsing of buildman arguments
+
+This creates the argument parser and uses it to parse the arguments passed in
+"""
+
+import argparse
+import os
+import pathlib
+import sys
+
+from patman import gitutil
+from patman import project
+from patman import settings
+
+PATMAN_DIR = pathlib.Path(__file__).parent
+HAS_TESTS = os.path.exists(PATMAN_DIR / "func_test.py")
+
+def parse_args():
+    """Parse command line arguments from sys.argv[]
+
+    Returns:
+        tuple containing:
+            options: command line options
+            args: command lin arguments
+    """
+    epilog = '''Create patches from commits in a branch, check them and email
+        them as specified by tags you place in the commits. Use -n to do a dry
+        run first.'''
+
+    parser = argparse.ArgumentParser(epilog=epilog)
+    parser.add_argument('-b', '--branch', type=str,
+        help="Branch to process (by default, the current branch)")
+    parser.add_argument('-c', '--count', dest='count', type=int,
+        default=-1, help='Automatically create patches from top n commits')
+    parser.add_argument('-e', '--end', type=int, default=0,
+        help='Commits to skip at end of patch list')
+    parser.add_argument('-D', '--debug', action='store_true',
+        help='Enabling debugging (provides a full traceback on error)')
+    parser.add_argument('-p', '--project', default=project.detect_project(),
+                        help="Project name; affects default option values and "
+                        "aliases [default: %(default)s]")
+    parser.add_argument('-P', '--patchwork-url',
+                        default='https://patchwork.ozlabs.org',
+                        help='URL of patchwork server [default: %(default)s]')
+    parser.add_argument('-s', '--start', dest='start', type=int,
+        default=0, help='Commit to start creating patches from (0 = HEAD)')
+    parser.add_argument(
+        '-v', '--verbose', action='store_true', dest='verbose', default=False,
+        help='Verbose output of errors and warnings')
+    parser.add_argument(
+        '-H', '--full-help', action='store_true', dest='full_help',
+        default=False, help='Display the README file')
+
+    subparsers = parser.add_subparsers(dest='cmd')
+    send = subparsers.add_parser(
+        'send', help='Format, check and email patches (default command)')
+    send.add_argument('-i', '--ignore-errors', action='store_true',
+           dest='ignore_errors', default=False,
+           help='Send patches email even if patch errors are found')
+    send.add_argument('-l', '--limit-cc', dest='limit', type=int, default=None,
+           help='Limit the cc list to LIMIT entries [default: %(default)s]')
+    send.add_argument('-m', '--no-maintainers', action='store_false',
+           dest='add_maintainers', default=True,
+           help="Don't cc the file maintainers automatically")
+    send.add_argument(
+        '--get-maintainer-script', dest='get_maintainer_script', type=str,
+        action='store',
+        default=os.path.join(gitutil.get_top_level(), 'scripts',
+                             'get_maintainer.pl') + ' --norolestats',
+        help='File name of the get_maintainer.pl (or compatible) script.')
+    send.add_argument('-n', '--dry-run', action='store_true', dest='dry_run',
+           default=False, help="Do a dry run (create but don't email patches)")
+    send.add_argument('-r', '--in-reply-to', type=str, action='store',
+                      help="Message ID that this series is in reply to")
+    send.add_argument('-t', '--ignore-bad-tags', action='store_true',
+                      default=False,
+                      help='Ignore bad tags / aliases (default=warn)')
+    send.add_argument('-T', '--thread', action='store_true', dest='thread',
+                      default=False, help='Create patches as a single thread')
+    send.add_argument('--cc-cmd', dest='cc_cmd', type=str, action='store',
+           default=None, help='Output cc list for patch file (used by git)')
+    send.add_argument('--no-binary', action='store_true', dest='ignore_binary',
+                      default=False,
+                      help="Do not output contents of changes in binary files")
+    send.add_argument('--no-check', action='store_false', dest='check_patch',
+                      default=True,
+                      help="Don't check for patch compliance")
+    send.add_argument(
+        '--tree', dest='check_patch_use_tree', default=False,
+        action='store_true',
+        help=("Set `tree` to True. If `tree` is False then we'll pass "
+              "'--no-tree' to checkpatch (default: tree=%(default)s)"))
+    send.add_argument('--no-tree', dest='check_patch_use_tree',
+                      action='store_false', help="Set `tree` to False")
+    send.add_argument(
+        '--no-tags', action='store_false', dest='process_tags', default=True,
+        help="Don't process subject tags as aliases")
+    send.add_argument('--no-signoff', action='store_false', dest='add_signoff',
+                      default=True, help="Don't add Signed-off-by to patches")
+    send.add_argument('--smtp-server', type=str,
+                      help="Specify the SMTP server to 'git send-email'")
+    send.add_argument('--keep-change-id', action='store_true',
+                      help='Preserve Change-Id tags in patches to send.')
+
+    send.add_argument('patchfiles', nargs='*')
+
+    # Only add the 'test' action if the test data files are available.
+    if HAS_TESTS:
+        test_parser = subparsers.add_parser('test', help='Run tests')
+        test_parser.add_argument('testname', type=str, default=None, nargs='?',
+                                 help="Specify the test to run")
+
+    status = subparsers.add_parser('status',
+                                   help='Check status of patches in patchwork')
+    status.add_argument('-C', '--show-comments', action='store_true',
+                        help='Show comments from each patch')
+    status.add_argument(
+        '-d', '--dest-branch', type=str,
+        help='Name of branch to create with collected responses')
+    status.add_argument('-f', '--force', action='store_true',
+                        help='Force overwriting an existing branch')
+
+    # Parse options twice: first to get the project and second to handle
+    # defaults properly (which depends on project)
+    # Use parse_known_args() in case 'cmd' is omitted
+    argv = sys.argv[1:]
+    args, rest = parser.parse_known_args(argv)
+    if hasattr(args, 'project'):
+        settings.Setup(parser, args.project)
+        args, rest = parser.parse_known_args(argv)
+
+    # If we have a command, it is safe to parse all arguments
+    if args.cmd:
+        args = parser.parse_args(argv)
+    else:
+        # No command, so insert it after the known arguments and before the ones
+        # that presumably relate to the 'send' subcommand
+        nargs = len(rest)
+        argv = argv[:-nargs] + ['send'] + rest
+        args = parser.parse_args(argv)
+
+    return args