diff --git a/.dockerignore b/.dockerignore index ff01e40a3e69dc6abe9adbda5ebc1ff846c847e5..9b4802e37c156381623e45fe2f72c5e3f0e12381 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,2 @@ .eggs/ +.venv*/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a30b5deacb3bedc96308cce5a346528667520980..1c92ece0f302538a439437c7eb4b7cd91167d132 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,14 +4,9 @@ stages: - publish - deploy -variables: &global - BUILD_TAG: ${CI_REGISTRY_IMAGE}/build/${CI_COMMIT_REF_SLUG}:${CI_PIPELINE_IID} - LATEST_TAG: ${CI_REGISTRY_IMAGE}/build/${CI_COMMIT_REF_SLUG}:latest - .python: image: python:slim variables: - <<: *global PYTHONPATH: . PIP_CACHE_DIR: $CI_PROJECT_DIR/.cache/pip cache: @@ -56,7 +51,12 @@ lint:publish: .docker: image: docker:stable variables: - <<: *global + BUILD_TAG: ${CI_REGISTRY_IMAGE}/pipeline:${CI_PIPELINE_IID} + BUILD_TAG_COMMIT: ${CI_REGISTRY_IMAGE}/commit:${CI_COMMIT_SHORT_SHA} + BUILD_TAG_REF: ${CI_REGISTRY_IMAGE}/ref/${CI_COMMIT_REF_SLUG}:${CI_PIPELINE_IID} + BUILD_TAG_LATEST: ${CI_REGISTRY_IMAGE}/ref/${CI_COMMIT_REF_SLUG}:latest + RELEASE_TAG: ${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG#v} + RELEASE_TAG_LATEST: ${CI_REGISTRY_IMAGE}:latest DOCKER_HOST: "tcp://docker:2375/" DOCKER_DRIVER: overlay2 DOCKER_TLS_CERTDIR: "" @@ -73,7 +73,11 @@ build: except: [ master ] script: - docker build . --pull=true --tag=${BUILD_TAG} + - docker tag ${BUILD_TAG} ${BUILD_TAG_COMMIT} - docker push ${BUILD_TAG} + - docker push ${BUILD_TAG_COMMIT} + - test -z "${CI_COMMIT_TAG}" && docker tag ${BUILD_TAG} ${BUILD_TAG_REF} + - test -z "${CI_COMMIT_TAG}" && docker push ${BUILD_TAG_REF} deploy:branch: stage: deploy @@ -81,8 +85,8 @@ deploy:branch: except: [ tags, master ] script: - docker pull ${BUILD_TAG} - - docker tag ${BUILD_TAG} ${LATEST_TAG} - - docker push ${LATEST_TAG} + - docker tag ${BUILD_TAG} ${BUILD_TAG_LATEST} + - docker push ${BUILD_TAG_LATEST} deploy:tag: stage: deploy @@ -93,7 +97,7 @@ deploy:tag: - /^v[^0-9]/ script: - docker pull ${BUILD_TAG} - - docker tag ${BUILD_TAG} ${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG#v} - - docker tag ${BUILD_TAG} ${CI_REGISTRY_IMAGE}:latest - - docker push ${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG#v} - - docker push ${CI_REGISTRY_IMAGE}:latest + - docker tag ${BUILD_TAG} ${RELEASE_TAG} + - docker tag ${BUILD_TAG} ${RELEASE_TAG_LATEST} + - docker push ${RELEASE_TAG} + - docker push ${RELEASE_TAG_LATEST} diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8c94ee0d6babc1e86e655cfea7020437a063c942 --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,7 @@ +- id: pylint + name: PyLint + entry: pylint-reporter run + language: python + require_serial: true + types: [python] + verbose: true diff --git a/Dockerfile b/Dockerfile index 4c58f210365ffac3be7a8853567104cd3c7d05b0..1a65c14d7a90ac140954df706ff06fc8df5f2380 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,3 +4,6 @@ ARG PY_VERSION=3.7 FROM python:$PY_VERSION RUN --mount=type=bind,rw,target=/src \ pip install file:///src/#egg=pylint_reporter[badges] +RUN apt-get update && apt-get install -y \ + build-essential \ + python3-dev diff --git a/pylint_reporter/main.py b/pylint_reporter/main.py index 73af639300d438acbdd5cf811e62aa7f8de2fc41..fe29cf5a238f2626e01c4ebd37400d022a38f740 100644 --- a/pylint_reporter/main.py +++ b/pylint_reporter/main.py @@ -1,5 +1,5 @@ # -# Copyright 2019 Dominik Sekotill +# Copyright 2019,2020 Dominik Sekotill # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,18 +19,36 @@ CLI command and subcommands import argparse import json +import subprocess import sys +from pylint import lint + from . import badge, errors, reporter +def set_command(parser, cmd): + parser.set_defaults(cmd=cmd, parser=parser) + + def main(argv=None): """Script and runpy entrypoint""" parser = argparse.ArgumentParser(__package__) subparsers = parser.add_subparsers(required=True) - score_parser = subparsers.add_parser('score') - score_parser.set_defaults(cmd=cmd_score) + run_parser = subparsers.add_parser( + 'run', + help=cmd_run.__doc__, + usage=f'{parser.prog} run [options] PYLINT-ARGUMENT [PYLINT-ARGUMENT ...]', + ) + run_parser.add_argument( + '--extras', '-e', + action='append', + help="The names of optional requirements sets to install", + ) + set_command(run_parser, cmd_run) + + score_parser = subparsers.add_parser('score', help=cmd_score.__doc__) score_parser.add_argument( '--minimum', '--min', type=float, @@ -48,14 +66,9 @@ def main(argv=None): A Pylint report serialised as a JSON file """ ) + set_command(score_parser, cmd_score) - badge_parser = subparsers.add_parser( - 'badge', - help=""" - Generate a shield.io badge to display the score - """ - ) - badge_parser.set_defaults(cmd=cmd_badge) + badge_parser = subparsers.add_parser('badge', help=cmd_badge.__doc__) badge_parser.add_argument( 'input', nargs='?', @@ -76,8 +89,10 @@ def main(argv=None): Save the badge to this file name. """ ) + set_command(badge_parser, cmd_badge) - args = parser.parse_args(argv or sys.argv[1:]) + args, additional = parser.parse_known_args(argv or sys.argv[1:]) + args.additional = additional try: args.cmd(args) @@ -85,7 +100,27 @@ def main(argv=None): parser.exit(3, str(exc) + '\n') -def cmd_score(args) -> int: +def cmd_run(args): + """ + Install dependencies in the current environment and run pylint on the given files + """ + if not args.additional: + args.parser.error("at least one file to lint is required") + + cmd = ['pip', 'install', '-e', '.'] + if args.extras: + cmd.append(".[{0}]".format(','.join(args.extras))) + + proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + if proc.returncode: + args.parser.exit(3, proc.stdout) + + cmd = ['--load-plugins=pylint_reporter.reporter'] + cmd.extend(args.additional) + lint.Run(cmd) + + +def cmd_score(args): """ Display the linter score, and optionally compare it with a minimum score """ @@ -95,13 +130,13 @@ def cmd_score(args) -> int: print("Lint Score: {0:.2f}".format(score)) if args.minimum: - return int(score < args.minimum) - - return 0 + args.parser.exit(int(score < args.minimum)) -def cmd_badge(args) -> int: - """Make a lint score badge from lint stats""" +def cmd_badge(args): + """ + Generate a shield.io badge to display the score + """ with args.input as fh: results = json.load(fh) score = reporter.calculate_score(results['stats']) diff --git a/pylint_reporter/reporter.py b/pylint_reporter/reporter.py index 7b4b9984a0c9d606fb364cacb02b20cb02d4956b..8c53c2858c2bcb45fd928b85c52d72219033063c 100644 --- a/pylint_reporter/reporter.py +++ b/pylint_reporter/reporter.py @@ -29,31 +29,31 @@ class JSONStatsReporter: """ def __init__(self, options, parent=None): + self.parent_reporter = parent or pylint.reporters.BaseReporter() + self.json_reporter = pylint.reporters.JSONReporter(parent.out if parent else None) self.options = options - self.parent = parent or pylint.reporters.BaseReporter() - self.messages = [] def __getattr__(self, name): - return getattr(self.parent, name) + return getattr(self.parent_reporter, name) @property def linter(self): """Proxy of the parent's linter""" - return self.parent.linter + return self.parent_reporter.linter @linter.setter def linter(self, linter): - self.parent.linter = linter + self.parent_reporter.linter = linter def handle_message(self, msg): """Lint message handling callback""" - self.messages.append(msg) - self.parent.handle_message(msg) + self.json_reporter.handle_message(msg) + self.parent_reporter.handle_message(msg) def on_close(self, stats, previous_stats): """Post-linting callback; save the lint data as a JSON file""" report = dict( - messages=self.messages, + messages=self.json_reporter.messages, stats=stats, previous=previous_stats, )