[elbe-devel] [PATCH 2/3] elbepack: pbuilder: migrate to argparse

Thomas Weißschuh thomas.weissschuh at linutronix.de
Fri Jul 26 10:55:11 CEST 2024


argparse has various advantages over optparse:

* Autogenerated command synopsis.
* Required arguments.
* Flexible argument types.
* Subparsers.

Furthermore optparse is deprecated since Python 3.2 (2011).

Replace the custom action registry with a simple dispatch table, which
is both less code and easier to understand.

Also move the options to the subcommands where they make sense.

Signed-off-by: Thomas Weißschuh <thomas.weissschuh at linutronix.de>
---
 elbepack/commands/pbuilder.py |  77 ++------
 elbepack/pbuilderaction.py    | 425 ++++++++++++++++++++----------------------
 2 files changed, 218 insertions(+), 284 deletions(-)

diff --git a/elbepack/commands/pbuilder.py b/elbepack/commands/pbuilder.py
index 851e277973b3..1bf16edd2ae3 100644
--- a/elbepack/commands/pbuilder.py
+++ b/elbepack/commands/pbuilder.py
@@ -2,75 +2,26 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
 # SPDX-FileCopyrightText: 2015-2017 Linutronix GmbH
 
-import sys
-from optparse import OptionParser
+import argparse
 
-from elbepack.commands.preprocess import add_xmlpreprocess_passthrough_options
-from elbepack.pbuilderaction import PBuilderAction
+from elbepack.cli import add_arguments_from_decorated_function
+from elbepack.commands.preprocess import add_xmlpreprocess_passthrough_arguments
+from elbepack.pbuilderaction import pbuilder_actions
 
 
 def run_command(argv):
-    oparser = OptionParser(usage='usage: elbe pbuilder [options] <command>')
+    aparser = argparse.ArgumentParser(prog='elbe pbuilder')
 
-    oparser.add_option('--project', dest='project', default=None,
-                       help='project directory on the initvm')
+    add_xmlpreprocess_passthrough_arguments(aparser)
 
-    oparser.add_option('--xmlfile', dest='xmlfile', default=None,
-                       help='xmlfile to use')
+    subparsers = aparser.add_subparsers(required=True)
 
-    oparser.add_option('--writeproject', dest='writeproject', default=None,
-                       help='write project name to file')
+    for action_name, do_action in pbuilder_actions.items():
+        action_parser = subparsers.add_parser(action_name)
+        action_parser.set_defaults(func=do_action)
+        add_arguments_from_decorated_function(action_parser, do_action)
 
-    oparser.add_option('--skip-download', action='store_true',
-                       dest='skip_download', default=False,
-                       help='Skip downloading generated Files')
+    args = aparser.parse_args(argv)
+    args.parser = aparser
 
-    oparser.add_option(
-        '--origfile',
-        dest='origfile',
-        default=[],
-        action='append',
-        help='upload orig file')
-
-    oparser.add_option('--source', dest='srcdir', default='.',
-                       help='directory containing sources')
-
-    oparser.add_option('--output', dest='outdir', default='..',
-                       help='directory where to save downloaded Files')
-
-    oparser.add_option('--profile', dest='profile', default='',
-                       help='profile that shall be built')
-
-    oparser.add_option('--cross', dest='cross', default=False,
-                       action='store_true',
-                       help='Creates an environment for crossbuilding if '
-                            'combined with create. Combined with build it'
-                            ' will use this environment.')
-
-    oparser.add_option('--no-ccache', dest='noccache', default=False,
-                       action='store_true',
-                       help="Deactivates the compiler cache 'ccache'")
-
-    oparser.add_option('--ccache-size', dest='ccachesize', default='10G',
-                       action='store', type='string',
-                       help='set a limit for the compiler cache size '
-                            '(should be a number followed by an optional '
-                            'suffix: k, M, G, T. Use 0 for no limit.)')
-
-    add_xmlpreprocess_passthrough_options(oparser)
-
-    (opt, args) = oparser.parse_args(argv)
-
-    if not args:
-        print('elbe pbuilder - no subcommand given', file=sys.stderr)
-        PBuilderAction.print_actions()
-        return
-
-    try:
-        action = PBuilderAction(args[0])
-    except KeyError:
-        print('elbe pbuilder - unknown subcommand', file=sys.stderr)
-        PBuilderAction.print_actions()
-        sys.exit(92)
-
-    action.execute(opt, args[1:])
+    args.func(args)
diff --git a/elbepack/pbuilderaction.py b/elbepack/pbuilderaction.py
index b8bc6d5f05b1..b2e8b2ec14bf 100644
--- a/elbepack/pbuilderaction.py
+++ b/elbepack/pbuilderaction.py
@@ -5,271 +5,254 @@
 import subprocess
 import sys
 
