forked from vsf-linux/wasm-micro-runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enable to trigger code guideline checks when PR is created (bytecodea…
…lliance#790) Enable to trigger code guideline checks for C/C++ source codes when PR is created
- Loading branch information
Showing
3 changed files
with
313 additions
and
263 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Copyright (C) 2019 Intel Corporation. All rights reserved. | ||
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
name: Coding Guidelines | ||
|
||
on: | ||
# will be triggered on PR events | ||
pull_request: | ||
# allow to be triggered manually | ||
workflow_dispatch: | ||
|
||
# Cancel any in-flight jobs for the same PR/branch so there's only one active | ||
# at a time | ||
concurrency: | ||
group: ${{ github.workflow }}-${{ github.ref }} | ||
cancel-in-progress: true | ||
|
||
jobs: | ||
# Cancel any in-flight jobs for the same PR/branch so there's only one active | ||
# at a time | ||
cancel_previous: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Cancel Workflow Action | ||
uses: styfle/cancel-workflow-action@0.6.0 | ||
with: | ||
access_token: ${{ github.token }} | ||
|
||
complinace_job: | ||
needs: cancel_previous | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: checkout | ||
uses: actions/checkout@v2 | ||
with: | ||
fetch-depth: 0 | ||
|
||
# github.event.pull_request.base.label = ${{github.repository}}/${{github.base_ref}} | ||
- name: Run Coding Guidelines Checks | ||
run: /usr/bin/env python3 ./ci/coding_guidelines_check.py --commits ${{ github.event.pull_request.base.sha }}..HEAD |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,274 @@ | ||
#!/usr/bin/env python3 | ||
# | ||
# Copyright (C) 2019 Intel Corporation. All rights reserved. | ||
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
# | ||
import argparse | ||
import re | ||
import pathlib | ||
import re | ||
import shlex | ||
import shutil | ||
import subprocess | ||
import sys | ||
|
||
CLANG_FORMAT_CMD = "clang-format-12" | ||
GIT_CLANG_FORMAT_CMD = "git-clang-format-12" | ||
|
||
# glob style patterns | ||
EXCLUDE_PATHS = [ | ||
"**/.git/*", | ||
"**/.github/*", | ||
"**/.vscode/*", | ||
"**/assembly-script/*", | ||
"**/build/*", | ||
"**/build-scripts/*", | ||
"**/ci/*", | ||
"**/core/deps/*", | ||
"**/doc/*", | ||
"**/samples/wasm-c-api/src/*.*", | ||
"**/samples/workload/*", | ||
"**/test-tools/wasi-sdk/*", | ||
"**/tests/wamr-test-suites/workspace/*", | ||
"**/wamr-sdk/*", | ||
] | ||
|
||
C_SUFFIXES = [".c", ".cpp", ".h"] | ||
INVALID_DIR_NAME_SEGMENT = r"([a-zA-Z0-9]+\_[a-zA-Z0-9]+)" | ||
INVALID_FILE_NAME_SEGMENT = r"([a-zA-Z0-9]+\-[a-zA-Z0-9]+)" | ||
|
||
|
||
def locate_command(command: str) -> bool: | ||
if not shutil.which(command): | ||
print(f"Command '{command}'' not found") | ||
return False | ||
|
||
return True | ||
|
||
|
||
def is_excluded(path: str) -> bool: | ||
path = pathlib.Path(path).resolve() | ||
for exclude_path in EXCLUDE_PATHS: | ||
if path.match(exclude_path): | ||
return True | ||
return False | ||
|
||
|
||
def pre_flight_check(root: pathlib) -> bool: | ||
def check_aspell(root): | ||
return True | ||
|
||
def check_clang_foramt(root: pathlib) -> bool: | ||
if not locate_command(CLANG_FORMAT_CMD): | ||
return False | ||
|
||
# Quick syntax check for .clang-format | ||
try: | ||
subprocess.check_output( | ||
shlex.split(f"{CLANG_FORMAT_CMD} --dump-config"), cwd=root | ||
) | ||
except subprocess.CalledProcessError: | ||
print(f"Might have a typo in .clang-format") | ||
return False | ||
return True | ||
|
||
def check_git_clang_format() -> bool: | ||
return locate_command(GIT_CLANG_FORMAT_CMD) | ||
|
||
return check_aspell(root) and check_clang_foramt(root) and check_git_clang_format() | ||
|
||
|
||
def run_clang_format(file_path: pathlib, root: pathlib) -> bool: | ||
try: | ||
subprocess.check_call( | ||
shlex.split( | ||
f"{CLANG_FORMAT_CMD} --style=file --Werror --dry-run {file_path}" | ||
), | ||
cwd=root, | ||
) | ||
return True | ||
except subprocess.CalledProcessError: | ||
print(f"{file_path} failed the check of {CLANG_FORMAT_CMD}") | ||
return False | ||
|
||
|
||
def run_clang_format_diff(root: pathlib, commits: str) -> bool: | ||
""" | ||
Use `clang-format-12` and `git-clang-format-12` to check code | ||
format of the PR, which specificed a commit range. It is required to | ||
format code before `git commit` or when failed the PR check: | ||
``` shell | ||
cd path/to/wamr/root | ||
clang-format-12 --style file -i path/to/file | ||
``` | ||
The code wrapped by `/* clang-format off */` and `/* clang-format on */` | ||
will not be formatted, you shall use them when the formatted code is not | ||
readable or friendly: | ||
``` cc | ||
/* clang-format off */ | ||
code snippets | ||
/* clang-format on */ | ||
``` | ||
""" | ||
try: | ||
before, after = commits.split("..") | ||
after = after if after else "HEAD" | ||
COMMAND = ( | ||
f"{GIT_CLANG_FORMAT_CMD} -v --binary " | ||
f"{shutil.which(CLANG_FORMAT_CMD)} --style file " | ||
f"--extensions c,cpp,h --diff {before} {after}" | ||
) | ||
|
||
p = subprocess.Popen( | ||
shlex.split(COMMAND), | ||
stdout=subprocess.PIPE, | ||
stderr=None, | ||
stdin=None, | ||
universal_newlines=True, | ||
) | ||
|
||
stdout, _ = p.communicate() | ||
if not stdout.startswith("diff --git"): | ||
return True | ||
|
||
diff_content = stdout.split("\n") | ||
found = False | ||
for summary in [x for x in diff_content if x.startswith("diff --git")]: | ||
# b/path/to/file -> path/to/file | ||
with_invalid_format = re.split("\s+", summary)[-1][2:] | ||
if not is_excluded(with_invalid_format): | ||
print(f"--- {with_invalid_format} failed on code style checking.") | ||
found = True | ||
else: | ||
return not found | ||
except subprocess.subprocess.CalledProcessError: | ||
return False | ||
|
||
|
||
def run_aspell(file_path: pathlib, root: pathlib) -> bool: | ||
return True | ||
|
||
|
||
def check_dir_name(path: pathlib, root: pathlib) -> bool: | ||
m = re.search(INVALID_DIR_NAME_SEGMENT, str(path.relative_to(root))) | ||
if m: | ||
print(f"--- found a character '_' in {m.groups()} in {path}") | ||
|
||
return not m | ||
|
||
|
||
def check_file_name(path: pathlib) -> bool: | ||
m = re.search(INVALID_FILE_NAME_SEGMENT, path.stem) | ||
if m: | ||
print(f"--- found a character '-' in {m.groups()} in {path}") | ||
|
||
return not m | ||
|
||
|
||
def parse_commits_range(root: pathlib, commits: str) -> list: | ||
GIT_LOG_CMD = f"git log --pretty='%H' {commits}" | ||
try: | ||
ret = subprocess.check_output( | ||
shlex.split(GIT_LOG_CMD), cwd=root, universal_newlines=True | ||
) | ||
return [x for x in ret.split("\n") if x] | ||
except subprocess.CalledProcessError: | ||
print(f"can not parse any commit from the range {commits}") | ||
return [] | ||
|
||
|
||
def analysis_new_item_name(root: pathlib, commit: str) -> bool: | ||
""" | ||
For any file name in the repo, it is required to use '_' to replace '-'. | ||
For any directory name in the repo, it is required to use '-' to replace '_'. | ||
""" | ||
GIT_SHOW_CMD = f"git show --oneline --name-status --diff-filter A {commit}" | ||
try: | ||
invalid_items = True | ||
output = subprocess.check_output( | ||
shlex.split(GIT_SHOW_CMD), cwd=root, universal_newlines=True | ||
) | ||
if not output: | ||
return True | ||
|
||
NEW_FILE_PATTERN = "^A\s+(\S+)" | ||
for line_no, line in enumerate(output.split("\n")): | ||
# bypass the first line, usually it is the commit description | ||
if line_no == 0: | ||
continue | ||
|
||
if not line: | ||
continue | ||
|
||
match = re.match(NEW_FILE_PATTERN, line) | ||
if not match: | ||
continue | ||
|
||
new_item = match.group(1) | ||
new_item = pathlib.Path(new_item).resolve() | ||
|
||
if new_item.is_file(): | ||
if not check_file_name(new_item): | ||
invalid_items = False | ||
continue | ||
|
||
new_item = new_item.parent | ||
|
||
if not check_dir_name(new_item, root): | ||
invalid_items = False | ||
continue | ||
else: | ||
return invalid_items | ||
|
||
except subprocess.CalledProcessError: | ||
return False | ||
|
||
|
||
def process_entire_pr(root: pathlib, commits: str) -> bool: | ||
if not commits: | ||
print("Please provide a commits range") | ||
return False | ||
|
||
commit_list = parse_commits_range(root, commits) | ||
if not commit_list: | ||
print(f"Quit since there is no commit to check with") | ||
return True | ||
|
||
print(f"there are {len(commit_list)} commits in the PR") | ||
|
||
found = False | ||
if not analysis_new_item_name(root, commits): | ||
print(f"{analysis_new_item_name.__doc__}") | ||
found = True | ||
|
||
if not run_clang_format_diff(root, commits): | ||
print(f"{run_clang_format_diff.__doc__}") | ||
found = True | ||
|
||
return not found | ||
|
||
|
||
def main() -> int: | ||
parser = argparse.ArgumentParser( | ||
description="Check if change meets all coding guideline requirements" | ||
) | ||
parser.add_argument( | ||
"-c", "--commits", default=None, help="Commit range in the form: a..b" | ||
) | ||
options = parser.parse_args() | ||
|
||
wamr_root = pathlib.Path(__file__).parent.joinpath("..").resolve() | ||
|
||
if not pre_flight_check(wamr_root): | ||
return False | ||
|
||
return process_entire_pr(wamr_root, options.commits) | ||
|
||
|
||
if __name__ == "__main__": | ||
sys.exit(0 if main() else 1) |
Oops, something went wrong.