diff options
author | Loïc Blot <nerzhul@users.noreply.github.com> | 2020-04-20 20:18:40 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-20 20:18:40 +0200 |
commit | 27a485a472706bededf38f0de2630688739ca0b2 (patch) | |
tree | a9eebf3c383e68d5856eddf1c608a427f2d7445b /util/ci | |
parent | 338195ff250bd7552ef8167348de2eb05e421c29 (diff) | |
download | minetest-27a485a472706bededf38f0de2630688739ca0b2.tar.gz minetest-27a485a472706bededf38f0de2630688739ca0b2.tar.bz2 minetest-27a485a472706bededf38f0de2630688739ca0b2.zip |
Replace travis with github actions (#9641)
* Move outside of travis to Github actions
This will permit to have better integrated CI workflow than the previous
travis one.
Diffstat (limited to 'util/ci')
-rwxr-xr-x | util/ci/build.sh | 8 | ||||
-rw-r--r-- | util/ci/clang-format-whitelist.txt | 500 | ||||
-rwxr-xr-x | util/ci/clang-tidy.sh | 18 | ||||
-rw-r--r-- | util/ci/common.sh | 45 | ||||
-rw-r--r-- | util/ci/lint.sh | 43 | ||||
-rwxr-xr-x | util/ci/run-clang-tidy.py | 321 |
6 files changed, 935 insertions, 0 deletions
diff --git a/util/ci/build.sh b/util/ci/build.sh new file mode 100755 index 000000000..59069b00a --- /dev/null +++ b/util/ci/build.sh @@ -0,0 +1,8 @@ +#! /bin/bash -eu + +mkdir cmakebuild +cd cmakebuild +cmake -DCMAKE_BUILD_TYPE=Debug \ + -DRUN_IN_PLACE=TRUE -DENABLE_GETTEXT=TRUE \ + -DBUILD_SERVER=TRUE ${CMAKE_FLAGS} .. +make -j2 diff --git a/util/ci/clang-format-whitelist.txt b/util/ci/clang-format-whitelist.txt new file mode 100644 index 000000000..3334257ae --- /dev/null +++ b/util/ci/clang-format-whitelist.txt @@ -0,0 +1,500 @@ +src/activeobject.h +src/ban.cpp +src/camera.cpp +src/camera.h +src/chat.cpp +src/chat.h +src/chat_interface.h +src/client/clientlauncher.cpp +src/client/clientlauncher.h +src/client/sound_openal.cpp +src/client.cpp +src/clientenvironment.cpp +src/clientenvironment.h +src/client/gameui.cpp +src/client.h +src/client/hud.cpp +src/client/hud.h +src/clientiface.cpp +src/clientiface.h +src/client/joystick_controller.cpp +src/client/joystick_controller.h +src/clientmap.cpp +src/clientmap.h +src/clientmedia.cpp +src/clientmedia.h +src/clientobject.cpp +src/clientobject.h +src/client/render/core.cpp +src/client/renderingengine.cpp +src/client/render/interlaced.cpp +src/client/render/plain.cpp +src/client/render/sidebyside.cpp +src/client/render/stereo.cpp +src/client/tile.cpp +src/client/tile.h +src/client/fontengine.h +src/client/clientenvironment.cpp +src/client/mapblock_mesh.cpp +src/client/sound_openal.h +src/client/clouds.cpp +src/client/fontengine.cpp +src/client/camera.h +src/client/hud.cpp +src/client/clientmap.cpp +src/client/sound_openal.cpp +src/client/minimap.h +src/client/content_cao.cpp +src/client/localplayer.h +src/client/mapblock_mesh.h +src/client/mesh.cpp +src/client/sound.cpp +src/client/guiscalingfilter.cpp +src/client/content_cso.cpp +src/client/gameui.cpp +src/client/wieldmesh.cpp +src/client/clientmedia.h +src/client/game.cpp +src/client/keys.h +src/client/client.h +src/client/shader.cpp +src/client/clientmap.h +src/client/inputhandler.h +src/client/content_mapblock.h +src/client/game.h +src/client/mesh.h +src/client/camera.cpp +src/client/sky.h +src/client/mesh_generator_thread.cpp +src/client/guiscalingfilter.h +src/client/clientobject.cpp +src/client/tile.cpp +src/client/hud.h +src/client/inputhandler.cpp +src/client/clientevent.h +src/client/gameui.h +src/client/content_cso.h +src/client/sky.cpp +src/client/localplayer.cpp +src/client/content_mapblock.cpp +src/client/clientobject.h +src/client/filecache.cpp +src/client/particles.h +src/client/clientenvironment.h +src/client/imagefilters.h +src/client/renderingengine.cpp +src/client/tile.h +src/client/clientmedia.cpp +src/client/event_manager.h +src/client/joystick_controller.h +src/client/clouds.h +src/client/clientlauncher.h +src/client/content_cao.h +src/client/minimap.cpp +src/client/sound.h +src/client/keycode.cpp +src/client/particles.cpp +src/client/joystick_controller.cpp +src/client/keycode.h +src/client/wieldmesh.h +src/client/filecache.h +src/client/shader.h +src/client/mesh_generator_thread.h +src/client/renderingengine.h +src/client/client.cpp +src/client/imagefilters.cpp +src/client/clientlauncher.cpp +src/clouds.cpp +src/clouds.h +src/collision.cpp +src/collision.h +src/config.h +src/content_cao.cpp +src/content_cao.h +src/content_cso.cpp +src/content_cso.h +src/content_mapblock.cpp +src/content_mapblock.h +src/content_mapnode.cpp +src/content_nodemeta.cpp +src/content_nodemeta.h +src/convert_json.cpp +src/convert_json.h +src/craftdef.cpp +src/craftdef.h +src/database/database.cpp +src/database/database-dummy.cpp +src/database/database-files.cpp +src/database/database-leveldb.cpp +src/database/database-postgresql.cpp +src/database/database-postgresql.h +src/database/database-redis.cpp +src/database/database-sqlite3.cpp +src/database/database-sqlite3.h +src/daynightratio.h +src/debug.cpp +src/debug.h +src/defaultsettings.cpp +src/emerge.cpp +src/emerge.h +src/environment.cpp +src/exceptions.h +src/face_position_cache.cpp +src/face_position_cache.h +src/filecache.cpp +src/filesys.cpp +src/filesys.h +src/fontengine.cpp +src/fontengine.h +src/game.cpp +src/gamedef.h +src/game.h +src/gettext.cpp +src/gettext.h +src/gui/guiAnimatedImage.cpp +src/gui/guiAnimatedImage.h +src/gui/guiBackgroundImage.cpp +src/gui/guiBackgroundImage.h +src/gui/guiBox.cpp +src/gui/guiBox.h +src/gui/guiButton.cpp +src/gui/guiButton.h +src/gui/guiButtonImage.cpp +src/gui/guiButtonImage.h +src/gui/guiButtonItemImage.cpp +src/gui/guiButtonItemImage.h +src/gui/guiChatConsole.cpp +src/gui/guiChatConsole.h +src/gui/guiConfirmRegistration.cpp +src/gui/guiEditBoxWithScrollbar.cpp +src/gui/guiEditBoxWithScrollbar.h +src/gui/guiEngine.cpp +src/gui/guiEngine.h +src/gui/guiFormSpecMenu.cpp +src/gui/guiFormSpecMenu.h +src/gui/guiKeyChangeMenu.cpp +src/gui/guiHyperText.cpp +src/gui/guiHyperText.h +src/gui/guiInventoryList.cpp +src/gui/guiInventoryList.h +src/gui/guiItemImage.cpp +src/gui/guiItemImage.h +src/gui/guiMainMenu.h +src/gui/guiPasswordChange.cpp +src/gui/guiPathSelectMenu.cpp +src/gui/guiPathSelectMenu.h +src/gui/guiScrollBar.cpp +src/gui/guiSkin.cpp +src/gui/guiSkin.h +src/gui/guiTable.cpp +src/gui/guiTable.h +src/gui/guiVolumeChange.cpp +src/gui/guiVolumeChange.h +src/gui/intlGUIEditBox.cpp +src/gui/intlGUIEditBox.h +src/gui/mainmenumanager.h +src/gui/modalMenu.h +src/guiscalingfilter.cpp +src/guiscalingfilter.h +src/gui/StyleSpec.h +src/gui/touchscreengui.cpp +src/httpfetch.cpp +src/hud.cpp +src/hud.h +src/imagefilters.cpp +src/imagefilters.h +src/inventory.cpp +src/inventory.h +src/inventorymanager.cpp +src/inventorymanager.h +src/irrlicht_changes/CGUITTFont.cpp +src/irrlicht_changes/CGUITTFont.h +src/irrlicht_changes/irrUString.h +src/irrlicht_changes/static_text.cpp +src/irrlicht_changes/static_text.h +src/irrlichttypes.h +src/itemdef.cpp +src/itemdef.h +src/itemstackmetadata.cpp +src/keycode.cpp +src/light.cpp +src/localplayer.cpp +src/log.cpp +src/log.h +src/main.cpp +src/mapblock.cpp +src/mapblock.h +src/mapblock_mesh.cpp +src/mapblock_mesh.h +src/map.cpp +src/mapgen/cavegen.cpp +src/mapgen/cavegen.h +src/mapgen/dungeongen.cpp +src/mapgen/dungeongen.h +src/mapgen/mapgen.cpp +src/mapgen/mapgen.h +src/mapgen/mapgen_carpathian.cpp +src/mapgen/mapgen_carpathian.h +src/mapgen/mapgen_flat.cpp +src/mapgen/mapgen_flat.h +src/mapgen/mapgen_fractal.cpp +src/mapgen/mapgen_fractal.h +src/mapgen/mapgen_singlenode.cpp +src/mapgen/mapgen_singlenode.h +src/mapgen/mapgen_v5.cpp +src/mapgen/mapgen_v5.h +src/mapgen/mapgen_v6.cpp +src/mapgen/mapgen_v6.h +src/mapgen/mapgen_v7.cpp +src/mapgen/mapgen_v7.h +src/mapgen/mapgen_valleys.cpp +src/mapgen/mapgen_valleys.h +src/mapgen/mg_biome.cpp +src/mapgen/mg_biome.h +src/mapgen/mg_decoration.cpp +src/mapgen/mg_decoration.h +src/mapgen/mg_ore.cpp +src/mapgen/mg_ore.h +src/mapgen/mg_schematic.cpp +src/mapgen/mg_schematic.h +src/mapgen/treegen.cpp +src/mapgen/treegen.h +src/map.h +src/mapnode.cpp +src/mapnode.h +src/mapsector.cpp +src/mapsector.h +src/map_settings_manager.cpp +src/map_settings_manager.h +src/mesh.cpp +src/mesh_generator_thread.cpp +src/mesh.h +src/metadata.h +src/minimap.cpp +src/minimap.h +src/mods.cpp +src/mods.h +src/network/address.cpp +src/network/clientopcodes.cpp +src/network/clientopcodes.h +src/network/clientpackethandler.cpp +src/network/connection.cpp +src/network/connection.h +src/network/connectionthreads.cpp +src/network/networkpacket.cpp +src/network/networkprotocol.h +src/network/serveropcodes.cpp +src/network/serveropcodes.h +src/network/serverpackethandler.cpp +src/nodedef.cpp +src/nodedef.h +src/nodemetadata.cpp +src/nodemetadata.h +src/nodetimer.cpp +src/nodetimer.h +src/noise.cpp +src/noise.h +src/objdef.cpp +src/objdef.h +src/object_properties.cpp +src/object_properties.h +src/particles.cpp +src/particles.h +src/pathfinder.cpp +src/pathfinder.h +src/player.cpp +src/player.h +src/porting_android.cpp +src/porting_android.h +src/porting.cpp +src/porting.h +src/profiler.h +src/raycast.cpp +src/raycast.h +src/reflowscan.cpp +src/reflowscan.h +src/remoteplayer.cpp +src/rollback.cpp +src/rollback.h +src/rollback_interface.cpp +src/rollback_interface.h +src/script/common/c_content.cpp +src/script/common/c_content.h +src/script/common/c_converter.cpp +src/script/common/c_converter.h +src/script/common/c_internal.cpp +src/script/common/c_internal.h +src/script/common/c_types.cpp +src/script/common/c_types.h +src/script/cpp_api/s_async.cpp +src/script/cpp_api/s_async.h +src/script/cpp_api/s_base.cpp +src/script/cpp_api/s_base.h +src/script/cpp_api/s_client.cpp +src/script/cpp_api/s_entity.cpp +src/script/cpp_api/s_entity.h +src/script/cpp_api/s_env.cpp +src/script/cpp_api/s_env.h +src/script/cpp_api/s_internal.h +src/script/cpp_api/s_inventory.cpp +src/script/cpp_api/s_inventory.h +src/script/cpp_api/s_item.cpp +src/script/cpp_api/s_item.h +src/script/cpp_api/s_mainmenu.h +src/script/cpp_api/s_node.cpp +src/script/cpp_api/s_node.h +src/script/cpp_api/s_nodemeta.cpp +src/script/cpp_api/s_nodemeta.h +src/script/cpp_api/s_player.cpp +src/script/cpp_api/s_player.h +src/script/cpp_api/s_security.cpp +src/script/cpp_api/s_security.h +src/script/cpp_api/s_server.cpp +src/script/cpp_api/s_server.h +src/script/lua_api/l_areastore.cpp +src/script/lua_api/l_base.cpp +src/script/lua_api/l_base.h +src/script/lua_api/l_client.cpp +src/script/lua_api/l_craft.cpp +src/script/lua_api/l_craft.h +src/script/lua_api/l_env.cpp +src/script/lua_api/l_env.h +src/script/lua_api/l_http.cpp +src/script/lua_api/l_http.h +src/script/lua_api/l_internal.h +src/script/lua_api/l_inventory.cpp +src/script/lua_api/l_inventory.h +src/script/lua_api/l_item.cpp +src/script/lua_api/l_item.h +src/script/lua_api/l_itemstackmeta.cpp +src/script/lua_api/l_itemstackmeta.h +src/script/lua_api/l_localplayer.cpp +src/script/lua_api/l_mainmenu.cpp +src/script/lua_api/l_mainmenu.h +src/script/lua_api/l_mapgen.cpp +src/script/lua_api/l_mapgen.h +src/script/lua_api/l_metadata.cpp +src/script/lua_api/l_minimap.cpp +src/script/lua_api/l_nodemeta.cpp +src/script/lua_api/l_nodemeta.h +src/script/lua_api/l_nodetimer.cpp +src/script/lua_api/l_noise.cpp +src/script/lua_api/l_object.cpp +src/script/lua_api/l_object.h +src/script/lua_api/l_particles.cpp +src/script/lua_api/l_particles.h +src/script/lua_api/l_particles_local.cpp +src/script/lua_api/l_rollback.cpp +src/script/lua_api/l_rollback.h +src/script/lua_api/l_server.cpp +src/script/lua_api/l_settings.cpp +src/script/lua_api/l_sound.cpp +src/script/lua_api/l_storage.cpp +src/script/lua_api/l_util.cpp +src/script/lua_api/l_vmanip.cpp +src/script/scripting_client.cpp +src/script/scripting_client.h +src/script/scripting_mainmenu.cpp +src/script/scripting_mainmenu.h +src/script/scripting_server.cpp +src/script/scripting_server.h +src/serialization.cpp +src/serialization.h +src/server.cpp +src/serverenvironment.cpp +src/serverenvironment.h +src/server.h +src/serverlist.cpp +src/serverlist.h +src/server/luaentity_sao.cpp +src/server/player_sao.cpp +src/server/serveractiveobject.cpp +src/server/serveractiveobject.h +src/settings.cpp +src/settings.h +src/settings_translation_file.cpp +src/shader.cpp +src/shader.h +src/sky.cpp +src/sound.cpp +src/staticobject.cpp +src/staticobject.h +src/subgame.cpp +src/subgame.h +src/terminal_chat_console.cpp +src/terminal_chat_console.h +src/texture_override.cpp +src/threading/atomic.h +src/threading/event.cpp +src/threading/mutex_auto_lock.h +src/threading/mutex.cpp +src/threading/mutex.h +src/threading/semaphore.cpp +src/threading/thread.cpp +src/threading/thread.h +src/threads.h +src/tileanimation.cpp +src/tileanimation.h +src/tool.cpp +src/tool.h +src/translation.cpp +src/unittest/test_areastore.cpp +src/unittest/test_collision.cpp +src/unittest/test_compression.cpp +src/unittest/test_connection.cpp +src/unittest/test.cpp +src/unittest/test_filepath.cpp +src/unittest/test.h +src/unittest/test_inventory.cpp +src/unittest/test_keycode.cpp +src/unittest/test_map_settings_manager.cpp +src/unittest/test_noderesolver.cpp +src/unittest/test_noise.cpp +src/unittest/test_random.cpp +src/unittest/test_schematic.cpp +src/unittest/test_serialization.cpp +src/unittest/test_settings.cpp +src/unittest/test_socket.cpp +src/unittest/test_threading.cpp +src/unittest/test_utilities.cpp +src/unittest/test_voxelalgorithms.cpp +src/unittest/test_voxelmanipulator.cpp +src/util/areastore.cpp +src/util/areastore.h +src/util/auth.cpp +src/util/auth.h +src/util/base64.cpp +src/util/base64.h +src/util/basic_macros.h +src/util/container.h +src/util/directiontables.cpp +src/util/directiontables.h +src/util/enriched_string.cpp +src/util/enriched_string.h +src/util/md32_common.h +src/util/numeric.cpp +src/util/numeric.h +src/util/pointedthing.cpp +src/util/pointedthing.h +src/util/pointer.h +src/util/quicktune.h +src/util/quicktune_shortcutter.h +src/util/quicktune.cpp +src/util/serialize.cpp +src/util/serialize.h +src/util/sha1.cpp +src/util/srp.cpp +src/util/srp.h +src/util/strfnd.h +src/util/string.cpp +src/util/string.h +src/util/thread.h +src/util/timetaker.cpp +src/util/timetaker.h +src/version.cpp +src/version.h +src/voxelalgorithms.cpp +src/voxelalgorithms.h +src/voxel.cpp +src/voxel.h +src/wieldmesh.cpp diff --git a/util/ci/clang-tidy.sh b/util/ci/clang-tidy.sh new file mode 100755 index 000000000..d048f54ee --- /dev/null +++ b/util/ci/clang-tidy.sh @@ -0,0 +1,18 @@ +#! /bin/bash -eu + +mkdir -p cmakebuild +cd cmakebuild +cmake -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -DRUN_IN_PLACE=TRUE \ + -DENABLE_GETTEXT=TRUE \ + -DENABLE_SOUND=FALSE \ + -DBUILD_SERVER=TRUE .. +make GenerateVersion + +cd .. + +./util/ci/run-clang-tidy.py \ + -clang-tidy-binary=clang-tidy-9 -p cmakebuild \ + -quiet -config="$(cat .clang-tidy)" \ + 'src/.*' diff --git a/util/ci/common.sh b/util/ci/common.sh new file mode 100644 index 000000000..5a4f78457 --- /dev/null +++ b/util/ci/common.sh @@ -0,0 +1,45 @@ +#!/bin/bash -e + +set_linux_compiler_env() { + if [[ "${COMPILER}" == "gcc-6" ]]; then + export CC=gcc-6 + export CXX=g++-6 + elif [[ "${COMPILER}" == "gcc-8" ]]; then + export CC=gcc-8 + export CXX=g++-8 + elif [[ "${COMPILER}" == "clang-3.9" ]]; then + export CC=clang-3.9 + export CXX=clang++-3.9 + elif [[ "${COMPILER}" == "clang-9" ]]; then + export CC=clang-9 + export CXX=clang++-9 + fi +} + +# Linux build only +install_linux_deps() { + local pkgs=(libirrlicht-dev cmake libbz2-dev libpng-dev \ + libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev \ + libhiredis-dev libogg-dev libgmp-dev libvorbis-dev libopenal-dev \ + gettext libpq-dev postgresql-server-dev-all libleveldb-dev \ + libcurl4-openssl-dev) + # for better coverage, build some jobs with luajit + if [ -n "$WITH_LUAJIT" ]; then + pkgs+=(libluajit-5.1-dev) + fi + + sudo apt-get update + sudo apt-get install -y --no-install-recommends ${pkgs[@]} +} + +# Mac OSX build only +install_macosx_deps() { + brew update + brew install freetype gettext hiredis irrlicht leveldb libogg libvorbis luajit + if brew ls | grep -q jpeg; then + brew upgrade jpeg + else + brew install jpeg + fi + #brew upgrade postgresql +} diff --git a/util/ci/lint.sh b/util/ci/lint.sh new file mode 100644 index 000000000..395445ca7 --- /dev/null +++ b/util/ci/lint.sh @@ -0,0 +1,43 @@ +#! /bin/bash +function perform_lint() { + echo "Performing LINT..." + if [ -z "${CLANG_FORMAT}" ]; then + CLANG_FORMAT=clang-format + fi + echo "LINT: Using binary $CLANG_FORMAT" + CLANG_FORMAT_WHITELIST="util/ci/clang-format-whitelist.txt" + + files_to_lint="$(find src/ -name '*.cpp' -or -name '*.h')" + + local errorcount=0 + local fail=0 + for f in ${files_to_lint}; do + d=$(diff -u "$f" <(${CLANG_FORMAT} "$f") || true) + + if ! [ -z "$d" ]; then + whitelisted=$(awk '$1 == "'$f'" { print 1 }' "$CLANG_FORMAT_WHITELIST") + + # If file is not whitelisted, mark a failure + if [ -z "${whitelisted}" ]; then + errorcount=$((errorcount+1)) + + printf "The file %s is not compliant with the coding style" "$f" + if [ ${errorcount} -gt 50 ]; then + printf "\nToo many errors encountered previously, this diff is hidden.\n" + else + printf ":\n%s\n" "$d" + fi + + fail=1 + fi + fi + done + + if [ "$fail" = 1 ]; then + echo "LINT reports failure." + exit 1 + fi + + echo "LINT OK" +} + diff --git a/util/ci/run-clang-tidy.py b/util/ci/run-clang-tidy.py new file mode 100755 index 000000000..6ad0ff24f --- /dev/null +++ b/util/ci/run-clang-tidy.py @@ -0,0 +1,321 @@ +#!/usr/bin/env python +# +#===- run-clang-tidy.py - Parallel clang-tidy runner ---------*- python -*--===# +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +#===------------------------------------------------------------------------===# +# FIXME: Integrate with clang-tidy-diff.py + +""" +Parallel clang-tidy runner +========================== + +Runs clang-tidy over all files in a compilation database. Requires clang-tidy +and clang-apply-replacements in $PATH. + +Example invocations. +- Run clang-tidy on all files in the current working directory with a default + set of checks and show warnings in the cpp files and all project headers. + run-clang-tidy.py $PWD + +- Fix all header guards. + run-clang-tidy.py -fix -checks=-*,llvm-header-guard + +- Fix all header guards included from clang-tidy and header guards + for clang-tidy headers. + run-clang-tidy.py -fix -checks=-*,llvm-header-guard extra/clang-tidy \ + -header-filter=extra/clang-tidy + +Compilation database setup: +http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +""" + +from __future__ import print_function + +import argparse +import glob +import json +import multiprocessing +import os +import re +import shutil +import subprocess +import sys +import tempfile +import threading +import traceback + +try: + import yaml +except ImportError: + yaml = None + +is_py2 = sys.version[0] == '2' + +if is_py2: + import Queue as queue +else: + import queue as queue + +def find_compilation_database(path): + """Adjusts the directory until a compilation database is found.""" + result = './' + while not os.path.isfile(os.path.join(result, path)): + if os.path.realpath(result) == '/': + print('Error: could not find compilation database.') + sys.exit(1) + result += '../' + return os.path.realpath(result) + + +def make_absolute(f, directory): + if os.path.isabs(f): + return f + return os.path.normpath(os.path.join(directory, f)) + + +def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path, + header_filter, extra_arg, extra_arg_before, quiet, + config): + """Gets a command line for clang-tidy.""" + start = [clang_tidy_binary] + if header_filter is not None: + start.append('-header-filter=' + header_filter) + if checks: + start.append('-checks=' + checks) + if tmpdir is not None: + start.append('-export-fixes') + # Get a temporary file. We immediately close the handle so clang-tidy can + # overwrite it. + (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir) + os.close(handle) + start.append(name) + for arg in extra_arg: + start.append('-extra-arg=%s' % arg) + for arg in extra_arg_before: + start.append('-extra-arg-before=%s' % arg) + start.append('-p=' + build_path) + if quiet: + start.append('-quiet') + if config: + start.append('-config=' + config) + start.append(f) + return start + + +def merge_replacement_files(tmpdir, mergefile): + """Merge all replacement files in a directory into a single file""" + # The fixes suggested by clang-tidy >= 4.0.0 are given under + # the top level key 'Diagnostics' in the output yaml files + mergekey="Diagnostics" + merged=[] + for replacefile in glob.iglob(os.path.join(tmpdir, '*.yaml')): + content = yaml.safe_load(open(replacefile, 'r')) + if not content: + continue # Skip empty files. + merged.extend(content.get(mergekey, [])) + + if merged: + # MainSourceFile: The key is required by the definition inside + # include/clang/Tooling/ReplacementsYaml.h, but the value + # is actually never used inside clang-apply-replacements, + # so we set it to '' here. + output = { 'MainSourceFile': '', mergekey: merged } + with open(mergefile, 'w') as out: + yaml.safe_dump(output, out) + else: + # Empty the file: + open(mergefile, 'w').close() + + +def check_clang_apply_replacements_binary(args): + """Checks if invoking supplied clang-apply-replacements binary works.""" + try: + subprocess.check_call([args.clang_apply_replacements_binary, '--version']) + except: + print('Unable to run clang-apply-replacements. Is clang-apply-replacements ' + 'binary correctly specified?', file=sys.stderr) + traceback.print_exc() + sys.exit(1) + + +def apply_fixes(args, tmpdir): + """Calls clang-apply-fixes on a given directory.""" + invocation = [args.clang_apply_replacements_binary] + if args.format: + invocation.append('-format') + if args.style: + invocation.append('-style=' + args.style) + invocation.append(tmpdir) + subprocess.call(invocation) + + +def run_tidy(args, tmpdir, build_path, queue, lock, failed_files): + """Takes filenames out of queue and runs clang-tidy on them.""" + while True: + name = queue.get() + invocation = get_tidy_invocation(name, args.clang_tidy_binary, args.checks, + tmpdir, build_path, args.header_filter, + args.extra_arg, args.extra_arg_before, + args.quiet, args.config) + + proc = subprocess.Popen(invocation) + proc.wait() + if proc.returncode != 0: + failed_files.append(name) + queue.task_done() + + +def main(): + parser = argparse.ArgumentParser(description='Runs clang-tidy over all files ' + 'in a compilation database. Requires ' + 'clang-tidy and clang-apply-replacements in ' + '$PATH.') + parser.add_argument('-clang-tidy-binary', metavar='PATH', + default='clang-tidy', + help='path to clang-tidy binary') + parser.add_argument('-clang-apply-replacements-binary', metavar='PATH', + default='clang-apply-replacements', + help='path to clang-apply-replacements binary') + parser.add_argument('-checks', default=None, + help='checks filter, when not specified, use clang-tidy ' + 'default') + parser.add_argument('-config', default=None, + help='Specifies a configuration in YAML/JSON format: ' + ' -config="{Checks: \'*\', ' + ' CheckOptions: [{key: x, ' + ' value: y}]}" ' + 'When the value is empty, clang-tidy will ' + 'attempt to find a file named .clang-tidy for ' + 'each source file in its parent directories.') + parser.add_argument('-header-filter', default=None, + help='regular expression matching the names of the ' + 'headers to output diagnostics from. Diagnostics from ' + 'the main file of each translation unit are always ' + 'displayed.') + if yaml: + parser.add_argument('-export-fixes', metavar='filename', dest='export_fixes', + help='Create a yaml file to store suggested fixes in, ' + 'which can be applied with clang-apply-replacements.') + parser.add_argument('-j', type=int, default=0, + help='number of tidy instances to be run in parallel.') + parser.add_argument('files', nargs='*', default=['.*'], + help='files to be processed (regex on path)') + parser.add_argument('-fix', action='store_true', help='apply fix-its') + parser.add_argument('-format', action='store_true', help='Reformat code ' + 'after applying fixes') + parser.add_argument('-style', default='file', help='The style of reformat ' + 'code after applying fixes') + parser.add_argument('-p', dest='build_path', + help='Path used to read a compile command database.') + parser.add_argument('-extra-arg', dest='extra_arg', + action='append', default=[], + help='Additional argument to append to the compiler ' + 'command line.') + parser.add_argument('-extra-arg-before', dest='extra_arg_before', + action='append', default=[], + help='Additional argument to prepend to the compiler ' + 'command line.') + parser.add_argument('-quiet', action='store_true', + help='Run clang-tidy in quiet mode') + args = parser.parse_args() + + db_path = 'compile_commands.json' + + if args.build_path is not None: + build_path = args.build_path + else: + # Find our database + build_path = find_compilation_database(db_path) + + try: + invocation = [args.clang_tidy_binary, '-list-checks'] + invocation.append('-p=' + build_path) + if args.checks: + invocation.append('-checks=' + args.checks) + invocation.append('-') + if args.quiet: + # Even with -quiet we still want to check if we can call clang-tidy. + with open(os.devnull, 'w') as dev_null: + subprocess.check_call(invocation, stdout=dev_null) + else: + subprocess.check_call(invocation) + except: + print("Unable to run clang-tidy.", file=sys.stderr) + sys.exit(1) + + # Load the database and extract all files. + database = json.load(open(os.path.join(build_path, db_path))) + files = [make_absolute(entry['file'], entry['directory']) + for entry in database] + + max_task = args.j + if max_task == 0: + max_task = multiprocessing.cpu_count() + + tmpdir = None + if args.fix or (yaml and args.export_fixes): + check_clang_apply_replacements_binary(args) + tmpdir = tempfile.mkdtemp() + + # Build up a big regexy filter from all command line arguments. + file_name_re = re.compile('|'.join(args.files)) + + return_code = 0 + try: + # Spin up a bunch of tidy-launching threads. + task_queue = queue.Queue(max_task) + # List of files with a non-zero return code. + failed_files = [] + lock = threading.Lock() + for _ in range(max_task): + t = threading.Thread(target=run_tidy, + args=(args, tmpdir, build_path, task_queue, lock, failed_files)) + t.daemon = True + t.start() + + # Fill the queue with files. + for name in files: + if file_name_re.search(name): + task_queue.put(name) + + # Wait for all threads to be done. + task_queue.join() + if len(failed_files): + return_code = 1 + + except KeyboardInterrupt: + # This is a sad hack. Unfortunately subprocess goes + # bonkers with ctrl-c and we start forking merrily. + print('\nCtrl-C detected, goodbye.') + if tmpdir: + shutil.rmtree(tmpdir) + os.kill(0, 9) + + if yaml and args.export_fixes: + print('Writing fixes to ' + args.export_fixes + ' ...') + try: + merge_replacement_files(tmpdir, args.export_fixes) + except: + print('Error exporting fixes.\n', file=sys.stderr) + traceback.print_exc() + return_code=1 + + if args.fix: + print('Applying fixes ...') + try: + apply_fixes(args, tmpdir) + except: + print('Error applying fixes.\n', file=sys.stderr) + traceback.print_exc() + return_code=1 + + if tmpdir: + shutil.rmtree(tmpdir) + sys.exit(return_code) + +if __name__ == '__main__': + main() |