I refactored it and I think I've ironed out all the bugs. Does anyone have any additional ideas or see any glaring problems?
diff --git a/namcap b/namcap index ea0bc94..019b077 100755 --- a/namcap +++ b/namcap @@ -1,39 +1,2 @@ #!/bin/bash - -args='' -tmp=$(mktemp -d --tmpdir namcap.XXXXXXXXXX) -cleanup() { - rm -rf "${tmp}" -} -trap 'cleanup' 0 - -for arg in "${@}"; do - if echo "${arg}" | grep -q -E "^.+\.pkg\.tar\..+$" && [ -f "${arg}" ]; then - - extra_opts='' - case "${arg##*.}" in - gz|z|Z) cmd='gzip' ;; - bz2|bz) cmd='bzip2' ;; - xz) cmd='xz' ;; - lzo) cmd='lzop' ;; - lrz) cmd='lrzip' - extra_opts="-q -o -" ;; - lz4) cmd='lz4' - extra_opts="-q" ;; - lz) cmd='lzip' - extra_opts="-q" ;; - zst) cmd='zstd' - extra_opts="-q" ;; - *) echo 'Unsupported compression'; exit 1;; - esac - - tar="${tmp}/$(basename "${arg%.*}")" - $cmd -dcf $extra_opts "${arg}" > "${tar}" - - args="${args} ${tar}" - else - args="${args} ${arg}" - fi -done - -/usr/bin/env python3 -m namcap ${args} +/usr/bin/env python3 -m namcap ${@} diff --git a/namcap.py b/namcap.py index a7f532a..9a240dc 100755 --- a/namcap.py +++ b/namcap.py @@ -22,7 +22,11 @@ import getopt import os import sys +import subprocess +import tempfile +import pathlib import tarfile +import xtarfile import Namcap.depends import Namcap.tags @@ -49,18 +53,6 @@ def usage(): sys.exit(2) -def open_package(filename): - try: - tar = tarfile.open(filename, "r") - if '.PKGINFO' not in tar.getnames(): - tar.close() - return None - except IOError: - if tar: - tar.close() - return None - return tar - def check_rules_exclude(optlist): '''Check if the -r (--rules) and the -r (--exclude) options are being used at same time''' @@ -76,13 +68,10 @@ def show_messages(name, key, messages): for msg in messages: print("%s %s: %s" % (name, key, Namcap.tags.format_message(msg))) -def process_realpackage(package, modules): +def process_realpackage(pkgtar, package, modules): """Runs namcap checks over a package tarball""" - extracted = 0 - pkgtar = open_package(package) - - if not pkgtar: - print("Error: %s is empty or is not a valid package" % package) + if '.PKGINFO' not in pkgtar.getnames(): + raise TypeError('Error: %s is not a valid package' % package) return 1 pkginfo = Namcap.package.load_from_tarball(package) @@ -98,7 +87,7 @@ def process_realpackage(package, modules): rule.analyze(pkginfo, pkgtar) else: show_messages(pkginfo["name"], 'E', - [('error-running-rule %s', i)]) + [('error-running-rule %s', i)]) # Output the three types of messages show_messages(pkginfo["name"], 'E', rule.errors) @@ -237,15 +226,73 @@ if len(active_modules) == 0: # Go through each package, get the info, and apply the rules for package in packages: - if not os.access(package, os.R_OK): + pkgpath = pathlib.Path(package) + + if not pkgpath.is_file(): print("Error: Problem reading %s" % package) usage() + sys.exit(1) + + if pkgpath.with_suffix('').suffix == '.tar': + # What compression is used? + try: # Try using xtarfile first + with xtarfile.open(package, "r") as pkgtar: + process_realpackage(pkgtar, package, active_modules) + + # If xtarfile can't handle the compression do it with external software + except NotImplementedError: + if pkgpath.suffix == '.lzo': + cmd = 'lzop' + extra_opts = '-c' + elif pkgpath.suffix == '.lrz': + cmd = 'lrzip' + extra_opts = '-o -' + elif pkgpath.suffix == '.lz4': + cmd = 'lz4' + extra_opts = '-c' + elif pkgpath.suffix == '.lz': + cmd = 'lzip' + extra_opts='-c' + else: + print("Unsupported compression format:", package) + sys.exit(1) + + # Run decompression and put the .tar file in a temporary directory + try: + tmpdir = tempfile.TemporaryDirectory(prefix='namcap.') + except Exception as err: + print("Unable to create temporary storage:", err) + sys.exit(1) + + tmpfilepath = pathlib.Path(tmpdir.name).joinpath(pkgpath.with_suffix('').name) + try: + with open(tmpfilepath.as_posix(), 'wb') as outfile: + p = subprocess.run(cmd + ' -dqf ' + extra_opts + ' ' + pkgpath.as_posix(), stdout=outfile, shell=True) + p.check_returncode() # Make sure it actually ran without errors + except Exception as err: + print("Unable to decompress:", err) + sys.exit(1) + + try: +# if tmpfilepath.is_file() and tarfile.is_tarfile(tmpfilepath): + with tarfile.open(tmpfilepath) as pkgtar: # Is additional error checking needed here? + process_realpackage(pkgtar, package, active_modules) + except Exception as err: + print("File doesn't exist or isn't a tar file:", package, err) + sys.exit(1) + + except TypeError as err: # The tar file doesn't contain a .PKGINFO file + print(err) + sys.exit(1) + except Exception as err: + print("Something went horrible wrong:", err) + sys.exit(1) - if os.path.isfile(package) and tarfile.is_tarfile(package): - process_realpackage(package, active_modules) elif 'PKGBUILD' in package: process_pkgbuild(package, active_modules) + else: print("Error: %s not package or PKGBUILD" % package) + sys.exit(1) # vim: set ts=4 sw=4 noet: ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐ On Friday, December 11, 2020 11:10 PM, meganomic via arch-projects <arch-projects@archlinux.org> wrote: > I did a quick patch using your idea and it definitely seems like a good way > to do it. Can probably clean it up and make it look prettier with some > refactoring. I'll look into it later. > > diff --git a/namcap.py b/namcap.py > index a7f532a..0e78b89 100755 > --- a/namcap.py > +++ b/namcap.py > @@ -22,7 +22,10 @@ > import getopt > import os > import sys > -import tarfile > +import subprocess > +import tempfile > +import pathlib > +import xtarfile > > import Namcap.depends > import Namcap.tags > @@ -49,18 +52,6 @@ def usage(): > > sys.exit(2) > > -def open_package(filename): > > - try: > - tar = tarfile.open(filename, "r") > > > - if '.PKGINFO' not in tar.getnames(): > > > - tar.close() > > > - return None > > > - except IOError: > - if tar: > > > - tar.close() > > > - return None > > > - return tar > - > > def check_rules_exclude(optlist): > '''Check if the -r (--rules) and the -r (--exclude) options > are being used at same time''' > @@ -79,39 +70,38 @@ def show_messages(name, key, messages): > def process_realpackage(package, modules): > """Runs namcap checks over a package tarball""" > extracted = 0 > > - pkgtar = open_package(package) > - > - if not pkgtar: > - print("Error: %s is empty or is not a valid package" % package) > > > - return 1 > > > - > - pkginfo = Namcap.package.load_from_tarball(package) > - Loop through each one, load them apply if possible > > =================================================== > > - for i in modules: > - rule = get_modules()[i]() > > > - > - if isinstance(rule, Namcap.ruleclass.PkgInfoRule): > > > - rule.analyze(pkginfo, None) > > > - elif isinstance(rule, Namcap.ruleclass.PkgbuildRule): > > > - pass > > > - elif isinstance(rule, Namcap.ruleclass.TarballRule): > > > - rule.analyze(pkginfo, pkgtar) > > > - else: > > > - show_messages(pkginfo["name"], 'E', > > > - [('error-running-rule %s', i)]) > > > - > - # Output the three types of messages > > > - show_messages(pkginfo["name"], 'E', rule.errors) > > > - show_messages(pkginfo["name"], 'W', rule.warnings) > > > > - with xtarfile.open(package, "r") as pkgtar: > - if '.PKGINFO' not in pkgtar.getnames(): > > > - print("Error: %s is not a valid package" % package) > > > - return 1 > > > - > - pkginfo = Namcap.package.load_from_tarball(package) > > > - # Loop through each one, load them apply if possible > > > - for i in modules: > > > - rule = get_modules()[i]() > > > - > - if isinstance(rule, Namcap.ruleclass.PkgInfoRule): > > > - rule.analyze(pkginfo, None) > > > - elif isinstance(rule, Namcap.ruleclass.PkgbuildRule): > > > - pass > > > - elif isinstance(rule, Namcap.ruleclass.TarballRule): > > > - rule.analyze(pkginfo, pkgtar) > > > - else: > > > - show_messages(pkginfo["name"], 'E', > > > - > [('error-running-rule %s', i)]) > > > - > - # Output the three types of messages > > > - show_messages(pkginfo["name"], 'E', rule.errors) > > > - show_messages(pkginfo["name"], 'W', rule.warnings) > > > - if info_reporting: > > > - show_messages(pkginfo["name"], 'I', rule.infos) > > > - > - # dependency analysis > > > - errs, warns, infos = Namcap.depends.analyze_depends(pkginfo) > > > - show_messages(pkginfo["name"], 'E', errs) > > > - show_messages(pkginfo["name"], 'W', warns) > if info_reporting: > > > > - show_messages(pkginfo["name"], 'I', rule.infos) > > > - > - dependency analysis > > ==================== > > - errs, warns, infos = Namcap.depends.analyze_depends(pkginfo) > - show_messages(pkginfo["name"], 'E', errs) > - show_messages(pkginfo["name"], 'W', warns) > - if info_reporting: > - show_messages(pkginfo["name"], 'I', infos) > > > > - show_messages(pkginfo["name"], 'I', infos) > > > > def process_pkginfo(pkginfo, modules): > """Runs namcap checks of a single, non-split PacmanPackage object""" > @@ -237,14 +227,45 @@ if len(active_modules) == 0: > > Go through each package, get the info, and apply the rules > > =========================================================== > > for package in packages: > > - if not os.access(package, os.R_OK): > > - pkgpath = pathlib.Path(package) > - > - if not pkgpath.is_file(): > print("Error: Problem reading %s" % package) > > > - usage() > > > > - parser.print_usage() > > > - continue # Skip to next package if any > > > - > - if pkgpath.with_suffix('').suffix == '.tar': > > - # What compression is used? > > > - extra_opts = '' > > > - if pkgpath.suffix == '.gz' or pkgpath.suffix == '.z' or > pkgpath.suffix == '.Z' or pkgpath.suffix == '.bz2' or pkgpath.suffix == '.bz' > or pkgpath.suffix == '.xz' or pkgpath.suffix == '.zst': > > > - process_realpackage(package, active_modules) > > > - continue # Skip to next package if any > > > - elif pkgpath.suffix == '.lzo': > > > - cmd = 'lzop' > > > - elif pkgpath.suffix == '.lrz': > > > - cmd = 'lrzip' > > > - extra_opts="-q -o -" > > > - elif pkgpath.suffix == '.lz4': > > > - cmd = 'lz4' > > > - extra_opts="-q" > > > - elif pkgpath.suffix == '.lz': > > > - cmd = 'lzip' > > > - extra_opts="-q" > > > - else: > > > - print("Unsupported compression!") > > > - continue # Skip to next package if any > > > - > - # Run decompression and put the .tar file in a temporary directory > > > - tmpdir = tempfile.TemporaryDirectory(prefix='namcap.') > > > - tmpfilepath = > pathlib.Path(tmpdir.name).joinpath(pkgpath.with_suffix('').name) > > > - subprocess.run(cmd + ' -dcf ' + extra_opts + pkgpath.as_posix() + ' > > ' + tmpfilepath.as_posix(), shell=True) > > > - > - if tmpfilepath.is_file() and xtarfile.is_tarfile(tmpfilepath): > > > - process_realpackage(package, active_modules) > > > > - if os.path.isfile(package) and tarfile.is_tarfile(package): > - process_realpackage(package, active_modules) > > > elif 'PKGBUILD' in package: > process_pkgbuild(package, active_modules) > > > - else: > print("Error: %s not package or PKGBUILD" % package) > > ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐ > On Friday, December 11, 2020 9:29 PM, Eli Schwartz via arch-projects > arch-projects@archlinux.org wrote: > > > > On 12/11/20 2:25 PM, meganomic via arch-projects wrote: > > > > > Adds handling of the compression and temporary storage into namcap.py > > > so it can be removed from the bash script. > > > https://bugs.archlinux.org/task/59844 mentions "use setuptools entry > > > points." instead of the bash script but I don't know how to do that. > > > I just removed it from the bash script for now. > > > > The subprocess to decompression tools is still (with your patch) just as > > annoying to me as the existence of the bash script. Python's builtin > > tarfile.open can handle gz, bz2, or xz. Using the python-xtarfile module > > you can use xtarfile.open to also handle zst. We should prefer the native > > code instead of basically inlining bash into python. > > True, in order to support lzo, lrz, lz4, lz, we might still need to do > > subprocess out and create a temporary file with the decompressed package, > > but this should really be a last resort (and users in practice should > > rarely hit this code). > > What do you think about trying xtarfile first? > > > > Eli Schwartz > > Bug Wrangler and Trusted User