[libcamera-devel] [PATCH] clang-format: Support multiple versions

Kieran Bingham kieran.bingham at ideasonboard.com
Fri Aug 27 18:05:00 CEST 2021


On 27/08/2021 16:43, Laurent Pinchart wrote:
> Hi Kieran,
> 
> Thank you for the patch.
> 
> On Fri, Aug 27, 2021 at 03:47:53PM +0100, Kieran Bingham wrote:
>> The .clang-format file can use CaseSensitive on Regex rules for Include
>> Categories. With this we can distinguish between QT headers which
> 
> s/QT/Qt/
> 
>> commence with a 'Q' verses system headers including <queue>.
>>
>> This requires clang-format-12 to support that rule, and clang-format is
>> not very amicable to .clang-format files with unsupported entries, and
>> will fail to run on previous versions if we add that option directly.
>>
>> This causes the checkstyle utility to become unusable for users of
>> clang-format versions prior to 12.
>>
>> Unfortunately, clang-format does not provide a way to specify a specific
>> file to use for the rules to apply when formatting, so we can not easily
>> call clang-format with a file specific to its versioned needs.
>>
>> Implement a means of identifying the version of clang-format, and use
>> that information to search for the most recent supportable
>> .clang-format-$(version) file, which can then be symlinked to the fixed
>> file location used by clang format (.clang-format).
>>
>>  .clang-format-12: is added with CaseSensitive:true for QT headers
> 
> Ditto.
> 
>>  .clang-format-7: is moved from the current .clang-format implementation
>>
>> Signed-off-by: Kieran Bingham <kieran.bingham at ideasonboard.com>
>>
>> ---
>> Note there are few things I dislike about this, hence RFC:
>>
>>  - We leave our tree without a .clang-format until checkstyle runs..
> 
> A bit annoying, but I think we can live with that. Maybe it's a good way
> to encourage usage of checkstyle.py :-)
> 
>>  - We run the unlinking and symlinking on every invocation of checkstyle
> 
> Shouldn't be too costly :-) It's slightly annoying that we can't run
> checkstyle.py on a read-only directory anymore though, but I don't think
> it's a common use case.
> 
>>  - The 'autogenerated' file in place of a git tracked one causes
>>    issues on rebase. Could be an issue for bisects...?
> 
> Possibly, but I don't think we'll often bisect and run checkstyle.py at
> the same time.
> 
>>
>>  .clang-format-12                 | 169 +++++++++++++++++++++++++++++++
>>  .clang-format => .clang-format-7 |   0
>>  utils/checkstyle.py              |  49 ++++++++-
> 
> The symlink should be added to .gitignore.
> 
>>  3 files changed, 217 insertions(+), 1 deletion(-)
>>  create mode 100644 .clang-format-12
>>  rename .clang-format => .clang-format-7 (100%)
>>
>> diff --git a/.clang-format-12 b/.clang-format-12
>> new file mode 100644
>> index 000000000000..98e9fad472fc
>> --- /dev/null
>> +++ b/.clang-format-12
>> @@ -0,0 +1,169 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +#
>> +# clang-format configuration file. Intended for clang-format >= 12.
>> +#
>> +# For more information, see:
>> +#
>> +#   Documentation/process/clang-format.rst
>> +#   https://clang.llvm.org/docs/ClangFormat.html
>> +#   https://clang.llvm.org/docs/ClangFormatStyleOptions.html
>> +#
>> +---
>> +Language: Cpp
>> +AccessModifierOffset: -8
>> +AlignAfterOpenBracket: Align
>> +AlignConsecutiveAssignments: false
>> +AlignConsecutiveDeclarations: false
>> +AlignEscapedNewlines: Right
>> +AlignOperands: true
>> +AlignTrailingComments: false
>> +AllowAllParametersOfDeclarationOnNextLine: false
>> +AllowShortBlocksOnASingleLine: false
>> +AllowShortCaseLabelsOnASingleLine: false
>> +AllowShortFunctionsOnASingleLine: InlineOnly
>> +AllowShortIfStatementsOnASingleLine: false
>> +AllowShortLoopsOnASingleLine: false
>> +AlwaysBreakAfterDefinitionReturnType: None
>> +AlwaysBreakAfterReturnType: None
>> +AlwaysBreakBeforeMultilineStrings: false
>> +AlwaysBreakTemplateDeclarations: MultiLine
>> +BinPackArguments: true
>> +BinPackParameters: true
>> +BraceWrapping:
>> +  AfterClass: true
>> +  AfterControlStatement: false
>> +  AfterEnum: false
>> +  AfterFunction: true
>> +  AfterNamespace: false
>> +  AfterObjCDeclaration: false
>> +  AfterStruct: false
>> +  AfterUnion: false
>> +  AfterExternBlock: false
>> +  BeforeCatch: false
>> +  BeforeElse: false
>> +  IndentBraces: false
>> +  SplitEmptyFunction: true
>> +  SplitEmptyRecord: true
>> +  SplitEmptyNamespace: true
>> +BreakBeforeBinaryOperators: None
>> +BreakBeforeBraces: Custom
>> +BreakBeforeInheritanceComma: false
>> +BreakInheritanceList: BeforeColon
>> +BreakBeforeTernaryOperators: true
>> +BreakConstructorInitializers: BeforeColon
>> +BreakAfterJavaFieldAnnotations: false
>> +BreakStringLiterals: false
>> +ColumnLimit: 0
>> +CommentPragmas: '^ IWYU pragma:'
>> +CompactNamespaces: false
>> +ConstructorInitializerAllOnOneLineOrOnePerLine: false
>> +ConstructorInitializerIndentWidth: 8
>> +ContinuationIndentWidth: 8
>> +Cpp11BracedListStyle: false
>> +DerivePointerAlignment: false
>> +DisableFormat: false
>> +ExperimentalAutoDetectBinPacking: false
>> +FixNamespaceComments: true
>> +ForEachMacros:
>> +  - 'udev_list_entry_foreach'
>> +SortIncludes: true
>> +IncludeBlocks: Regroup
>> +IncludeCategories:
>> +  # Headers matching the name of the component are matched automatically.
>> +  # Priority 1
>> +  # Other library headers (explicit overrides to match before system headers)
>> +  - Regex:           '(<jpeglib.h>|<libudev.h>|<tiffio.h>|<xf86drm.h>|<xf86drmMode.h>|<yaml.h>)'
>> +    Priority:        9
>> +  # Qt includes (match before C++ standard library)
>> +  - Regex:           '<Q([A-Za-z0-9\-_])+>'
>> +    Priority:        9
>> +    CaseSensitive:   true
>> +  # Headers in <> with an extension. (+system libraries)
>> +  - Regex:           '<([A-Za-z0-9\-_])+\.h>'
>> +    Priority:        2
>> +  # System headers
>> +  - Regex:           '<sys/.*>'
>> +    Priority:        2
>> +  # C++ standard library includes (no extension)
>> +  - Regex:           '<([A-Za-z0-9\-_/])+>'
>> +    Priority:        2
>> +  # Linux headers, as a second group/subset of system headers
>> +  - Regex:           '<linux/.*>'
>> +    Priority:        3
>> +  # Headers for libcamera Base support
>> +  - Regex:           '<libcamera/base/private.h>'
>> +    Priority:        4
>> +  - Regex:           '<libcamera/base/.*\.h>'
>> +    Priority:        5
>> +  # Public API Headers for libcamera, which are not in a subdir (i.e. ipa/,internal/)
>> +  - Regex:           '<libcamera/([A-Za-z0-9\-_])+.h>'
>> +    Priority:        6
>> +  # IPA Interfaces
>> +  - Regex:           '<libcamera/ipa/.*\.h>'
>> +    Priority:        7
>> +  # libcamera Internal headers in ""
>> +  - Regex:           '"libcamera/internal/.*\.h"'
>> +    Priority:        8
>> +  # Other libraries headers with one group per library (.h or .hpp)
>> +  - Regex:           '<.*/.*\.hp*>'
>> +    Priority:        9
>> +  # local modular includes "path/file.h" (.h or .hpp)
>> +  - Regex:           '"(.*/)+.*\.hp*"'
>> +    Priority:        10
>> +  # Other local headers "file.h" with extension (.h or .hpp)
>> +  - Regex:           '".*.hp*"'
>> +    Priority:        11
>> +  # Any unmatched line, separated from the last group
>> +  - Regex:	     '"*"'
>> +    Priority:        100
>> +
>> +IncludeIsMainRegex: '(_test)?$'
>> +IndentCaseLabels: false
>> +IndentPPDirectives: None
>> +IndentWidth: 8
>> +IndentWrappedFunctionNames: false
>> +JavaScriptQuotes: Leave
>> +JavaScriptWrapImports: true
>> +KeepEmptyLinesAtTheStartOfBlocks: false
>> +MacroBlockBegin: ''
>> +MacroBlockEnd: ''
>> +MaxEmptyLinesToKeep: 1
>> +NamespaceIndentation: None
>> +ObjCBinPackProtocolList: Auto
>> +ObjCBlockIndentWidth: 8
>> +ObjCSpaceAfterProperty: true
>> +ObjCSpaceBeforeProtocolList: true
>> +
>> +# Taken from git's rules
>> +PenaltyBreakAssignment: 10
>> +PenaltyBreakBeforeFirstCallParameter: 30
>> +PenaltyBreakComment: 10
>> +PenaltyBreakFirstLessLess: 0
>> +PenaltyBreakString: 10
>> +PenaltyBreakTemplateDeclaration: 10
>> +PenaltyExcessCharacter: 100
>> +PenaltyReturnTypeOnItsOwnLine: 60
>> +
>> +PointerAlignment: Right
>> +ReflowComments: false
>> +SortIncludes: true
>> +SortUsingDeclarations: true
>> +SpaceAfterCStyleCast: false
>> +SpaceAfterTemplateKeyword: false
>> +SpaceBeforeAssignmentOperators: true
>> +SpaceBeforeCpp11BracedList: false
>> +SpaceBeforeCtorInitializerColon: true
>> +SpaceBeforeInheritanceColon: true
>> +SpaceBeforeParens: ControlStatements
>> +SpaceBeforeRangeBasedForLoopColon: true
>> +SpaceInEmptyParentheses: false
>> +SpacesBeforeTrailingComments: 1
>> +SpacesInAngles: false
>> +SpacesInContainerLiterals: false
>> +SpacesInCStyleCastParentheses: false
>> +SpacesInParentheses: false
>> +SpacesInSquareBrackets: false
>> +Standard: Cpp11
>> +TabWidth: 8
>> +UseTab: Always
>> +...
> 
> I trust you on this.
> 
>> diff --git a/.clang-format b/.clang-format-7
>> similarity index 100%
>> rename from .clang-format
>> rename to .clang-format-7
>> diff --git a/utils/checkstyle.py b/utils/checkstyle.py
>> index ececb46eaacc..279ace8346b4 100755
>> --- a/utils/checkstyle.py
>> +++ b/utils/checkstyle.py
>> @@ -16,6 +16,7 @@
>>  import argparse
>>  import difflib
>>  import fnmatch
>> +import glob
>>  import os.path
>>  import re
>>  import shutil
>> @@ -566,9 +567,55 @@ class Formatter(metaclass=ClassRegistry):
>>          return patterns
>>  
>>  
>> -class CLangFormatter(Formatter):
>> +# Identify the version of clang-format running, and match it against locally
>> +# available versioned configuration files of the form:
>> +# .clang-format-$(version)
>> +#
>> +# The highest supported local configuration file is linked as .clang-format by
>> +# ClangFormatLinker.relink() for use by the ClangFormatter.
>> +#
>> +class ClangFormatLinker():
> 
> No need for parentheses if you don't inherit from another class, but you
> can inherit from object.
> 
> Do we need ClangFormatLinker though ? Couldn't those functions be move
> to ClangFormatter ? Although relinking should probably be done from
> main(), see below.

