Bladeren bron

Customize clang-format coding styles based on Mozilla template (#770)

Customize clang-format coding styles for C source files based on Mozilla template.
To check whether the C source codes are well formatted:
``` bash
$ cd ${wamr-root}
$ clang-format --Werror --dry-run --style=file path/to/file
```
To format the C source codes in place
``` bash
$ cd ${wamr_root}
$ clang-format -i --style=file path/to/file
```

Signed-off-by: Wenyong Huang <wenyong.huang@intel.com>
Wenyong Huang 4 jaren geleden
bovenliggende
commit
7191ecf880
3 gewijzigde bestanden met toevoegingen van 301 en 124 verwijderingen
  1. 22 124
      .clang-format
  2. 16 0
      .clang-tidy
  3. 263 0
      ci/run_pre_commit_check.py

+ 22 - 124
.clang-format

@@ -1,5 +1,6 @@
+# using [clang-formt-12 options](https://releases.llvm.org/12.0.0/tools/clang/docs/ClangFormatStyleOptions.html)
 RawStringFormats:
-  - Language:        Cpp
+  - Language: Cpp
     Delimiters:
       - c
       - C
@@ -13,35 +14,23 @@ RawStringFormats:
       - h
       - hpp
     CanonicalDelimiter: ''
-    BasedOnStyle:    google
-  - Language:        TextProto
-    Delimiters:
-      - pb
-      - PB
-      - proto
-      - PROTO
-    EnclosingFunctions:
-      - EqualsProto
-      - EquivToProto
-      - PARSE_PARTIAL_TEXT_PROTO
-      - PARSE_TEST_PROTO
-      - PARSE_TEXT_PROTO
-      - ParseTextOrDie
-      - ParseTextProtoOrDie
-    CanonicalDelimiter: ''
-    BasedOnStyle:    google
+    BasedOnStyle:    Mozilla
 
 Language: Cpp
 BasedOnStyle: Mozilla
+# 6.1
 IndentWidth: 4
+ContinuationIndentWidth: 4
+# 6.2
+TabWidth: 4
+UseTab: Never
+# 6.3
+ColumnLimit: 80
+# 6.9
 AlignAfterOpenBracket: Align
-AllowAllArgumentsOnNextLine: false
-AlignConsecutiveMacros: true
-AllowShortBlocksOnASingleLine: true
-AlwaysBreakAfterReturnType: All
 BinPackArguments: true
-BinPackParameters: false
-BreakBeforeBinaryOperators: NonAssignment
+BinPackParameters: true
+# 6.10
 BreakBeforeBraces: Custom
 BraceWrapping:
   AfterCaseLabel: true
@@ -60,104 +49,13 @@ BraceWrapping:
   SplitEmptyFunction: true
   SplitEmptyRecord: false
   SplitEmptyNamespace: true
-ColumnLimit: 79
-ContinuationIndentWidth: 2
-DerivePointerAlignment: false
-IncludeBlocks: Regroup
-IncludeCategories:
-  - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
-    Priority: 2
-  - Regex: '^(<|"(gtest|gmock|isl|json)/)'
-    Priority: 1
-  - Regex: ".*"
-    Priority: 3
-IndentPPDirectives: None
-KeepEmptyLinesAtTheStartOfBlocks: false
-NamespaceIndentation: None
-PointerAlignment: Right
-ReflowComments: false
-SortIncludes:   false
-Standard: Auto
-StatementMacros:
-  - Q_UNUSED
-  - QT_REQUIRE_VERSION
-
-# AccessModifierOffset: -2
-# AlignConsecutiveAssignments: false
-# AlignConsecutiveDeclarations: false
-# AlignEscapedNewlines: Right
-# AlignOperands:   true
-# AlignTrailingComments: true
-# AllowAllConstructorInitializersOnNextLine: true
-# AllowAllParametersOfDeclarationOnNextLine: false
-# AllowShortCaseLabelsOnASingleLine: false
-# AllowShortFunctionsOnASingleLine: Inline
-# AllowShortLambdasOnASingleLine: All
-# AllowShortIfStatementsOnASingleLine: Never
-# AllowShortLoopsOnASingleLine: false
-# AlwaysBreakAfterDefinitionReturnType: TopLevel
-# AlwaysBreakAfterReturnType: TopLevel
-# AlwaysBreakBeforeMultilineStrings: false
-# AlwaysBreakTemplateDeclarations: Yes
-# BreakBeforeInheritanceComma: false
-# BreakInheritanceList: BeforeComma
-# BreakBeforeTernaryOperators: true
-# BreakConstructorInitializersBeforeComma: false
-# BreakConstructorInitializers: BeforeComma
-# BreakAfterJavaFieldAnnotations: false
-# BreakStringLiterals: true
-# CommentPragmas:  '^ IWYU pragma:'
-# CompactNamespaces: false
-# ConstructorInitializerAllOnOneLineOrOnePerLine: false
-# ConstructorInitializerIndentWidth: 2
-# Cpp11BracedListStyle: false
-# DisableFormat:   false
-# ExperimentalAutoDetectBinPacking: false
-# FixNamespaceComments: false
-# ForEachMacros:
-#   - foreach
-#   - Q_FOREACH
-#   - BOOST_FOREACH
-# IncludeIsMainRegex: '(Test)?$'
-# IndentCaseLabels: true
-# IndentWrappedFunctionNames: false
-# JavaScriptQuotes: Leave
-# JavaScriptWrapImports: true
-# KeepEmptyLinesAtTheStartOfBlocks: true
-# MacroBlockBegin: ''
-# MacroBlockEnd:   ''
-# MaxEmptyLinesToKeep: 1
-# ObjCBinPackProtocolList: Auto
-# ObjCBlockIndentWidth: 2
-# ObjCSpaceAfterProperty: true
-# ObjCSpaceBeforeProtocolList: false
-# PenaltyBreakAssignment: 2
-# PenaltyBreakBeforeFirstCallParameter: 19
-# PenaltyBreakComment: 300
-# PenaltyBreakFirstLessLess: 120
-# PenaltyBreakString: 1000
-# PenaltyBreakTemplateDeclaration: 10
-# PenaltyExcessCharacter: 1000000
-# PenaltyReturnTypeOnItsOwnLine: 200
-# SortIncludes:    true
-# SortUsingDeclarations: true
-# SpaceAfterCStyleCast: false
-# SpaceAfterLogicalNot: false
-# SpaceAfterTemplateKeyword: false
-# SpaceBeforeAssignmentOperators: true
-# SpaceBeforeCpp11BracedList: false
-# SpaceBeforeCtorInitializerColon: true
-# SpaceBeforeInheritanceColon: true
-# SpaceBeforeParens: ControlStatements
-# SpaceBeforeRangeBasedForLoopColon: true
-# SpaceInEmptyParentheses: false
-# SpacesBeforeTrailingComments: 1
-# SpacesInAngles:  false
-# SpacesInContainerLiterals: true
-# SpacesInCStyleCastParentheses: false
-# SpacesInParentheses: false
-# SpacesInSquareBrackets: false
-# TabWidth:        4
-# UseTab:          Never
-# ...
+# 6.27
+BreakBeforeBinaryOperators: NonAssignment
 
+# additional
+AlignEscapedNewlines: Left
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowAllArgumentsOnNextLine: false
+PointerAlignment: Right
+SpaceAroundPointerQualifiers: After
+SortIncludes: false

+ 16 - 0
.clang-tidy

@@ -0,0 +1,16 @@
+# refer to https://clang.llvm.org/extra/clang-tidy/checks/list.html
+
+Checks:  '-*, readability-identifier-naming, clang-analyzer-core.*,'
+WarningsAsErrors:    '-*'
+HeaderFilterRegex:   ''
+FormatStyle:         file
+InheritParentConfig: false
+AnalyzeTemporaryDtors: false
+User:                wamr
+CheckOptions:
+  - key:             readability-identifier-naming.VariableCase
+    value:           lower_case
+  - key:             readability-identifier-naming.ParameterCase
+    value:           lower_case
+  - key:             readability-identifier-naming.MacroDefinitionCase
+    value:           UPPER_CASE

+ 263 - 0
ci/run_pre_commit_check.py

@@ -0,0 +1,263 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 Intel Corporation.  All rights reserved.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#
+
+import json
+import os
+import pathlib
+import queue
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+
+CLANG_CMD = "clang-13"
+CLANG_CPP_CMD = "clang-cpp-13"
+CLANG_FORMAT_CMD = "clang-format-13"
+CLANG_TIDY_CMD = "clang-tidy-13"
+CMAKE_CMD = "cmake"
+
+
+# glob style patterns
+EXCLUDE_PATHS = [
+    "**/.git/",
+    "**/.github/",
+    "**/.vscode/",
+    "**/assembly-script/",
+    "**/build/",
+    "**/build-scripts/",
+    "**/ci/",
+    "**/core/deps/",
+    "**/doc/",
+    "**/samples/workload/",
+    "**/test-tools/",
+    "**/wamr-sdk/",
+    "**/wamr-dev/",
+    "**/wamr-dev-simd/",
+]
+
+C_SUFFIXES = [".c", ".h"]
+
+VALID_DIR_NAME = r"([a-zA-Z0-9]+\-*)+[a-zA-Z0-9]*"
+VALID_FILE_NAME = r"\.?([a-zA-Z0-9]+\_*)+[a-zA-Z0-9]*\.*\w*"
+
+
+def locate_command(command):
+    if not shutil.which(command):
+        print(f"Command '{command}'' not found")
+        return False
+
+    return True
+
+
+def is_excluded(path):
+    for exclude_path in EXCLUDE_PATHS:
+        if path.match(exclude_path):
+            return True
+    return False
+
+
+def pre_flight_check(root):
+    def check_clang_foramt(root):
+        if not locate_command(CLANG_FORMAT_CMD):
+            return False
+
+        # Quick syntax check for .clang-format
+        try:
+            subprocess.check_call(
+                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_clang_tidy(root):
+        if not locate_command(CLANG_TIDY_CMD):
+            return False
+
+        if (
+            not locate_command(CLANG_CMD)
+            or not locate_command(CLANG_CPP_CMD)
+            or not locate_command(CMAKE_CMD)
+        ):
+            return False
+
+        # Quick syntax check for .clang-format
+        try:
+            subprocess.check_call(
+                shlex.split("{CLANG_TIDY_CMD} --dump-config"), cwd=root
+            )
+        except subprocess.CalledProcessError:
+            print(f"Might have a typo in .clang-tidy")
+            return False
+
+        # looking for compile command database
+        return True
+
+    def check_aspell(root):
+        return True
+
+    return check_clang_foramt(root) and check_clang_tidy(root) and check_aspell(root)
+
+
+def run_clang_format(file_path, root):
+    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 generate_compile_commands(compile_command_database, root):
+    CMD = f"{CMAKE_CMD} -DCMAKE_C_COMPILER={shutil.which(CLANG_CMD)} -DCMAKE_CXX_COMPILER={shutil.which(CLANG_CPP_CMD)} -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .."
+
+    try:
+        linux_mini_build = root.joinpath("product-mini/platforms/linux/build").resolve()
+        linux_mini_build.mkdir(exist_ok=True)
+        if subprocess.check_call(shlex.split(CMD), cwd=linux_mini_build):
+            return False
+
+        wamrc_build = root.joinpath("wamr-compiler/build").resolve()
+        wamrc_build.mkdir(exist_ok=True)
+
+        if subprocess.check_call(shlex.split(CMD), cwd=wamrc_build):
+            return False
+
+        with open(linux_mini_build.joinpath("compile_commands.json"), "r") as f:
+            iwasm_compile_commands = json.load(f)
+
+        with open(wamrc_build.joinpath("compile_commands.json"), "r") as f:
+            wamrc_compile_commands = json.load(f)
+
+        all_compile_commands = iwasm_compile_commands + wamrc_compile_commands
+        # TODO: duplication items ?
+        with open(compile_command_database, "w") as f:
+            json.dump(all_compile_commands, f)
+
+        return True
+    except subprocess.CalledProcessError:
+        return False
+
+
+def run_clang_tidy(file_path, root):
+    # preparatoin
+    compile_command_database = pathlib.Path("/tmp/compile_commands.json")
+    if not compile_command_database.exists() and not generate_compile_commands(
+        compile_command_database, root
+    ):
+        return False
+
+    try:
+        if subprocess.check_call(
+            shlex.split(f"{CLANG_TIDY_CMD} -p={compile_command_database} {file_path}"),
+            cwd=root,
+        ):
+            print(f"{file_path} failed the check of {CLANG_TIDY_CMD}")
+    except subprocess.CalledProcessError:
+        print(f"{file_path} failed the check of {CLANG_TIDY_CMD}")
+        return False
+    return True
+
+
+def run_aspell(file_path, root):
+    return True
+
+
+def check_dir_name(path, root):
+    # since we don't want to check the path beyond root.
+    # we hope "-" only will be used in a dir name as separators
+    return all(
+        [
+            re.match(VALID_DIR_NAME, path_part)
+            for path_part in path.relative_to(root).parts
+        ]
+    )
+
+
+def check_file_name(path):
+    # since we don't want to check the path beyond root.
+    # we hope "_" only will be used in a file name as separators
+    return re.match(VALID_FILE_NAME, path.name) is not None
+
+
+def run_pre_commit_check(path, root=None):
+    path = path.resolve()
+    if path.is_dir():
+        if not check_dir_name(path, root):
+            print(f"{path} is not a valid directory name")
+            return False
+        else:
+            return True
+
+    if path.is_file():
+        if not check_file_name(path):
+            print(f"{path} is not a valid file name")
+            return False
+
+        if not path.suffix in C_SUFFIXES:
+            return True
+
+        return (
+            run_clang_format(path, root)
+            and run_clang_tidy(path, root)
+            and run_aspell(path, root)
+        )
+
+    print(f"{path} neither a file nor a directory")
+    return False
+
+
+def main():
+    wamr_root = pathlib.Path(__file__).parent.joinpath("..").resolve()
+
+    if not pre_flight_check(wamr_root):
+        return False
+
+    invalid_file, invalid_directory = 0, 0
+
+    # in order to skip exclude directories ASAP,
+    # will not yield Path.
+    # since we will create every object
+    dirs = queue.Queue()
+    dirs.put(wamr_root)
+    while not dirs.empty():
+        qsize = dirs.qsize()
+        while qsize:
+            current_dir = dirs.get()
+
+            for path in current_dir.iterdir():
+                path = path.resolve()
+
+                if path.is_symlink():
+                    continue
+
+                if path.is_dir() and not is_excluded(path):
+                    invalid_directory += (
+                        0 if run_pre_commit_check(path, wamr_root) else 1
+                    )
+                    dirs.put(path)
+
+                if not path.is_file():
+                    continue
+
+                invalid_file += 0 if run_pre_commit_check(path) else 1
+
+            else:
+                qsize -= 1
+
+    print(f"invalid_directory={invalid_directory}, invalid_file={invalid_file}")
+    return True
+
+
+if __name__ == "__main__":
+    main()