#2325 vcs-diff-lint: a new sub-package
Merged 2 years ago by praiskup. Opened 2 years ago by praiskup.
Unknown source vcs-diff-lint  into  main

@@ -1,46 +0,0 @@

- #! /usr/bin/python3

- 

- """

- The csdiff tool doesn't support the Pylint's JSON output yet.  So this just a

- trivial wrapper which reads Pylint's report and transforms it to JSON which is

- supported by csdiff.

- 

- The script accepts the same parameters as pylint itself.

- """

- 

- import sys

- import json

- import subprocess

- 

- PYLINT = ["pylint", "-rn", "--score=no", "--output-format=json"]

- 

- 

- def _main():

-     pylint_command = PYLINT + sys.argv[1:]

- 

-     # pylint: disable=subprocess-run-check

-     pylint_result = subprocess.run(pylint_command, capture_output=True)

-     data = json.loads(pylint_result.stdout)

-     csdiff_data = []

-     for defect in data:

-         message = defect["obj"] + ": " if defect["obj"] else ""

-         message += defect["message"]

- 

-         csdiff_data.append({

-             "checker": "PYLINT_WARNING",

-             "events": [{

-                 "file_name": defect["path"],

-                 "line": defect["line"],

-                 "column": defect["column"],

-                 "event": "{}[{}]".format(defect["message-id"],

-                                          defect["symbol"]),

-                 "message": message,

-             }],

-         })

- 

-     print(json.dumps({"defects": csdiff_data}, indent=2))

-     return pylint_result.returncode

- 

- 

- if __name__ == "__main__":

-     sys.exit(_main())

file removed
-340
@@ -1,340 +0,0 @@

- #! /usr/bin/python3

- 

- """

- Using 'csdiff', print newly added coding errors.

- """

- 

- import os

- import sys

- from subprocess import Popen, PIPE, check_output, check_call

- import glob

- import logging

- import tempfile

- import shutil

- import argparse

- 

- 

- logging.basicConfig(level=logging.ERROR, format='%(levelname)s: %(message)s')

- log = logging.getLogger()  # pylint: disable=invalid-name

- 

- CSDIFF_PYLINT = os.path.realpath(os.path.join(os.path.dirname(__file__),

-                                               'copr-csdiff-pylint'))

- 

- 

- def _run_csdiff(old, new, msg):

-     popen_diff = Popen(['csdiff', '-c', old, new],

-                        stdout=PIPE)

-     diff = popen_diff.communicate()[0].decode('utf-8')

-     if diff:

-         sep = "=" * (len(msg) + 2)

-         print(sep)

-         print(" {} ".format(msg))

-         print(sep + "\n")

-         sys.stdout.write(diff)

-     return int(bool(diff))

- 

- 

- def file_type(filename):

-     """

-     Taking FILENAME (must exist), return it's type in string format.  Current

-     supported formats are ('python',).

-     """

-     if filename.endswith(".py"):

-         return 'python'

- 

-     if os.path.islink(filename):

-         return 'unknown'

- 

-     with open(filename) as f_d:

-         try:

-             first_line = f_d.readline()

-         except UnicodeDecodeError:

-             return 'unknown'

-         if first_line.startswith('#!') and first_line.find('python') != -1:

-             return 'python'

-     return 'unknown'

- 

- 

- class _Linter:

-     filetype = None

-     path_filters = None

- 

-     def __init__(self, gitroot, renames=None):

-         self.gitroot = gitroot

-         self.renames = renames

- 

-     @classmethod

-     def modify_rename(cls, old, new):

-         """ if the paths in linter output need adjustments """

-         return old, new

- 

-     def _sed_filter(self):

-         if not self.renames:

-             return None

- 

-         rules = []

-         for pair in self.renames:

-             old, new = self.modify_rename(pair[0], pair[1])

-             rule = "s|: \"{}\"|: \"{}\"|".format(old, new)

-             rules += ['-e', rule]

- 

-         return ['sed'] + rules

- 

-     def command(self, projectdir, filenames):

-         """

-         Given the list of FILENAMES, generate command that will be executed

-         by lint() method, and environment vars set.  Return (CMD, ENV) pair.

-         """

-         raise NotImplementedError

- 

-     # pylint: disable=no-self-use,unused-argument

-     def is_compatible(self, file):

-         """ file contains 'filename' and 'type' attributes """

