diff options
author | Tom Rini <trini@konsulko.com> | 2023-11-15 14:15:21 -0500 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2023-11-15 14:15:21 -0500 |
commit | 169c3cc49e40758956ee6c310f7f59a6443826a1 (patch) | |
tree | 469779cc9196adce361abe56c2ce27a6563e98ce | |
parent | 92b27528d777ce85362af45e7d2974a6c856219b (diff) | |
parent | 0d4d9f94c555577f78cddc372c307465fc92413e (diff) | |
download | u-boot-169c3cc49e40758956ee6c310f7f59a6443826a1.tar.gz u-boot-169c3cc49e40758956ee6c310f7f59a6443826a1.tar.bz2 u-boot-169c3cc49e40758956ee6c310f7f59a6443826a1.zip |
Merge tag 'dm-pull-15nov23' of https://source.denx.de/u-boot/custodians/u-boot-dm
patman correct import of u_boot_pylib
correct long-standing EFI framebuffer bug
minor test refactor
-rw-r--r-- | arch/sandbox/cpu/os.c | 15 | ||||
-rw-r--r-- | boot/expo.c | 4 | ||||
-rw-r--r-- | cmd/bootflow.c | 53 | ||||
-rw-r--r-- | common/bootstage.c | 2 | ||||
-rw-r--r-- | doc/usage/cmd/bootflow.rst | 67 | ||||
-rw-r--r-- | drivers/core/Kconfig | 1 | ||||
-rw-r--r-- | include/dm/util.h | 4 | ||||
-rw-r--r-- | include/video.h | 9 | ||||
-rw-r--r-- | lib/efi_loader/efi_gop.c | 12 | ||||
-rw-r--r-- | test/boot/bootflow.c | 64 | ||||
-rwxr-xr-x | tools/patman/__main__.py | 254 | ||||
-rw-r--r-- | tools/patman/cmdline.py | 147 |
12 files changed, 413 insertions, 219 deletions
diff --git a/arch/sandbox/cpu/os.c b/arch/sandbox/cpu/os.c index 85d0d6a170..95c26d855a 100644 --- a/arch/sandbox/cpu/os.c +++ b/arch/sandbox/cpu/os.c @@ -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) diff --git a/boot/expo.c b/boot/expo.c index 139d684f8e..cadb6a0ad6 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -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); diff --git a/cmd/bootflow.c b/cmd/bootflow.c index ad39ebe437..3aeb40d690 100644 --- a/cmd/bootflow.c +++ b/cmd/bootflow.c @@ -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; } diff --git a/common/bootstage.c b/common/bootstage.c index a68d883c68..0e6d80718f 100644 --- a/common/bootstage.c +++ b/common/bootstage.c @@ -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"); } } diff --git a/doc/usage/cmd/bootflow.rst b/doc/usage/cmd/bootflow.rst index 9c5ea9c5d8..2198ff6049 100644 --- a/doc/usage/cmd/bootflow.rst +++ b/doc/usage/cmd/bootflow.rst @@ -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). diff --git a/drivers/core/Kconfig b/drivers/core/Kconfig index fe5c41d57e..737d4590d5 100644 --- a/drivers/core/Kconfig +++ b/drivers/core/Kconfig @@ -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. diff --git a/include/dm/util.h b/include/dm/util.h index 89206cc496..95c3527a37 100644 --- a/include/dm/util.h +++ b/include/dm/util.h @@ -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; diff --git a/include/video.h b/include/video.h index 5048116a3d..4d8df9baaa 100644 --- a/include/video.h +++ b/include/video.h @@ -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) */ diff --git a/lib/efi_loader/efi_gop.c b/lib/efi_loader/efi_gop.c index 778b693f98..a09db31eb4 100644 --- a/lib/efi_loader/efi_gop.c +++ b/lib/efi_loader/efi_gop.c @@ -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; } diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c index f640db8a24..b97c566f00 100644 --- a/test/boot/bootflow.c +++ b/test/boot/bootflow.c @@ -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"); diff --git a/tools/patman/__main__.py b/tools/patman/__main__.py index 197ac1aad1..f645b38b64 100755 --- a/tools/patman/__main__.py +++ b/tools/patman/__main__.py @@ -6,197 +6,93 @@ """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 index 0000000000..d6496c0cb7 --- /dev/null +++ b/tools/patman/cmdline.py @@ -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 |