+from elbepack.cli import add_argument
 from elbepack.directories import run_elbe
 from elbepack.filesystem import TmpdirFilesystem
 from elbepack.xmlpreprocess import preprocess_file
 
 
-class PBuilderAction:
-    actiondict = {}
-
-    @classmethod
-    def register(cls, action):
-        cls.actiondict[action.tag] = action
-
-    @classmethod
-    def print_actions(cls):
-        print('available subcommands are:', file=sys.stderr)
-        for a in cls.actiondict:
-            print(f'   {a}', file=sys.stderr)
-
-    def __new__(cls, node):
-        action = cls.actiondict[node]
-        return object.__new__(action)
-
-    def __init__(self, node):
-        self.node = node
-
-    def execute(self, _opt, _args):
-        raise NotImplementedError('execute() not implemented')
-
-
-class CreateAction(PBuilderAction):
-
-    tag = 'create'
-
-    def execute(self, opt, _args):
-        crossopt = []
-        if opt.cross:
-            crossopt = ['--cross']
-        if opt.noccache:
-            ccacheopt = ['--no-ccache']
-        else:
-            ccacheopt = ['--ccache-size', opt.ccachesize]
-
-        if opt.xmlfile:
-            with preprocess_file(opt.xmlfile, opt.variants) as preproc:
-                ps = run_elbe(['control', 'create_project'],
-                              capture_output=True, encoding='utf-8')
-                if ps.returncode != 0:
-                    print('elbe control create_project failed.',
-                          file=sys.stderr)
-                    print(ps.stderr, file=sys.stderr)
-                    print('Giving up', file=sys.stderr)
-                    sys.exit(152)
-
-                prjdir = ps.stdout.strip()
-                ps = run_elbe(['control', 'set_xml', prjdir, preproc],
-                              capture_output=True, encoding='utf-8')
-
-                if ps.returncode != 0:
-                    print('elbe control set_xml failed.', file=sys.stderr)
-                    print(ps.stderr, file=sys.stderr)
-                    print('Giving up', file=sys.stderr)
-                    sys.exit(153)
+ at add_argument('--writeproject', help='write project name to file')
+ at add_argument('--ccache-size', dest='ccachesize', default='10G',
+              help='set a limit for the compiler cache size '
+                   '(should be a number followed by an optional '
+                   'suffix: k, M, G, T. Use 0 for no limit.)')
+ at add_argument('--cross', dest='cross', default=False,
+              action='store_true',
+              help='Creates an environment for crossbuilding if '
+                   'combined with create. Combined with build it'
+                   ' will use this environment.')
+ at add_argument('--no-ccache', dest='noccache', default=False,
+              action='store_true',
+              help="Deactivates the compiler cache 'ccache'")
+ at add_argument('--xmlfile', help='xmlfile to use')
+ at add_argument('--project', help='project directory on the initvm')
+def _create(args):
+    crossopt = []
+    if args.cross:
+        crossopt = ['--cross']
+    if args.noccache:
+        ccacheopt = ['--no-ccache']
+    else:
+        ccacheopt = ['--ccache-size', args.ccachesize]
+
+    if args.xmlfile:
+        with preprocess_file(args.xmlfile, args.variants) as preproc:
+            ps = run_elbe(['control', 'create_project'],
+                          capture_output=True, encoding='utf-8')
+            if ps.returncode != 0:
+                print('elbe control create_project failed.',
+                      file=sys.stderr)
+                print(ps.stderr, file=sys.stderr)
+                print('Giving up', file=sys.stderr)
+                sys.exit(152)
 
-            if opt.writeproject:
-                wpf = open(opt.writeproject, 'w')
-                wpf.write(prjdir)
-                wpf.close()
+            prjdir = ps.stdout.strip()
+            ps = run_elbe(['control', 'set_xml', prjdir, preproc],
+                          capture_output=True, encoding='utf-8')
 
