def ninja_check_all_report(step: Step, _: Report, filter_output: bool): print('Full log will be available in Artifacts "ninja-check-all.log"', flush=True) step.reproduce_commands.append('ninja check-all') with open(f'{artifacts_dir}/ninja-check-all.log', 'wb') as f: w = sys.stdout.buffer.write if filter_output: r = re.compile( r'^(\[.*] (Building|Linking|Generating)|(PASS|XFAIL|UNSUPPORTED):)' ) w = partial(if_not_matches, write=sys.stdout.buffer.write, regexp=r) rc = watch_shell(partial(tee, write1=w, write2=f.write), partial(tee, write1=sys.stderr.buffer.write, write2=f.write), 'ninja check-all', cwd=build_dir) logging.debug(f'ninja check-all: returned {rc}') step.set_status_from_exit_code(rc) test_results_report.run(build_dir, 'test-results.xml', step, report) if not step.success: message = 'tests failed' f = report.test_stats['fail'] if f == 1: message = '1 test failed' if f > 1: message = f'{f} tests failed' report.add_artifact(artifacts_dir, 'ninja-check-all.log', message)
def cmake_report(projects: str, step: Step, _: Report): global build_dir cmake_result, build_dir, cmake_artifacts = run_cmake.run( projects, os.getcwd()) for file in cmake_artifacts: if os.path.exists(file): shutil.copy2(file, artifacts_dir) step.set_status_from_exit_code(cmake_result)
def ninja_all_report(step: Step, _: Report): print('Full log will be available in Artifacts "ninja-all.log"', flush=True) r = subprocess.run( f'ninja all | ' f'tee {artifacts_dir}/ninja-all.log | ' f'grep -vE "\\[.*] (Building|Linking|Linting|Copying|Generating|Creating)"', shell=True, cwd=build_dir) logging.debug(f'ninja all: returned {r.returncode}, stderr: "{r.stderr}"') step.set_status_from_exit_code(r.returncode)
def run_step(name: str, report: Report, thunk: Callable[[Step, Report], None]) -> Step: start = time.time() print(f'--- {name}', flush=True) # New section in Buildkite log. step = Step() step.name = name thunk(step, report) step.duration = time.time() - start # Expand section if it failed. if not step.success: print('^^^ +++', flush=True) report.steps.append(step) return step
def ninja_check_all_report(step: Step, _: Report): print('Full log will be available in Artifacts "ninja-check-all.log"', flush=True) r = subprocess.run( f'ninja check-all | tee {artifacts_dir}/ninja-check-all.log | ' f'grep -vE "^\\[.*] (Building|Linking)" | ' f'grep -vE "^(PASS|XFAIL|UNSUPPORTED):"', shell=True, cwd=build_dir) logging.debug( f'ninja check-all: returned {r.returncode}, stderr: "{r.stderr}"') step.set_status_from_exit_code(r.returncode) test_results_report.run(build_dir, 'test-results.xml', step, report)
def run(base_commit, ignore_config, step: Optional[Step], report: Optional[Report]): """Apply clang-format and return if no issues were found.""" if report is None: report = Report() # For debugging. if step is None: step = Step() # For debugging. r, patch = get_diff(base_commit) if not r: step.success = False return add_artifact = False patches = unidiff.PatchSet(patch) ignore_lines = [] if ignore_config is not None and os.path.exists(ignore_config): ignore_lines = open(ignore_config, 'r').readlines() ignore = pathspec.PathSpec.from_lines( pathspec.patterns.GitWildMatchPattern, ignore_lines) patched_file: unidiff.PatchedFile success = True for patched_file in patches: add_artifact = True if ignore.match_file(patched_file.source_file) or ignore.match_file( patched_file.target_file): logging.info(f'patch of {patched_file.patch_info} is ignored') continue hunk: unidiff.Hunk for hunk in patched_file: lines = [str(x) for x in hunk] success = False m = 10 # max number of lines to report. description = 'please reformat the code\n```\n' n = len(lines) cut = n > m + 1 if cut: lines = lines[:m] description += ''.join(lines) + '\n```' if cut: description += f'\n{n - m} diff lines are omitted. See full path.' report.add_lint({ 'name': 'clang-format', 'severity': 'autofix', 'code': 'clang-format', 'path': patched_file.source_file, 'line': hunk.source_start, 'char': 1, 'description': description, }) if add_artifact: patch_file = 'clang-format.patch' with open(patch_file, 'w') as f: f.write(patch) report.add_artifact(os.getcwd(), patch_file, 'clang-format') if not success: step.success = False step.messages.append( 'Please format your changes with clang-format by running `git-clang-format HEAD^` or applying patch.' ) logging.debug(f'report: {report}') logging.debug(f'step: {step}')
def ninja_all_report(step: Step, _: Report, filter_output: bool): print('Full log will be available in Artifacts "ninja-all.log"', flush=True) step.reproduce_commands.append('ninja all') with open(f'{artifacts_dir}/ninja-all.log', 'wb') as f: w = sys.stdout.buffer.write if filter_output: r = re.compile( r'^\[.*] (Building|Linking|Linting|Copying|Generating|Creating)' ) w = partial(if_not_matches, write=sys.stdout.buffer.write, regexp=r) rc = watch_shell(partial(tee, write1=w, write2=f.write), partial(tee, write1=sys.stderr.buffer.write, write2=f.write), 'ninja all', cwd=build_dir) logging.debug(f'ninja all: returned {rc}') step.set_status_from_exit_code(rc) if not step.success: report.add_artifact(artifacts_dir, 'ninja-all.log', 'build failed')
def run(working_dir: str, test_results: str, step: Optional[Step], report: Optional[Report]): if report is None: report = Report() # For debugging. if step is None: step = Step() path = os.path.join(working_dir, test_results) if not os.path.exists(path): logging.warning(f'{path} is not found') step.success = False step.messages.append(f'test report "{path}" is not found') return report.add_artifact(working_dir, test_results, 'test results') try: success = True root_node = etree.parse(path) for test_case in root_node.xpath('//testcase'): test_result = 'pass' if test_case.find('failure') is not None: test_result = 'fail' if test_case.find('skipped') is not None: test_result = 'skip' report.test_stats[test_result] += 1 if test_result == 'fail': success = False failure = test_case.find('failure') test_result = { 'name': test_case.attrib['name'], 'namespace': test_case.attrib['classname'], 'result': test_result, 'duration': float(test_case.attrib['time']), 'details': failure.text } report.unit.append(test_result) msg = f'{report.test_stats["pass"]} tests passed, {report.test_stats["fail"]} failed and ' \ f'{report.test_stats["skip"]} were skipped.\n' if not success: step.success = False for test_case in report.unit: if test_case['result'] == 'fail': msg += f'{test_case["namespace"]}/{test_case["name"]}\n' except Exception as e: logging.error(e) step.messages.append('Parsing of test results failed') step.success = False logging.debug(f'report: {report}') logging.debug(f'step: {step}')
def run(base_commit, ignore_config, step: Optional[Step], report: Optional[Report]): """Apply clang-format and return if no issues were found.""" if report is None: report = Report() # For debugging. if step is None: step = Step() # For debugging. r = subprocess.run(f'git diff -U0 --no-prefix {base_commit}', shell=True, capture_output=True) logging.debug(f'git diff {r}') diff = r.stdout.decode() if ignore_config is not None and os.path.exists(ignore_config): ignore = pathspec.PathSpec.from_lines( pathspec.patterns.GitWildMatchPattern, open(ignore_config, 'r').readlines()) diff = ignore_diff.remove_ignored(diff.splitlines(keepends=True), open(ignore_config, 'r')) logging.debug(f'filtered diff: {diff}') else: ignore = pathspec.PathSpec.from_lines( pathspec.patterns.GitWildMatchPattern, []) p = subprocess.Popen(['clang-tidy-diff', '-p0', '-quiet'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) step.reproduce_commands.append( f'git diff -U0 --no-prefix {base_commit} | clang-tidy-diff -p0') a = ''.join(diff) logging.info(f'clang-tidy input: {a}') out = p.communicate(input=a.encode())[0].decode() logging.debug(f'clang-tidy-diff {p}: {out}') # Typical finding looks like: # [cwd/]clang/include/clang/AST/DeclCXX.h:3058:20: error: ... [clang-diagnostic-error] pattern = '^([^:]*):(\\d+):(\\d+): (.*): (.*)' add_artifact = False logging.debug("cwd", os.getcwd()) errors_count = 0 warn_count = 0 inline_comments = 0 for line in out.splitlines(keepends=False): line = line.strip() line = line.replace(os.getcwd() + os.sep, '') logging.debug(line) if len(line) == 0 or line == 'No relevant changes found.': continue add_artifact = True match = re.search(pattern, line) if match: file_name = match.group(1) line_pos = match.group(2) char_pos = match.group(3) severity = match.group(4) text = match.group(5) text += '\n[[{} | not useful]] '.format( 'https://github.com/google/llvm-premerge-checks/blob/master/docs/clang_tidy.md#warning-is-not-useful' ) if severity in ['warning', 'error']: if severity == 'warning': warn_count += 1 if severity == 'error': errors_count += 1 if ignore.match_file(file_name): print( '{} is ignored by pattern and no comment will be added' .format(file_name)) else: inline_comments += 1 report.add_lint({ 'name': 'clang-tidy', 'severity': 'warning', 'code': 'clang-tidy', 'path': file_name, 'line': int(line_pos), 'char': int(char_pos), 'description': '{}: {}'.format(severity, text), }) else: logging.debug('does not match pattern') if add_artifact: p = 'clang-tidy.txt' with open(p, 'w') as f: f.write(out) report.add_artifact(os.getcwd(), p, 'clang-tidy') if errors_count + warn_count != 0: step.success = False url = format_url( "https://github.com/google/llvm-premerge-checks/blob/master/docs/clang_tidy.md" "#review-comments.", "why?") step.messages.append( f'clang-tidy found {errors_count} errors and {warn_count} warnings. {inline_comments} of them are added ' f'as review comments {url}') logging.debug(f'report: {report}') logging.debug(f'step: {step}')