Probably not, I was just trying to group this code together really, and
wanted to break out into functions a little to make it clearer.


> 
>> +    def version():
>> +        version_regex = re.compile('[^0-9]*([0-9]+).*')
>> +        ret = subprocess.run(['clang-format', '--version'], stdout=subprocess.PIPE)
> 
> What happens if clang-format isn't available ? We check for it in
> main(), but I think this code will run before that.
> 
>> +        version = ret.stdout.decode('utf-8')
>> +        search = version_regex.search(version)
>> +        if search:
>> +            version = search.group(1)
>> +            return int(version)
>> +
>> +        # Lowest supported version
>> +        return 7
> 
> Shouldn't this cause an error instead ?

Probably - we expect there to be a clang-format, and if we can't
interrogate it - then it's an issue.


> 
>> +
>> +    def supported():
>> +        version = ClangFormatLinker.version()
> 
> That's a bit of an abuse of the Python class system. Instance methods
> are supposed to be called on an instance and take a self argument. These
> three functions should be declared as class method I think. This would
> become
> 
>     @classmethod
>     def supported(cls):
>         version = cls.version()
> 
>> +
>> +        # Match the highest supported version
>> +        clang_files = glob.glob('.clang-format-[0-9]*')
> 
> When checkstyle.py is run manually, it may be run from a subdirectory of
> the git tree. You should pass the git top-level directory to relink().
> This would then call for calling relink() directly from main() instead
> of ClangFormatter.
> 
>> +        clang_files = sorted(clang_files, reverse=True,
>> +                             key=lambda s: [int(re.sub("[^0-9]", "", s))])
> 
> We use single quotes for strings when possible.