-        elif opt.project:
-            prjdir = opt.project
-        else:
-            print('you need to specify --project option', file=sys.stderr)
-            sys.exit(155)
+            if ps.returncode != 0:
+                print('elbe control set_xml failed.', file=sys.stderr)
+                print(ps.stderr, file=sys.stderr)
+                print('Giving up', file=sys.stderr)
+                sys.exit(153)
+
+        if args.writeproject:
+            wpf = open(args.writeproject, 'w')
+            wpf.write(prjdir)
+            wpf.close()
+
+    elif args.project:
+        prjdir = args.project
+    else:
+        args.parser.error('you need to specify --project option')
+
+    print('Creating pbuilder')
+
+    try:
+        run_elbe(['control', 'build_pbuilder', prjdir, *crossopt, *ccacheopt],
+                 check=True)
+    except subprocess.CalledProcessError:
+        print('elbe control build_pbuilder Failed', file=sys.stderr)
+        print('Giving up', file=sys.stderr)
+        sys.exit(156)
+
+    try:
+        run_elbe(['control', 'wait_busy', prjdir], check=True)
+    except subprocess.CalledProcessError:
+        print('elbe control wait_busy Failed', file=sys.stderr)
+        print('Giving up', file=sys.stderr)
+        sys.exit(157)
+
+    print('')
+    print('Building Pbuilder finished !')
+    print('')
+
+
+ at add_argument('--project', required=True, help='project directory on the initvm')
+def _update(args):
+    prjdir = args.project
+
+    print('Updating pbuilder')
+
+    try:
+        run_elbe(['control', 'update_pbuilder', prjdir], check=True)
+    except subprocess.CalledProcessError:
+        print('elbe control update_pbuilder Failed', file=sys.stderr)
+        print('Giving up', file=sys.stderr)
+        sys.exit(159)
+
+    print('')
+    print('Updating Pbuilder finished !')
+    print('')
+
+
+ at add_argument('--origfile', default=[], action='append', help='upload orig file')
+ at add_argument('--profile', default='', help='profile that shall be built')
+ at add_argument('--skip-download', action='store_true', dest='skip_download', default=False,
+              help='Skip downloading generated Files')
+ at add_argument('--source', dest='srcdir', default='.', help='directory containing sources')
+ at add_argument('--cross', dest='cross', default=False,
+              action='store_true',
+              help='Creates an environment for crossbuilding if '
+                   'combined with create. Combined with build it'
+                   ' will use this environment.')
+ at add_argument('--output', dest='outdir', default='..',
+              help='directory where to save downloaded Files')
+ at add_argument('--xmlfile', help='xmlfile to use')
+ at add_argument('--project', help='project directory on the initvm')
+def _build(args):
+    crossopt = []
+    if args.cross:
+        crossopt = ['--cross']
+    tmp = TmpdirFilesystem()
+
+    if args.xmlfile:
+        ps = run_elbe(['control', '--retries', '60', 'create_project', args.xmlfile],
+                      capture_output=True, encoding='utf-8')
+        if ps.returncode != 0:
+            print('elbe control create_project failed.', file=sys.stderr)
+            print(ps.stderr, file=sys.stderr)
+            print('Giving up', file=sys.stderr)
+            sys.exit(160)
 
-        print('Creating pbuilder')
+        prjdir = ps.stdout.strip()
 
         try:
-            run_elbe(['control', 'build_pbuilder', prjdir, *crossopt, *ccacheopt],
-                     check=True)
+            run_elbe(['control', 'build_pbuilder', prjdir], check=True)
         except subprocess.CalledProcessError:
             print('elbe control build_pbuilder Failed', file=sys.stderr)
             print('Giving up', file=sys.stderr)
-            sys.exit(156)
+            sys.exit(161)
 
         try:
             run_elbe(['control', 'wait_busy', prjdir], check=True)
         except subprocess.CalledProcessError:
             print('elbe control wait_busy Failed', file=sys.stderr)
             print('Giving up', file=sys.stderr)
-            sys.exit(157)
+            sys.exit(162)
 
         print('')
         print('Building Pbuilder finished !')
         print('')
