diff --git a/INSTALL.md b/INSTALL.md index 31cb6098b6a8c30d58ff2950d5ce6b7efaf335b2..4588d9ece5e54312f025496c3b43035887cad08c 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -63,7 +63,7 @@ for getting the latest development versions; otherwise, you can use submodule). ```bash -./build.py --init +./build.py --init [--qt=<path/to/qt> (this is required for qmlformatting to work)] ``` Then you will need to install dependencies: diff --git a/README.md b/README.md index 6ba07cb3db1296217efc4ce97fe3fa9b836bed3a..ca660c53c2a05c783fd3aa20f7cf550cb549a761 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ For more information about the jami project, see the following: ## Notes -- Coding style is managed by the clang-format, if you want to contribute, please use the pre-commit hook automatically installed with `./build.py --init` +- Coding style is managed by the clang-format and qmlformat, if you want to contribute, please use the pre-commit hook automatically installed with `./build.py --init --qt=<path/to/qt>` - We use gerrit for our review. Please read about [working with Gerrit](https://docs.jami.net/developer/working-with-gerrit.html) if you want to submit patches. ## Build diff --git a/build.py b/build.py index dfda2092156163c52f18d209855e819d668a22ca..43dfcb0f40cd864f49107338fc8c04df718ba2af 100755 --- a/build.py +++ b/build.py @@ -304,18 +304,37 @@ def run_dependencies(args): sys.exit(1) -def run_init(): +def run_init(args): + """Initialize the git submodules and install the commit-msg hook.""" subprocess.run(["git", "submodule", "update", "--init"], check=True) - hooks_directories = ['.git/hooks/', f'.git/modules/daemon/hooks'] - for hooks_dir in hooks_directories: + client_hooks_dir = '.git/hooks' + daemon_hooks_dir = '.git/modules/daemon/hooks' + + print("Installing commit-msg hooks...") + # Copy the commit-msg hook to all modules in the same way. + for hooks_dir in [client_hooks_dir, daemon_hooks_dir]: if not os.path.exists(hooks_dir): os.makedirs(hooks_dir) copy_file("./extras/scripts/commit-msg", os.path.join(hooks_dir, "commit-msg")) - execute_script(['./extras/scripts/format.py --install %(path)s'], - {"path": hooks_dir}) + + print("Installing pre-commit hooks...") + format_script = "./extras/scripts/format.py" + # Prepend with the python executable if on Windows (not WSL). + if sys.platform == 'win32': + format_script = f'python {format_script}' + # The client submodule has QML files, so we need to run qmlformat on it, + # and thus need to supply the Qt path. + execute_script([f'{format_script} --install {client_hooks_dir}' + f' --qt {args.qt}' if args.qt else ''], + {"path": client_hooks_dir}) + + # The daemon submodule has no QML files, so we don't need to run + # qmlformat on it, and thus don't need to supply the Qt path. + execute_script([f'{format_script} --install {daemon_hooks_dir}'], + {"path": daemon_hooks_dir}) def copy_file(src, dest): @@ -651,7 +670,8 @@ def main(): run_dependencies(parsed_args) elif parsed_args.init: - run_init() + run_init(parsed_args) + elif parsed_args.clean: run_clean() diff --git a/extras/scripts/format.py b/extras/scripts/format.py index 4676c592a3f656cbee1356433d7b562d9b1dd071..59cf6bffd79e847dfbc038e536ca3cd1cc4d68bf 100755 --- a/extras/scripts/format.py +++ b/extras/scripts/format.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 """ -Clang format C/C++ source files with clang-format. +Clang format C/C++ source files with clang-format (C/C++), and +qmlformat (QML) if installed. Also optionally installs a pre-commit hook to run this script. Usage: @@ -18,16 +19,34 @@ import sys import subprocess import argparse import shutil +from platform import uname CFVERSION = "9" CLANGFORMAT = "" +QMLFORMAT = None + def command_exists(cmd): """ Check if a command exists """ return shutil.which(cmd) is not None +def find_qmlformat(qt_path): + """Find the path to the qmlformat binary.""" + + # Correct the path if it's a Windows WSL path. + is_windows = os.name == "nt" + if 'Microsoft' in uname().release: + qt_path = qt_path.replace('C:', '/mnt/c') + is_windows = True + + # Check if qmlformat is in a subdirectory called bin. + qmlformat_path = os.path.join(qt_path, "bin", "qmlformat") + qmlformat_path += ".exe" if is_windows else "" + return qmlformat_path if os.path.exists(qmlformat_path) else None + + def clang_format_file(filename): """ Format a file using clang-format """ if os.path.isfile(filename): @@ -36,18 +55,35 @@ def clang_format_file(filename): def clang_format_files(files): """ Format a list of files """ + if not files: + return for filename in files: print(f"Formatting: {filename}", end='\r') clang_format_file(filename) +def qml_format_files(files): + """ Format a file using qmlformat """ + if QMLFORMAT is None or not files: + return + for filename in files: + if os.path.isfile(filename): + print(f"Formatting: {filename}", end='\r') + extra_args = ['--normalize', '--force'] + subprocess.call([QMLFORMAT, '--inplace', filename] + extra_args) + # This may generate a backup file (ending with ~), so delete it. + backup_file = filename + "~" + if os.path.isfile(backup_file): + os.remove(backup_file) + + def exit_if_no_files(): """ Exit if no files to format """ print("No files to format") sys.exit(0) -def install_hook(hooks_path): +def install_hook(hooks_path, qt_path=None): """ Install a pre-commit hook to run this script """ if not os.path.isdir(hooks_path): print(f"{hooks_path} path does not exist") @@ -55,7 +91,8 @@ def install_hook(hooks_path): print(f"Installing pre-commit hook in {hooks_path}") with open(os.path.join(hooks_path, "pre-commit"), "w", encoding="utf-8") as file: - file.write(os.path.realpath(sys.argv[0])) + file.write(os.path.realpath(sys.argv[0]) + + f' --qt={qt_path}' if qt_path else '') os.chmod(os.path.join(hooks_path, "pre-commit"), 0o755) @@ -86,7 +123,7 @@ def get_files(file_types, recursive=True, committed_only=False): def main(): - """Check if clang-format is installed, and format files.""" + """Check for formatter installations, install hooks, and format files.""" global CLANGFORMAT # pylint: disable=global-statement parser = argparse.ArgumentParser( description="Format source filess with a clang-format") @@ -94,33 +131,50 @@ def main(): help="format all files instead of only committed ones") parser.add_argument("-i", "--install", metavar="PATH", help="install a pre-commit hook to run this script") + parser.add_argument("-q", "--qt", default=None, + help="The Qt root path") + # Add an option to only format a specific type (qml, cpp, or both) + parser.add_argument("-t", "--type", default="both", + help="The type of files to format (qml, cpp, or both)") args = parser.parse_args() - if not command_exists("clang-format-" + CFVERSION): - if not command_exists("clang-format"): - print("Required version of clang-format not found") - sys.exit(1) + if args.type in ["cpp", "both"]: + if not command_exists("clang-format-" + CFVERSION): + if not command_exists("clang-format"): + print("Required version of clang-format not found") + sys.exit(1) + else: + CLANGFORMAT = "clang-format" else: - CLANGFORMAT = "clang-format" + CLANGFORMAT = "clang-format-" + CFVERSION + print("Using source formatter: " + CLANGFORMAT) + + if args.qt is not None and args.type in ["qml", "both"]: + global QMLFORMAT # pylint: disable=global-statement + QMLFORMAT = find_qmlformat(args.qt) + + if QMLFORMAT is not None: + print("Using qmlformatter: " + QMLFORMAT) else: - CLANGFORMAT = "clang-format-" + CFVERSION + print("No qmlformat found, can't format QML files") if args.install: - install_hook(args.install) + install_hook(args.install, args.qt) sys.exit(0) - if args.all: - print("Formatting all files...") - # Find all files in the recursively in the current directory. - clang_format_files(get_files((".cpp", ".cxx", ".cc", ".h", ".hpp"), - committed_only=False)) + src_files = get_files([".cpp", ".cxx", ".cc", ".h", ".hpp"], + committed_only=not args.all) + qml_files = get_files([".qml"], committed_only=not args.all) + + if not src_files and not qml_files: + exit_if_no_files() else: - files = get_files((".cpp", ".cxx", ".cc", ".h", ".hpp"), - committed_only=True) - if not files: - exit_if_no_files() - print("Formatting committed source files...") - clang_format_files(files) + if src_files and args.type in ["cpp", "both"] and CLANGFORMAT: + print("Formatting source files...") + clang_format_files(src_files) + if qml_files and args.type in ["qml", "both"] and QMLFORMAT: + print("Formatting QML files...") + qml_format_files(qml_files) if __name__ == "__main__":