diff --git a/.codeclimate.yml b/.codeclimate.yml index 147d6a543..ee811077a 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,50 +1,50 @@ checks: duplicate: enabled: true exclude_patterns: - "test/" - "examples/" structure: enabled: true exclude_patterns: - "test/" plugins: editorconfig: enabled: false config: editorconfig: .editorconfig exclude_patterns: - ".clangd/" - ".cache/" pep8: enabled: true exclude_patterns: - "test/test_fe_engine/py_engine/py_engine.py" cppcheck: enabled: false project: compile_commands.json language: c++ check: warning, style, performance stds: [c++14] fixme: enabled: true exclude_patterns: - "doc/" clang-tidy: enabled: true extra-arg: - -std=c++14 - -Ithird-party/akantu_iterators/include - - -I/code/third-party/akantu_iterators/include - - -I/code/third-party/iohelper/src - - -I/code/test/ci/includes_for_ci + - -Ithird-party/iohelper/src + - -Itest/ci/includes_for_ci exclude_patterns: - "test/" - "cmake/" - "examples/" - "extra_packages/igfem/" + - "extra_packages/extra-materials/" exclude_patterns: - "third-party/" - "build*/" diff --git a/test/ci/codeclimate/codeclimate-clang-tidy/Dockerfile b/test/ci/codeclimate/codeclimate-clang-tidy/Dockerfile index b412dad58..90200e79c 100644 --- a/test/ci/codeclimate/codeclimate-clang-tidy/Dockerfile +++ b/test/ci/codeclimate/codeclimate-clang-tidy/Dockerfile @@ -1,25 +1,25 @@ FROM alpine:edge LABEL maintainer "Nicolas Richart " WORKDIR /usr/src/app RUN apk --update add --no-cache --upgrade \ clang\ clang-extra-tools \ g++ \ musl-dev \ boost-dev \ python3 \ - py3-lxml && \ + py3-termcolor && \ rm -rf /usr/share/ri && \ adduser -u 9000 -D -s /bin/false app #COPY engine.json / COPY . ./ RUN chown -R app:app ./ USER app VOLUME /code WORKDIR /code CMD ["/usr/src/app/bin/codeclimate-clang-tidy"] diff --git a/test/ci/codeclimate/codeclimate-clang-tidy/lib/command.py b/test/ci/codeclimate/codeclimate-clang-tidy/lib/command.py index edd6a3612..8e5141f14 100644 --- a/test/ci/codeclimate/codeclimate-clang-tidy/lib/command.py +++ b/test/ci/codeclimate/codeclimate-clang-tidy/lib/command.py @@ -1,75 +1,73 @@ -import os import json -import sys +import os +import re class Command: """Returns command line arguments by parsing codeclimate config file.""" - def __init__(self, config, file_list): + def __init__(self, config, workspace): self.config = config - self.file_list = file_list + self._workspace = workspace def build(self): command = ['/usr/src/app/bin/run-clang-tidy', '-clang-tidy-binary', '/usr/bin/clang-tidy'] if 'checks' in self.config: command.extend( ['-checks', self.config["checks"]]) if 'config' in self.config: command.extend( ['-config', self.config["config"]]) if 'header-filter' in self.config: command.extend( ['-header-filter', self.config["header-file"]]) extra_args = [] if 'extra-arg' in self.config: - extra_args = self.config['extra-arg'] + tmp_extra_args = self.config['extra-arg'] if not isinstance(extra_args, list): - extra_args = [extra_args] + tmp_extra_args = [extra_args] + + extra_args = [] + includes_re = re.compile(r'-I(.*)') + for arg in tmp_extra_args: + match = includes_re.match(arg) + if match: + path = os.path.abspath(match.group(1)) + extra_args.append(f'-I{path}') + else: + extra_args.append(arg) if 'compilation-database-path' in self.config: for arg in extra_args: command.extend(['-extra-arg', arg]) - # if 'line-filter' in self.config: - # command.append( - # f'--line-filter={json.dumps(self.config["line-filter"])}') - - # if 'system-headers' in self.config: - # command.append('--system-headers') - if 'compilation-database-path' in self.config: command.extend( ['-p', self.config['compilation-database-path']]) else: - include_paths = [] - for file_ in self.file_list: - include_paths.append(os.path.dirname(file_)) - include_paths = list(set(include_paths)) - - include_flags = ' -I'.join(include_paths) + include_flags = ' -I'.join(self._workspace.include_paths) compile_commands = [] - for file_ in self.file_list: + for file_ in self._workspace.files: cmd = { 'directory': os.path.dirname(file_), 'file': file_, 'command': f'/usr/bin/clang++ {include_flags} {" ".join(extra_args)} -c {file_} -o dummy.o', # noqa } compile_commands.append(cmd) location = '/tmp' compile_database = os.path.join(location, 'compile_commands.json') with open(compile_database, 'w') as db: json.dump(compile_commands, db) command.extend(['-p', location]) - command.extend(self.file_list) + command.extend([f'{path}*' for path in self._workspace.paths]) return command diff --git a/test/ci/codeclimate/codeclimate-clang-tidy/lib/runner.py b/test/ci/codeclimate/codeclimate-clang-tidy/lib/runner.py index a8b6bb77f..f64028f3e 100644 --- a/test/ci/codeclimate/codeclimate-clang-tidy/lib/runner.py +++ b/test/ci/codeclimate/codeclimate-clang-tidy/lib/runner.py @@ -1,94 +1,101 @@ import json import subprocess import sys import re -import tempfile +import os +try: + from termcolor import colored +except ImportError: + def colored(text, color): + return text from command import Command from issue_formatter import IssueFormatter from workspace import Workspace class Runner: CONFIG_FILE_PATH = '/config.json' """Runs clang-tidy, collects and reports results.""" def __init__(self): self._config_file_path = self.CONFIG_FILE_PATH self._config = {} self._decode_config() self._ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') self._issue_parse = re.compile(r'(?P.*\.(cc|hh)):(?P[0-9]+):(?P[0-9]+): (warning|error): (?P.*) \[(?P.*)\]') # noqa + self._issues_fpr = [] + self._workspace = Workspace(self._config.get('include_paths', [])) + self._files = self._workspace.files + self._include_paths = self._workspace.include_paths def run(self): - workspace_files = self._workspace.calculate() - if not len(workspace_files) > 0: + if not len(self._files) > 0: return - self._print_debug(f'[clang-tidy] analyzing {len(workspace_files)} files') + self._print_debug(f'[clang-tidy] analyzing {len(self._files)} files') + command = Command(self._config, self._workspace).build() - command = Command(self._config, workspace_files).build() self._print_debug(f'[clang-tidy] command: {command}') - self._generate_issues(command) def _decode_config(self): self._print_debug(f"Decoding config file {self._config_file_path}") contents = "" with open(self._config_file_path, "r") as config: contents = config.read() self._config = json.loads(contents) self._print_debug(f'[clang-tidy] config: {self._config}') - def _run_command(self, command): - popen = subprocess.Popen(command, - stdout=subprocess.PIPE, - universal_newlines=True) - - for stdout_line in iter(popen.stdout.readline, ""): - yield stdout_line - - popen.stdout.close() - - return_code = popen.wait() - if return_code: - raise subprocess.CalledProcessError(return_code, command) - def _print_issue(self, issue): issue_ = IssueFormatter(issue).format() - if not issue_: - return - if not self._workspace.should_include(issue_["location"]["path"]): + path = os.path.dirname(os.path.abspath(issue_["location"]["path"])) + if path not in self._include_paths: return if issue_['fingerprint'] in self._issues_fpr: return self._issues_fpr.append(issue_['fingerprint']) print('{}\0'.format(json.dumps(issue_))) def _generate_issues(self, command): issue = None for line in self._run_command(command): clean_line = self._ansi_escape.sub('', line) match = self._issue_parse.match(clean_line) if match: - if issue: + if issue is not None: self._print_issue(issue) issue = match.groupdict() elif issue: if 'content' in issue: issue['content'].append(line) else: issue['content'] = [line] - self._print_issue(issue) + def _run_command(self, command): + popen = subprocess.Popen(command, + stdout=subprocess.PIPE, + universal_newlines=True) + + for stdout_line in iter(popen.stdout.readline, ""): + yield stdout_line + + popen.stdout.close() + + return_code = popen.wait() + if return_code: + self._print_debug( + f"[clang-tidy] {command} ReturnCode {return_code}") + # raise subprocess.CalledProcessError(return_code, command) + def _print_debug(self, message): - if 'debug' in self._config: + if 'debug' in self._config and self._config['debug'] == 1: print(message, file=sys.stderr) diff --git a/test/ci/codeclimate/codeclimate-clang-tidy/lib/workspace.py b/test/ci/codeclimate/codeclimate-clang-tidy/lib/workspace.py index aec07b669..29685c3db 100644 --- a/test/ci/codeclimate/codeclimate-clang-tidy/lib/workspace.py +++ b/test/ci/codeclimate/codeclimate-clang-tidy/lib/workspace.py @@ -1,33 +1,40 @@ import os -SRC_SUFFIX = ['.c', '.cpp', '.cc', '.cxx'] - class Workspace: - def __init__(self, include_paths): - self.include_paths = include_paths - - def calculate(self): + def __init__(self, include_paths, suffixes=['.c', '.cpp', '.cc', '.cxx']): + self._include_paths = include_paths + self._suffixes = suffixes + + @property + def files(self): + paths = self._walk() + return [path for path in paths + if self._should_include(path)] + + @property + def include_paths(self): + paths = self._walk() + return [path for path in paths if os.path.isdir(path)] + + @property + def paths(self): + return [path for path in self._include_paths + if self._should_include(path) or os.path.isdir(path)] + + def _should_include(self, name): + _, ext = os.path.splitext(name) + return ext in self._suffixes + + def _walk(self): paths = [] - for path in self.include_paths: + for path in self._include_paths: if os.path.isdir(path): - paths.extend(self._walk(path)) - elif self.should_include(path): + for root, dirs, files in os.walk(path): + paths.extend([os.path.join(root, dir_) for dir_ in dirs]) + paths.extend([os.path.join(root, file_) for file_ in files]) + else: paths.append(path) - return [os.path.join('/code', path) for path in paths] - - def should_include(self, name): - return name.lower().endswith(tuple(SRC_SUFFIX)) - - def _walk(self, path): - paths = [] - - for root, _directories, files in os.walk(path): - for name in files: - if self.should_include(name): - path = os.path.join(root, name) - paths.append(path) - - return paths + return [os.path.abspath(path) for path in paths]