-
-
-PBuilderAction.register(CreateAction)
-
-
-class UpdateAction(PBuilderAction):
-
-    tag = 'update'
-
-    def execute(self, opt, _args):
-
-        if not opt.project:
-            print('you need to specify --project option', file=sys.stderr)
-            sys.exit(158)
-
-        prjdir = opt.project
-
-        print('Updating pbuilder')
-
+    elif args.project:
+        prjdir = args.project
+        run_elbe(['control', 'rm_log', prjdir], check=True)
+    else:
+        args.parser.error('you need to specify --project or --xmlfile option')
+
+    print('')
+    print('Packing Source into tmp archive')
+    print('')
+    try:
+        subprocess.run(['tar', '-C', args.srcdir, '-czf', tmp.fname('pdebuild.tar.gz'), '.'],
+                       check=True)
+    except subprocess.CalledProcessError:
+        print('tar Failed', file=sys.stderr)
+        print('Giving up', file=sys.stderr)
+        sys.exit(164)
+
+    for of in args.origfile:
+        print('')
+        print(f"Pushing orig file '{of}' into pbuilder")
+        print('')
         try:
-            run_elbe(['control', 'update_pbuilder', prjdir], check=True)
+            run_elbe(['control', 'set_orig', prjdir, of], check=True)
         except subprocess.CalledProcessError:
-            print('elbe control update_pbuilder Failed', file=sys.stderr)
+            print('elbe control set_orig Failed', file=sys.stderr)
             print('Giving up', file=sys.stderr)
-            sys.exit(159)
-
+            sys.exit(165)
+
+    print('')
+    print('Pushing source into pbuilder')
+    print('')
+
+    try:
+        run_elbe([
+            'control', 'set_pdebuild',
+            '--profile', args.profile, *crossopt,
+            prjdir, tmp.fname('pdebuild.tar.gz'),
+        ], check=True)
+    except subprocess.CalledProcessError:
+        print('elbe control set_pdebuild Failed', file=sys.stderr)
+        print('Giving up', file=sys.stderr)
+        sys.exit(166)
+    try:
+        run_elbe(['control', 'wait_busy', prjdir], check=True)
+    except subprocess.CalledProcessError:
+        print('elbe control wait_busy Failed', file=sys.stderr)
+        print('Giving up', file=sys.stderr)
+        sys.exit(167)
+    print('')
+    print('Pdebuild finished !')
+    print('')
+
+    if args.skip_download:
         print('')
-        print('Updating Pbuilder finished !')
-        print('')
-
-
-PBuilderAction.register(CreateAction)
-
-
-class BuildAction(PBuilderAction):
-
-    tag = 'build'
-
-    def execute(self, opt, _args):
-
-        crossopt = []
-        if opt.cross:
-            crossopt = ['--cross']
-        tmp = TmpdirFilesystem()
-
-        if opt.xmlfile:
-            ps = run_elbe(['control', '--retries', '60', 'create_project', opt.xmlfile],
-                          capture_output=True, encoding='utf-8')
-            if ps.returncode != 0:
-                print('elbe control create_project failed.', file=sys.stderr)
-                print(ps.stderr, file=sys.stderr)
-                print('Giving up', file=sys.stderr)
-                sys.exit(160)
-
-            prjdir = ps.stdout.strip()
-
-            try:
-                run_elbe(['control', 'build_pbuilder', prjdir], check=True)
-            except subprocess.CalledProcessError:
-                print('elbe control build_pbuilder Failed', file=sys.stderr)
-                print('Giving up', file=sys.stderr)
-                sys.exit(161)
-
-            try:
-                run_elbe(['control', 'wait_busy', prjdir], check=True)
-            except subprocess.CalledProcessError:
-                print('elbe control wait_busy Failed', file=sys.stderr)
-                print('Giving up', file=sys.stderr)
-                sys.exit(162)
-
-            print('')
-            print('Building Pbuilder finished !')
-            print('')
-        elif opt.project:
-            prjdir = opt.project
-            run_elbe(['control', 'rm_log', prjdir], check=True)
-        else:
-            print(
-                'you need to specify --project or --xmlfile option',
-                file=sys.stderr)
-            sys.exit(163)
-
-        print('')
-        print('Packing Source into tmp archive')
+        print('Listing available files:')
         print('')
         try:
-            subprocess.run(['tar', '-C', opt.srcdir, '-czf', tmp.fname('pdebuild.tar.gz'), '.'],
-                           check=True)
+            run_elbe(['control', 'get_files', '--pbuilder-only', prjdir], check=True)
         except subprocess.CalledProcessError:
