diff --git a/.github/check_style.py b/.github/check_style.py new file mode 100755 index 000000000..047c0f227 --- /dev/null +++ b/.github/check_style.py @@ -0,0 +1,133 @@ +#!/usr/bin/python + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import os.path +import re +import subprocess +import tempfile +import sys + +coding_style_url = "https://github.com/apache/mynewt-core/blob/master/CODING_STANDARDS.md" + +def get_lines_range(m: re.Match) -> range: + first = int(m.group(1)) + + if m.group(2) is not None: + last = first + int(m.group(2)) + else: + last = first + 1 + + return range(first, last) + + +def run_cmd(cmd: str) -> list[str]: + out = subprocess.check_output(cmd, text=True, shell=True) + return out.splitlines() + + +def check_file(fname: str, commit: str, upstream: str) -> list[str]: + ret = [] + + diff_lines = set() + for s in run_cmd(f"git diff -U0 {upstream} {commit} -- {fname}"): + m = re.match(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@", s) + if not m: + continue + diff_lines.update(get_lines_range(m)) + + with tempfile.NamedTemporaryFile(suffix=os.path.basename(fname)) as tmpf: + lines = subprocess.check_output(f"git show {commit}:{fname}", + shell=True) + tmpf.write(lines) + + in_chunk = False + + for s in run_cmd(f"uncrustify -q -c uncrustify.cfg -f {tmpf.name} | " + f"diff -u0 -p {tmpf.name} - || true"): + m = re.match(r"^@@ -(\d+)(?:,(\d+))? \+\d+(?:,\d+)? @@", s) + if not m: + if in_chunk: + ret.append(s) + continue + + in_chunk = len(diff_lines & set(get_lines_range(m))) != 0 + + if in_chunk: + ret.append(s) + + return ret + + +def is_ignored(fname: str, ign_dirs: list[str]) -> bool: + if not re.search(r"\.(c|cpp|h|hpp)$", fname): + return True + + for d in ign_dirs: + if fname.startswith(d): + return True + + return False + + +def main() -> bool: + if len(sys.argv) > 1: + commit = sys.argv[1] + else: + commit = "HEAD" + if len(sys.argv) > 2: + upstream = sys.argv[2] + else: + upstream = "origin/master" + + mb = run_cmd(f"git merge-base {upstream} {commit}") + upstream = mb[0] + + has_error = False + + cfg_fname = os.path.join(os.path.dirname(__file__), "../.style_ignored_dirs") + with open(cfg_fname, "r") as x: + ign_dirs = [s.strip() for s in x.readlines() if + s.strip() and not s.startswith("#")] + + files = run_cmd(f"git diff --diff-filter=AM --name-only {upstream} {commit}") + for cfg_fname in files: + if is_ignored(cfg_fname, ign_dirs): + print(f"\033[90m- {cfg_fname}\033[0m") + continue + + diff = check_file(cfg_fname, commit, upstream) + if len(diff) > 0: + print(f"\033[31m! See {coding_style_url} for details.\033[0m") + print() + print(f"\033[31m! {cfg_fname}\033[0m") + print() + print("\n".join(diff)) + print() + has_error = True + else: + print(f"+ {cfg_fname}") + + return not has_error + + +if __name__ == "__main__": + if not main(): + sys.exit(1) diff --git a/.github/workflows/check_style.yml b/.github/workflows/check_style.yml new file mode 100644 index 000000000..ce713bb4e --- /dev/null +++ b/.github/workflows/check_style.yml @@ -0,0 +1,38 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +name: Compliance check + +on: [pull_request] + +jobs: + style_check: + name: Coding style + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Install Dependencies + run: | + sudo apt-get update + sudo apt-get install -y uncrustify + - name: check style + run: | + .github/check_style.py diff --git a/uncrustify.cfg b/uncrustify.cfg index 481a62ab0..2bdd2ac53 100644 --- a/uncrustify.cfg +++ b/uncrustify.cfg @@ -678,7 +678,7 @@ sp_try_brace = ignore # ignore/add/remove/force sp_getset_brace = ignore # ignore/add/remove/force # Add or remove space between a variable and '{' for C++ uniform initialization. Default=Add -sp_word_brace = add # ignore/add/remove/force +sp_word_brace_init_lst = add # ignore/add/remove/force # Add or remove space between a variable and '{' for a namespace. Default=Add sp_word_brace_ns = add # ignore/add/remove/force