-         return True

- 

-     def lint(self, projectdir, files, logfd):

-         """ run the linter """

-         if not files:

-             return

- 

-         abs_projectdir = os.path.join(self.gitroot, projectdir)

- 

-         oldcwd = os.getcwd()

- 

-         try:

-             log.info("linting in %s", abs_projectdir)

-             os.chdir(abs_projectdir)

-             files = [f.filename for f in files if self.is_compatible(f)]

-             if not files:

-                 return

-             linter_cmd, linter_env = self.command(projectdir, files)

-             log.debug("Running linter command: %s", linter_cmd)

-             env = os.environ.copy()

-             env.update(linter_env)

-             sed_cmd = self._sed_filter()

-             if sed_cmd:

-                 linter = Popen(linter_cmd, env=env, stdout=PIPE)

-                 sed = Popen(sed_cmd, stdout=logfd, stdin=linter.stdout)

-                 sed.communicate()

-             else:

-                 linter = Popen(linter_cmd, env=env, stdout=logfd)

-                 linter.communicate()

- 

-         finally:

-             os.chdir(oldcwd)

- 

- 

- class PylintLinter(_Linter):

-     """

-     Generate pyilnt error output that is compatible with 'csdiff'.

-     """

-     def is_compatible(self, file):

-         return file.type == 'python'

- 

-     def command(self, projectdir, filenames):

-         abs_pylintrc = os.path.join(self.gitroot, projectdir, 'pylintrc')

-         pylintrc = os.path.realpath(abs_pylintrc)

-         env = {}

-         if os.path.exists(pylintrc):

-             env['PYLINTRC'] = pylintrc

-         cmd = [CSDIFF_PYLINT] + filenames

-         return cmd, env

- 

- 

- def get_rename_map(options, subdir):

-     """

-     Using the os.getcwd() and 'git diff --namestatus', generate list of

-     files to analyze with possible overrides.  The returned format is

-     dict of format 'new_file' -> 'old_file'.

-     """

-     cmd = ['git', 'diff', '--name-status', '-C', options.compare_against,

-            '--numstat', '--relative', os.path.join(".", subdir)]

- 

-     log.debug("running: %s", " ".join(cmd))

-     # current file -> old_name

-     return_map = {}

-     output = check_output(cmd).decode('utf-8')

-     for line in output.split('\n'):

-         if not line:

-             continue

- 

-         parts = line.split('\t')

-         mode = parts[0]

-         if mode == '':

-             continue

-         if mode.startswith('R'):

-             log.debug("renamed: %s -> %s", parts[1], parts[2])

-             return_map[parts[2]] = parts[1]

-         elif mode.startswith('A'):

-             log.debug("new: %s", parts[1])

-             return_map[parts[1]] = None

-         elif mode == 'M':

-             log.debug("modified: %s", parts[1])

-             return_map[parts[1]] = parts[1]

-         else:

-             log.info("skipping diff mode %s for file %s", mode, parts[1])

- 

-     return return_map

- 

- 

- class _Worker:  # pylint: disable=too-few-public-methods

-     gitroot = None

-     # relative path within gitroot to the sub-project

-     projectdir = None

-     projectsubdir = None

-     workdir = None

-     checkout = None

-     linters = [PylintLinter]

- 

-     def __init__(self, options):

-         self.options = options

- 

-     def _analyze_projectdir(self):

-         """ find sub-directory in git repo which contains spec file """

-         gitroot = check_output(['git', 'rev-parse', '--show-toplevel'])

-         self.gitroot = gitroot.decode('utf-8').strip()

- 

-         checkout = check_output(['git', 'rev-parse',

-                                  self.options.compare_against])

-         self.checkout = checkout.decode('utf-8').strip()

- 

-         def rel_projdir(projdir):

-             gitroot_a = os.path.realpath(self.gitroot)

-             gitproj_a = os.path.realpath(projdir)

-             cwd_a = os.path.realpath(os.getcwd())

-             self.projectdir = gitproj_a.replace(gitroot_a + '/', '')

-             self.projectsubdir = (cwd_a + "/").replace(

-                 "{}/{}/".format(gitroot_a, self.projectdir), "")

-             log.debug("relative projectdir: %s", self.projectdir)

-             log.debug("project subdir: %s", self.projectsubdir)

- 

-         path = os.getcwd()

