Files
test2/tools/utils_maintenance/clang_format_paths.py
Sergey Sharybin 03806d0b67 Re-design of submodules used in blender.git
This commit implements described in the #104573.

The goal is to fix the confusion of the submodule hashes change, which are not
ideal for any of the supported git-module configuration (they are either always
visible causing confusion, or silently staged and committed, also causing
confusion).

This commit replaces submodules with a checkout of addons and addons_contrib,
covered by the .gitignore, and locale and developer tools are moved to the
main repository.

This also changes the paths:
- /release/scripts are moved to the /scripts
- /source/tools are moved to the /tools
- /release/datafiles/locale is moved to /locale

This is done to avoid conflicts when using bisect, and also allow buildbot to
automatically "recover" wgen building older or newer branches/patches.

Running `make update` will initialize the local checkout to the changed
repository configuration.

Another aspect of the change is that the make update will support Github style
of remote organization (origin remote pointing to thy fork, upstream remote
pointing to the upstream blender/blender.git).

Pull Request #104755
2023-02-21 16:39:58 +01:00

269 lines
7.8 KiB
Python
Executable File

#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
"""
This script runs clang-format on multiple files/directories.
While it can be called directly, you may prefer to run this from Blender's root directory with the command:
make format
"""
import argparse
import multiprocessing
import os
import sys
import subprocess
from typing import (
List,
Optional,
Sequence,
Tuple,
)
VERSION_MIN = (8, 0, 0)
VERSION_MAX_RECOMMENDED = (12, 0, 0)
CLANG_FORMAT_CMD = "clang-format"
BASE_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
os.chdir(BASE_DIR)
extensions = (
".c", ".cc", ".cpp", ".cxx",
".h", ".hh", ".hpp", ".hxx",
".m", ".mm",
".osl", ".glsl",
)
extensions_only_retab = (
".cmake",
"CMakeLists.txt",
".sh",
)
ignore_files = {
"intern/cycles/render/sobol.cpp", # Too heavy for clang-format
}
def compute_paths(paths: List[str], use_default_paths: bool) -> List[str]:
# Optionally pass in files to operate on.
if use_default_paths:
paths = [
"intern/atomic",
"intern/audaspace",
"intern/clog",
"intern/cycles",
"intern/dualcon",
"intern/eigen",
"intern/ffmpeg",
"intern/ghost",
"intern/glew-mx",
"intern/guardedalloc",
"intern/iksolver",
"intern/libmv",
"intern/locale",
"intern/memutil",
"intern/mikktspace",
"intern/opencolorio",
"intern/opensubdiv",
"intern/openvdb",
"intern/rigidbody",
"intern/utfconv",
"source",
"tests/gtests",
]
else:
# Filter out files, this is only done so this utility wont print that it's
# "Operating" on files that will be filtered out later on.
paths = [
f for f in paths
if os.path.isdir(f) or (os.path.isfile(f) and f.endswith(extensions))
]
if os.sep != "/":
paths = [f.replace("/", os.sep) for f in paths]
return paths
def source_files_from_git(paths: Sequence[str], changed_only: bool) -> List[str]:
if changed_only:
cmd = ("git", "diff", "HEAD", "--name-only", "-z", "--", *paths)
else:
cmd = ("git", "ls-tree", "-r", "HEAD", *paths, "--name-only", "-z")
files = subprocess.check_output(cmd).split(b'\0')
return [f.decode('ascii') for f in files]
def convert_tabs_to_spaces(files: Sequence[str]) -> None:
for f in files:
print("TabExpand", f)
with open(f, 'r', encoding="utf-8") as fh:
data = fh.read()
if False:
# Simple 4 space
data = data.expandtabs(4)
else:
# Complex 2 space
# because some comments have tabs for alignment.
def handle(l: str) -> str:
ls = l.lstrip("\t")
d = len(l) - len(ls)
if d != 0:
return (" " * d) + ls.expandtabs(4)
else:
return l.expandtabs(4)
lines = data.splitlines(keepends=True)
lines = [handle(l) for l in lines]
data = "".join(lines)
with open(f, 'w', encoding="utf-8") as fh:
fh.write(data)
def clang_format_ensure_version() -> Optional[Tuple[int, int, int]]:
global CLANG_FORMAT_CMD
clang_format_cmd = None
version_output = ""
for i in range(2, -1, -1):
clang_format_cmd = (
"clang-format-" + (".".join(["%d"] * i) % VERSION_MIN[:i])
if i > 0 else
"clang-format"
)
try:
version_output = subprocess.check_output((clang_format_cmd, "-version")).decode('utf-8')
except FileNotFoundError:
continue
CLANG_FORMAT_CMD = clang_format_cmd
break
version: Optional[str] = next(iter(v for v in version_output.split() if v[0].isdigit()), None)
if version is None:
return None
version = version.split("-")[0]
# Ensure exactly 3 numbers.
version_num: Tuple[int, int, int] = (tuple(int(n) for n in version.split(".")) + (0, 0, 0))[:3] # type: ignore
print("Using %s (%d.%d.%d)..." % (CLANG_FORMAT_CMD, version_num[0], version_num[1], version_num[2]))
return version_num
def clang_format_file(files: List[str]) -> bytes:
cmd = [
CLANG_FORMAT_CMD,
# Update the files in-place.
"-i",
# Shows the list of processed files.
"-verbose",
] + files
return subprocess.check_output(cmd, stderr=subprocess.STDOUT)
def clang_print_output(output: bytes) -> None:
print(output.decode('utf8', errors='ignore').strip())
def clang_format(files: List[str]) -> None:
pool = multiprocessing.Pool()
# Process in chunks to reduce overhead of starting processes.
cpu_count = multiprocessing.cpu_count()
chunk_size = min(max(len(files) // cpu_count // 2, 1), 32)
for i in range(0, len(files), chunk_size):
files_chunk = files[i:i + chunk_size]
pool.apply_async(clang_format_file, args=[files_chunk], callback=clang_print_output)
pool.close()
pool.join()
def argparse_create() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Format C/C++/GLSL & Objective-C source code.",
epilog=__doc__,
# Don't re-wrap text, keep newlines & indentation.
formatter_class=argparse.RawTextHelpFormatter,
)
parser.add_argument(
"--expand-tabs",
dest="expand_tabs",
default=False,
action='store_true',
help="Run a pre-pass that expands tabs "
"(default=False)",
required=False,
)
parser.add_argument(
"--changed-only",
dest="changed_only",
default=False,
action='store_true',
help=(
"Format only edited files, including the staged ones. "
"Using this with \"paths\" will pick the edited files lying on those paths. "
"(default=False)"
),
required=False,
)
parser.add_argument(
"paths",
nargs=argparse.REMAINDER,
help="All trailing arguments are treated as paths.",
)
return parser
def main() -> None:
version = clang_format_ensure_version()
if version is None:
print("Unable to detect 'clang-format -version'")
sys.exit(1)
if version < VERSION_MIN:
print("Version of clang-format is too old:", version, "<", VERSION_MIN)
sys.exit(1)
if version > VERSION_MAX_RECOMMENDED:
print(
"WARNING: Version of clang-format is too recent:",
version, ">", VERSION_MAX_RECOMMENDED,
)
print(
"You may want to install clang-format-%d.%d, "
"or use the precompiled libs repository." %
(VERSION_MAX_RECOMMENDED[0], VERSION_MAX_RECOMMENDED[1]),
)
args = argparse_create().parse_args()
use_default_paths = not (bool(args.paths) or bool(args.changed_only))
paths = compute_paths(args.paths, use_default_paths)
print("Operating on:" + (" (%d changed paths)" % len(paths) if args.changed_only else ""))
for p in paths:
print(" ", p)
files = [
f for f in source_files_from_git(paths, args.changed_only)
if f.endswith(extensions)
if f not in ignore_files
]
# Always operate on all CMAKE files (when expanding tabs and no paths given).
files_retab = [
f for f in source_files_from_git((".",) if use_default_paths else paths, args.changed_only)
if f.endswith(extensions_only_retab)
if f not in ignore_files
]
if args.expand_tabs:
convert_tabs_to_spaces(files + files_retab)
clang_format(files)
if __name__ == "__main__":
main()