Ok

> 
>> +        for f in clang_files:
>> +            file_version = int(re.sub("[^0-9]", "", f))
>> +            if file_version <= version:
>> +                return f
>> +
>> +        return None
>> +
>> +    def relink():
>> +        supported = ClangFormatLinker.supported()
>> +        if not supported:
>> +            return
>> +
>> +        if os.path.exists('.clang-format'):
>> +            os.unlink('.clang-format')
>> +        os.symlink(supported, '.clang-format')
> 
> Could we avoid relinking if the symlink points to the right file already
> ?

Probably, - this was just the quick and dirty RFC (where I forgot to add
--rfc to git-format-patch :D)

Presumably os.readlink() will give us something we can work with ...

>> +
>> +
>> +class ClangFormatter(Formatter):
> 
> CLangFormatter became ClangFormatter. Is it intentional ?

Yes, but it was supposed to be in a separate patch.

I don't think I've ever seen anyone really call it "C Lang" rather than
"Clang"



> 
>>      patterns = ('*.c', '*.cpp', '*.h')
>>  
>> +    ClangFormatLinker.relink()
>> +
>>      @classmethod
>>      def format(cls, filename, data):
>>          ret = subprocess.run(['clang-format', '-style=file',
> 


More information about the libcamera-devel mailing list