-         while True:

-             log.info("checking for projectdir: %s", path)

-             if os.path.realpath(path) == '/':

-                 raise Exception("project dir not found")

-             if os.path.isdir(os.path.join(path, '.git')):

-                 rel_projdir(path)

-                 return

-             if glob.glob(os.path.join(path, '*.spec')):

-                 rel_projdir(path)

-                 return

-             path = os.path.normpath(os.path.join(path, '..'))

- 

-     def _run_linters(self, old_report_fd, new_report_fd):

-         # pylint: disable=too-many-locals

-         lookup = get_rename_map(self.options, self.projectsubdir)

-         if not lookup:

-             return

- 

-         # prepare the old checkout

-         old_gitroot = os.path.join(self.workdir, 'old_dir')

-         origin_from = self.gitroot

-         check_call(['git', 'clone', '--quiet', origin_from, old_gitroot])

-         ret_cwd = os.getcwd()

-         try:

-             os.chdir(old_gitroot)

-             check_call(['git', 'checkout', '-q', self.checkout])

-         finally:

-             os.chdir(ret_cwd)

- 

-         def add_file(gitroot, files, filename):

-             if not filename:

-                 return

-             git_file = os.path.join(gitroot, self.projectdir, filename)

-             if not os.path.isfile(git_file):

-                 log.debug("skipping non-file %s", filename)

-                 return

-             file = lambda: None  # noqa: E731

-             file.filename = filename

-             file.type = file_type(git_file)

-             files.append(file)

- 

-         old_files = []

-         new_files = []

-         for filename in lookup:

-             add_file(self.gitroot, new_files, filename)

-             add_file(old_gitroot, old_files, lookup[filename])

- 

-         renames = []

-         for new in lookup:

-             old = lookup[new]

-             if old and old != new:

-                 renames.append((old, new))

- 

-         for LinterClass in self.linters:

-             linter_new = LinterClass(self.gitroot)

-             linter_old = LinterClass(old_gitroot, renames)

-             linter_new.lint(self.projectdir, new_files, logfd=new_report_fd)

-             linter_old.lint(self.projectdir, old_files, logfd=old_report_fd)

- 

-     def run(self):

-         """

-         Run all the 'self.linters' against old sources and new sources,

-         and provide the diff.

-         """

-         self._analyze_projectdir()

-         self.workdir = tempfile.mkdtemp(prefix='copr-linter-')

-         try:

-             old_report = os.path.join(self.workdir, 'old')

-             new_report = os.path.join(self.workdir, 'new')

- 

-             with open(old_report, 'w') as old, open(new_report, 'w') as new:

-                 oldcwd = os.getcwd()

-                 try:

-                     pd = os.path.join(self.gitroot, self.projectdir)

-                     log.debug("Switching to project directory %s", pd)

-                     os.chdir(pd)

-                     self._run_linters(old, new)

-                 finally:

-                     os.chdir(oldcwd)

- 

-             if self.options.print_fixed_errors:

-                 _run_csdiff(new_report, old_report, "Fixed warnings")

-                 print()

- 

-             sys.exit(_run_csdiff(old_report, new_report, "Added warnings"))

- 

-         finally:

-             log.debug("removing workdir %s", self.workdir)

-             if not self.options.no_cleanup:

-                 shutil.rmtree(self.workdir)

- 

- 

- def _get_arg_parser():

-     parser = argparse.ArgumentParser()

-     parser.add_argument(

-         "--compare-against",

-         default="origin/main",

-         help=("What git ref to diff the linters' results against.  Note "

-               "that the reference needs to be available in the current "

-               "git directory"))

-     parser.add_argument(

-         "--log-level",

-         default='error',

-         help="The python logging level, e.g. debug, error, ...")

-     parser.add_argument(

-         "--no-cleanup",

-         action='store_true',

-         default=False,

-         help=("Keep the temporary working directory, you can use the "

-               "--log-level=info switch to see where it is created"),

-     )

-     parser.add_argument(

-         "--print-fixed-errors",

-         action='store_true',

-         default=False,

-         help="Also print defects which were fixed by the changes",

-     )

-     return parser

- 

- 

- def _main():

-     options = _get_arg_parser().parse_args()

-     log.setLevel(level=getattr(logging, options.log_level.upper()))

-     worker = _Worker(options)

-     worker.run()

