[libcamera-devel] clang-format: Support multiple versions
diff mbox series

Message ID 20210827144753.419929-1-kieran.bingham@ideasonboard.com
State New
Delegated to: Kieran Bingham
Headers show
Series
  • [libcamera-devel] clang-format: Support multiple versions
Related show

Commit Message

Kieran Bingham Aug. 27, 2021, 2:47 p.m. UTC
The .clang-format file can use CaseSensitive on Regex rules for Include
Categories. With this we can distinguish between QT headers which
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
 .clang-format-7: is moved from the current .clang-format implementation

Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>

---
Note there are few things I dislike about this, hence RFC:

 - We leave our tree without a .clang-format until checkstyle runs..
 - We run the unlinking and symlinking on every invocation of checkstyle
 - The 'autogenerated' file in place of a git tracked one causes
   issues on rebase. Could be an issue for bisects...?

 .clang-format-12                 | 169 +++++++++++++++++++++++++++++++
 .clang-format => .clang-format-7 |   0
 utils/checkstyle.py              |  49 ++++++++-
 3 files changed, 217 insertions(+), 1 deletion(-)
 create mode 100644 .clang-format-12
 rename .clang-format => .clang-format-7 (100%)

Comments

Laurent Pinchart Aug. 27, 2021, 3:43 p.m. UTC | #1
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@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.

> +    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 ?

> +
> +    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.

> +        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
?

> +
> +
> +class ClangFormatter(Formatter):

CLangFormatter became ClangFormatter. Is it intentional ?

>      patterns = ('*.c', '*.cpp', '*.h')
>  
> +    ClangFormatLinker.relink()
> +
>      @classmethod
>      def format(cls, filename, data):
>          ret = subprocess.run(['clang-format', '-style=file',
Kieran Bingham Aug. 27, 2021, 4:05 p.m. UTC | #2
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@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',
>

Patch
diff mbox series

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
+...
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():
+    def version():
+        version_regex = re.compile('[^0-9]*([0-9]+).*')
+        ret = subprocess.run(['clang-format', '--version'], stdout=subprocess.PIPE)
+        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
+
+    def supported():
+        version = ClangFormatLinker.version()
+
+        # Match the highest supported version
+        clang_files = glob.glob('.clang-format-[0-9]*')
+        clang_files = sorted(clang_files, reverse=True,
+                             key=lambda s: [int(re.sub("[^0-9]", "", s))])
+        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')
+
+
+class ClangFormatter(Formatter):
     patterns = ('*.c', '*.cpp', '*.h')
 
+    ClangFormatLinker.relink()
+
     @classmethod
     def format(cls, filename, data):
         ret = subprocess.run(['clang-format', '-style=file',