-            print('tar Failed', file=sys.stderr)
-            print('Giving up', file=sys.stderr)
-            sys.exit(164)
+            print('elbe control get_files Failed', file=sys.stderr)
+            print('', file=sys.stderr)
+            print('dumping logfile', file=sys.stderr)
 
-        for of in opt.origfile:
-            print('')
-            print(f"Pushing orig file '{of}' into pbuilder")
-            print('')
             try:
-                run_elbe(['control', 'set_orig', prjdir, of], check=True)
+                run_elbe(['control', 'dump_file', prjdir, 'log.txt'], check=True)
             except subprocess.CalledProcessError:
-                print('elbe control set_orig Failed', file=sys.stderr)
+                print('elbe control dump_file Failed', file=sys.stderr)
+                print('', file=sys.stderr)
                 print('Giving up', file=sys.stderr)
-                sys.exit(165)
 
+            sys.exit(168)
+
+        print('')
+        print(f"Get Files with: 'elbe control get_file {prjdir} <filename>'")
+    else:
         print('')
-        print('Pushing source into pbuilder')
+        print(f'Saving generated Files to {args.outdir}')
         print('')
 
         try:
-            run_elbe([
-                'control', 'set_pdebuild',
-                '--profile', opt.profile, *crossopt,
-                prjdir, tmp.fname('pdebuild.tar.gz'),
-            ], check=True)
-        except subprocess.CalledProcessError:
-            print('elbe control set_pdebuild Failed', file=sys.stderr)
-            print('Giving up', file=sys.stderr)
-            sys.exit(166)
-        try:
-            run_elbe(['control', 'wait_busy', prjdir], check=True)
+            run_elbe(['control', 'get_files', '--pbuilder-only',
+                      '--output', args.outdir, prjdir], check=True)
         except subprocess.CalledProcessError:
-            print('elbe control wait_busy Failed', file=sys.stderr)
-            print('Giving up', file=sys.stderr)
-            sys.exit(167)
-        print('')
-        print('Pdebuild finished !')
-        print('')
-
-        if opt.skip_download:
-            print('')
-            print('Listing available files:')
-            print('')
-            try:
-                run_elbe(['control', 'get_files', '--pbuilder-only', prjdir], check=True)
-            except subprocess.CalledProcessError:
-                print('elbe control get_files Failed', file=sys.stderr)
-                print('', file=sys.stderr)
-                print('dumping logfile', file=sys.stderr)
-
-                try:
-                    run_elbe(['control', 'dump_file', prjdir, 'log.txt'], check=True)
-                except subprocess.CalledProcessError:
-                    print('elbe control dump_file Failed', file=sys.stderr)
-                    print('', file=sys.stderr)
-                    print('Giving up', file=sys.stderr)
-
-                sys.exit(168)
-
-            print('')
-            print(f"Get Files with: 'elbe control get_file {prjdir} <filename>'")
-        else:
-            print('')
-            print(f'Saving generated Files to {opt.outdir}')
-            print('')
+            print('elbe control get_files Failed', file=sys.stderr)
+            print('', file=sys.stderr)
+            print('dumping logfile', file=sys.stderr)
 
             try:
-                run_elbe(['control', 'get_files', '--pbuilder-only',
-                          '--output', opt.outdir, prjdir], check=True)
+                run_elbe(['control', 'dump_file', prjdir, 'log.txt'], check=True)
             except subprocess.CalledProcessError:
-                print('elbe control get_files Failed', file=sys.stderr)
+                print('elbe control dump_file Failed', file=sys.stderr)
                 print('', file=sys.stderr)
-                print('dumping logfile', file=sys.stderr)
-
-                try:
-                    run_elbe(['control', 'dump_file', prjdir, 'log.txt'], check=True)
-                except subprocess.CalledProcessError:
-                    print('elbe control dump_file Failed', file=sys.stderr)
-                    print('', file=sys.stderr)
-                    print('Giving up', file=sys.stderr)
+                print('Giving up', file=sys.stderr)
 
-                sys.exit(169)
+            sys.exit(169)
 
 
-PBuilderAction.register(BuildAction)
+pbuilder_actions = {
+    'create': _create,
+    'update': _update,
+    'build':  _build,
+}

-- 
2.45.2



More information about the elbe-devel mailing list