- 

- 

- if __name__ == "__main__":

-     _main()

@@ -11,4 +11,4 @@

  	./run_tests.sh -vv -s

  

  lint:

- 	../build_aux/linter

+ 	vcs-diff-lint

@@ -7,7 +7,9 @@

        name:

          - csdiff

          - git

+         - python3-mypy

          - pylint

+         - vcs-diff-lint

      become: yes

  

    - name: Run the differential pylint check

@@ -48,7 +48,7 @@

  run_linter()

  {

      package=$1

-     linter_cmd=(../build_aux/linter --print-fixed-errors --compare-against)

+     linter_cmd=(vcs-diff-lint --print-fixed-errors --compare-against)

      echo "==== Testing package $package modified by this PR ===="

      ( cd "$package" &&  "${linter_cmd[@]}" "$DIFF_AGAINST" )

  }

file modified
+1 -1
@@ -1,6 +1,6 @@

  include LICENSE

  include MANIFEST.in

  include copr-messaging.spec

- include runtests.sh

+ include run_tests.sh

  include requirements.txt

  recursive-include docs/ *

file added
+1
@@ -0,0 +1,1 @@

+ ../build_aux/per-subdir-makefile 

\ No newline at end of file

@@ -71,7 +71,7 @@

  

  

  %check

- ./runtests.sh -vv

+ ./run_tests.sh -vv

  

  

  %files -n python3-%name

messaging/run_tests.sh messaging/runtests.sh
file renamed
-5
@@ -2,11 +2,6 @@

  

  set -e

  

- test -f ../build_aux/linter && {

-     echo >&2 "running linter"

-     ../build_aux/linter

- }

- 

  dir=$(dirname "$(readlink -f "$0")")

  export PYTHONPATH=$dir:$dir/../common

  

The linter can actually live on it's own in Fedora, and can be used
easily elsewhere.

Build failed. More information on how to proceed and troubleshoot errors available at https://fedoraproject.org/wiki/Zuul-based-ci

rebased onto 16d6e55e435a8157c1082bde91fea42c0ed4383e

2 years ago

rebased onto da3b73edadd0326326a21a6456d38e1ac771cb85

2 years ago

Build failed. More information on how to proceed and troubleshoot errors available at https://fedoraproject.org/wiki/Zuul-based-ci

rebased onto 33b91a95aaa69820ed01d9943ddb13b0c6cc3eaa

2 years ago

Build failed. More information on how to proceed and troubleshoot errors available at https://fedoraproject.org/wiki/Zuul-based-ci

rebased onto be695ee2cbdb3f28b3da35d9db1fe70d2601bee0

2 years ago

Build failed. More information on how to proceed and troubleshoot errors available at https://fedoraproject.org/wiki/Zuul-based-ci

rebased onto edfea30

2 years ago

Build failed. More information on how to proceed and troubleshoot errors available at https://fedoraproject.org/wiki/Zuul-based-ci

I like this split into a separate package very much.
The tool is really useful and now other people will be able to use it.

But I think it should no longer be part of the Copr codebase and we should move it to a separate git repository. What do you think @praiskup?

Metadata Update from @praiskup:
- Pull-request tagged with: wip

2 years ago

I'll recycle this PR once we get the package into Fedora.

Metadata Update from @praiskup:
- Request assigned

2 years ago

can we close this PR since the package is released (https://src.fedoraproject.org/rpms/vcs-diff-lint) and it's moved to github?

I would prefer to recycle this pull-request ID, and start using vcs-diff-lint from Fedora.

Metadata Update from @praiskup:
- Request assignee reset

2 years ago

Metadata Update from @praiskup:
- Pull-request untagged with: wip

2 years ago

rebased onto acba6fcbdd27c3b3b3bcd5e089fb72410287936b

2 years ago

Build failed. More information on how to proceed and troubleshoot errors available at https://fedoraproject.org/wiki/Zuul-based-ci

rebased onto fb0d45c

2 years ago

Build succeeded.

2 new commits added

  • messaging: sync tooling with other sub-projects
  • build_aux: move the linter logic out from Copr tree
2 years ago

Build succeeded.

Commit 1d4ebcc fixes this pull-request

Pull-Request has been merged by praiskup

2 years ago

Commit dbef82c fixes this pull-request

Pull-Request has been merged by praiskup

2 years ago