diff --git a/python/mozlint/mozlint/cli.py b/python/mozlint/mozlint/cli.py index 63e36a8e209b..3decf3c063fd 100644 --- a/python/mozlint/mozlint/cli.py +++ b/python/mozlint/mozlint/cli.py @@ -42,10 +42,13 @@ class MozlintParser(ArgumentParser): 'help': "Display and fail on warnings in addition to errors.", }], [['-f', '--format'], - {'dest': 'fmt', - 'default': 'stylish', - 'choices': all_formatters.keys(), - 'help': "Formatter to use. Defaults to 'stylish'.", + {'dest': 'formats', + 'action': 'append', + 'help': "Formatter to use. Defaults to 'stylish' on stdout. " + "You can specify an optional path as --format formatter:path " + "that will be used instead of stdout. " + "You can also use multiple formatters at the same time. " + "Formatters available: {}.".format(', '.join(all_formatters.keys())), }], [['-n', '--no-filter'], {'dest': 'use_filters', @@ -126,6 +129,29 @@ class MozlintParser(ArgumentParser): if invalid: self.error("the following paths do not exist:\n{}".format("\n".join(invalid))) + if args.formats: + formats = [] + for fmt in args.formats: + path = None + if ':' in fmt: + # Detect optional formatter path + pos = fmt.index(':') + fmt, path = fmt[:pos], os.path.realpath(fmt[pos+1:]) + + # Check path is writable + fmt_dir = os.path.dirname(path) + if not os.access(fmt_dir, os.W_OK | os.X_OK): + self.error("the following directory is not writable: {}".format(fmt_dir)) + + if fmt not in all_formatters.keys(): + self.error('the following formatter is not available: {}'.format(fmt)) + + formats.append((fmt, path)) + args.formats = formats + else: + # Can't use argparse default or this choice will be always present + args.formats = [('stylish', None)] + def find_linters(linters=None): lints = [] @@ -150,7 +176,7 @@ def find_linters(linters=None): return lints -def run(paths, linters, fmt, outgoing, workdir, edit, +def run(paths, linters, formats, outgoing, workdir, edit, setup=False, list_linters=False, **lintargs): from mozlint import LintRoller, formatters from mozlint.editor import edit_issues @@ -177,13 +203,16 @@ def run(paths, linters, fmt, outgoing, workdir, edit, edit_issues(result) result = lint.roll(result.issues.keys()) - formatter = formatters.get(fmt) + for formatter_name, path in formats: + formatter = formatters.get(formatter_name) + + # Encode output with 'replace' to avoid UnicodeEncodeErrors on + # environments that aren't using utf-8. + out = formatter(result).encode(sys.stdout.encoding or 'ascii', 'replace') + if out: + output_file = open(path, 'w') if path else sys.stdout + print(out, file=output_file) - # Encode output with 'replace' to avoid UnicodeEncodeErrors on - # environments that aren't using utf-8. - out = formatter(result).encode(sys.stdout.encoding or 'ascii', 'replace') - if out: - print(out) return result.returncode diff --git a/taskcluster/ci/source-test/mozlint.yml b/taskcluster/ci/source-test/mozlint.yml index e433f668ff1d..384679549906 100644 --- a/taskcluster/ci/source-test/mozlint.yml +++ b/taskcluster/ci/source-test/mozlint.yml @@ -9,6 +9,10 @@ job-defaults: worker: docker-image: {in-tree: "lint"} max-run-time: 1800 + artifacts: + - type: file + name: public/code-review/mozlint.json + path: /builds/worker/mozlint.json run: using: mach when: @@ -23,7 +27,7 @@ codespell: treeherder: symbol: spell run: - mach: lint -l codespell -f treeherder + mach: lint -l codespell -f treeherder -f json:/builds/worker/mozlint.json when: files-changed: - '**/*.js' @@ -46,7 +50,7 @@ cpp-virtual-final: treeherder: symbol: Cvf run: - mach: lint -l cpp-virtual-final -f treeherder + mach: lint -l cpp-virtual-final -f treeherder -f json:/builds/worker/mozlint.json when: files-changed: - '**/*.cpp' @@ -65,7 +69,7 @@ eslint: cp -r /build/node_modules_eslint node_modules && ln -s ../tools/lint/eslint/eslint-plugin-mozilla node_modules && ln -s ../tools/lint/eslint/eslint-plugin-spidermonkey-js node_modules && - ./mach lint -l eslint -f treeherder --quiet + ./mach lint -l eslint -f treeherder --quiet -f json:/builds/worker/mozlint.json when: files-changed: # Files that are likely audited. @@ -88,7 +92,7 @@ mingw-cap: treeherder: symbol: mingw run: - mach: lint -l mingw-capitalization -f treeherder + mach: lint -l mingw-capitalization -f treeherder -f json:/builds/worker/mozlint.json when: files-changed: - '**/*.cpp' @@ -102,7 +106,7 @@ py-compat: treeherder: symbol: py-compat run: - mach: lint -l py2 -l py3 -f treeherder + mach: lint -l py2 -l py3 -f treeherder -f json:/builds/worker/mozlint.json when: files-changed: - '**/*.py' @@ -114,7 +118,7 @@ py-flake8: treeherder: symbol: f8 run: - mach: lint -l flake8 -f treeherder + mach: lint -l flake8 -f treeherder -f json:/builds/worker/mozlint.json when: files-changed: - '**/*.py' @@ -128,7 +132,7 @@ test-manifest: treeherder: symbol: tm run: - mach: lint -l test-disable -f treeherder + mach: lint -l test-disable -f treeherder -f json:/builds/worker/mozlint.json when: files-changed: - '**/*.ini' @@ -141,7 +145,7 @@ wptlint-gecko: treeherder: symbol: W run: - mach: lint -l wpt -f treeherder + mach: lint -l wpt -f treeherder -f json:/builds/worker/mozlint.json when: files-changed: - 'testing/web-platform/tests/**' @@ -153,7 +157,7 @@ yaml: treeherder: symbol: yaml run: - mach: lint -l yaml -f treeherder + mach: lint -l yaml -f treeherder -f json:/builds/worker/mozlint.json when: files-changed: - '**/*.yml' @@ -166,7 +170,7 @@ shellcheck: treeherder: symbol: shell run: - mach: lint -l shellcheck -f treeherder + mach: lint -l shellcheck -f treeherder -f json:/builds/worker/mozlint.json when: files-changed: - '**/*.sh'