Pour tout problème contactez-nous par mail : support@froggit.fr | La FAQ :grey_question: | Rejoignez-nous sur le Chat :speech_balloon:

Skip to content
Snippets Groups Projects
Commit 633b970c authored by g4m4's avatar g4m4
Browse files

Modernise Cmake and structure

parent e0cf4a02
No related branches found
No related tags found
No related merge requests found
Pipeline #50053 failed
Showing
with 998 additions and 418 deletions
# @brief Required for building SandBox
cmake_minimum_required(VERSION 3.8)
cmake_minimum_required(VERSION 3.22)
project(sandbox)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
# Paths definitions
set(SANDBOX_INCLUDE_DIR
${CMAKE_CURRENT_SOURCE_DIR}
)
# Default parameters
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
# Various detection, to be used as shortcuts below
# Compilers
set(COMPILER_IS_CLANG
0)
set(COMPILER_IS_GCC
0)
set(COMPILER_IS_MSVC
0)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(COMPILER_IS_CLANG
1)
message("Compiler detected as Clang")
elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
set(COMPILER_IS_GCC
1)
message("Compiler detected as gcc")
elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
set(COMPILER_IS_MSVC
1)
message("Compiler detected as MSVC")
else()
message(SEND_ERROR "Could not deduce compiler")
endif()
# Systems
set(SYSTEM_IS_WINDOWS
0)
set(SYSTEM_IS_MACOSX
0)
set(SYSTEM_IS_LINUX
0)
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
set(SYSTEM_IS_WINDOWS
1)
message("System detected as Windows")
elseif (CMAKE_SYSTEM_NAME MATCHES "Darwin")
set(SYSTEM_IS_MACOSX
1)
message("System detected as Mac OS X")
else()
# ${CMAKE_SYSTEM_NAME} MATCHES "Linux" may be true on Mac!
set(SYSTEM_IS_LINUX
1)
message("System detected as Linux")
endif()
# Build configuration
set(BUILD_IS_DEBUG
0)
if (CMAKE_BUILD_TYPE MATCHES "Debug")
set(BUILD_IS_DEBUG
1)
message("Build configuration set to Debug")
else()
message("Build configuration set to Release")
endif()
# Functions
# @brief For the specified target, append compiler flags to the existing ones
#
# @param _TARGET_NAME target name
# @param _FLAGS flags to set
function(add_compiler_flags _TARGET_NAME _FLAGS)
set_property(TARGET ${_TARGET_NAME}
APPEND_STRING PROPERTY COMPILE_FLAGS " ${_FLAGS}"
)
endfunction(add_compiler_flags)
# @brief For the specified target, append linker flags to the existing ones
#
# @param _TARGET_NAME target name
# @param _FLAGS flags to set
function(add_linker_flags _TARGET_NAME _FLAGS)
set_property(TARGET ${_TARGET_NAME}
APPEND PROPERTY LINK_FLAGS " ${_FLAGS}"
)
endfunction(add_linker_flags)
# @brief For the whole project, append debug compiler flags
#
# @param _FLAGS flags to set
function(add_debug_flags _FLAGS)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${_FLAGS}"
PARENT_SCOPE)
endfunction(add_debug_flags)
# @brief For the whole project, append release compiler flags
#
# @param _FLAGS flags to set
function(add_release_flags _FLAGS)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${_FLAGS}"
PARENT_SCOPE)
# Same for the "symbol" configuration
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${_FLAGS}"
PARENT_SCOPE)
endfunction(add_release_flags)
# @brief For the whole project, append release linker flags
#
# @param _FLAGS flags to set
function(add_release_linker_flags _FLAGS)
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${_FLAGS}"
PARENT_SCOPE)
# Same for the "symbol" configuration
set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} ${_FLAGS}"
PARENT_SCOPE)
endfunction(add_release_linker_flags)
# @brief For the given list of files, disable all warnings
#
# @param _TARGETS List of files
function(disable_warnings_files _TARGETS)
if (COMPILER_IS_GCC OR COMPILER_IS_CLANG)
set(FLAG
"-w")
else()
set(FLAG
"/W0")
endif()
SET_SOURCE_FILES_PROPERTIES(${_TARGETS}
PROPERTIES COMPILE_FLAGS " ${FLAG}"
)
endfunction(disable_warnings_files)
# @brief For the given target, disable all warnings
#
# @param _TARGET target name
function(disable_warnings_target _TARGET)
if (COMPILER_IS_CLANG)
set(FLAG
"-Wno-everything")
elseif(COMPILER_IS_GCC OR COMPILER_IS_MSVC)
set(FLAG
"-w")
endif()
add_compiler_flags(${_TARGET} ${FLAG})
endfunction(disable_warnings_target)
# @brief (MSVC only) Set one specific target to use MT runtime library,
# or MTd in debug configuration
#
# @param _TARGET_NAME target name
function(set_target_mt _TARGET_NAME)
if(COMPILER_IS_MSVC)
# Cannot use "add_CONFIG_flags" function here due to variable scoping
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd"
PARENT_SCOPE)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT"
PARENT_SCOPE)
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /MT"
PARENT_SCOPE)
endif(COMPILER_IS_MSVC)
endfunction(set_target_mt)
# Project-wide various options
if (COMPILER_IS_MSVC)
# Multithreaded build
add_definitions("/MP")
endif (COMPILER_IS_MSVC)
# Project-wide warning options
if(COMPILER_IS_GCC OR COMPILER_IS_CLANG)
add_definitions(-pedantic)
add_definitions(-Wall)
add_definitions(-Wextra)
add_definitions(-Wpointer-arith)
add_definitions(-Wcast-qual)
elseif(COMPILER_IS_MSVC)
add_definitions(/W4)
add_definitions(/WX)
# This disable useless msvc warnings about checked iterators
add_definitions(-D_SCL_SECURE_NO_WARNINGS)
endif(COMPILER_IS_GCC OR COMPILER_IS_CLANG)
set(SANDBOX_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
# Debug-only options
if(COMPILER_IS_GCC OR COMPILER_IS_CLANG)
add_debug_flags("-ggdb")
elseif(COMPILER_IS_MSVC)
endif(COMPILER_IS_GCC OR COMPILER_IS_CLANG)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
# Release-only options
if(COMPILER_IS_GCC OR COMPILER_IS_CLANG)
add_release_flags("-Ofast")
add_release_flags("-march=native")
elseif(COMPILER_IS_MSVC)
add_release_linker_flags("/LTCG")
add_release_flags("/O2")
add_release_flags("/GL")
add_release_flags("/fp:fast")
endif(COMPILER_IS_GCC OR COMPILER_IS_CLANG)
include(cmake/static_analysis.cmake)
add_subdirectory(sandbox)
add_subdirectory(cmd)
add_subdirectory(lib)
{
"version": 3,
"configurePresets": [
{
"name": "base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"installDir": "${sourceDir}/out/install/${presetName}",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "clang-tidy",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_CLANG_TIDY": "clang-tidy;--config-file=${sourceDir}/.clang-tidy"
}
},
{
"name": "x64",
"hidden": true,
"architecture": {
"value": "x64",
"strategy": "external"
}
},
{
"name": "cl-flags",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS_INIT": "/W4 -D_SCL_SECURE_NO_WARNINGS",
"CMAKE_CXX_FLAGS_RELEASE_INIT": "-O2 /GL /fp:fast",
"CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT": "-O2 /GL /fp:fast",
"CMAKE_EXE_LINKER_FLAGS_RELEASE_INIT": "/LTCG"
}
},
{
"name": "clang-gcc-flags",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS_INIT": "-pedantic -Wall -Wextra -Wpointer-arith -Wcast-qual",
"CMAKE_CXX_FLAGS_DEBUG_INIT": "-ggdb",
"CMAKE_CXX_FLAGS_RELEASE_INIT": "-Ofast -march=native"
}
},
{
"name": "cl",
"hidden": true,
"inherits": "cl-flags",
"cacheVariables": {
"CMAKE_C_COMPILER": "cl.exe",
"CMAKE_CXX_COMPILER": "cl.exe"
}
},
{
"name": "gcc",
"hidden": true,
"inherits": "clang-gcc-flags",
"cacheVariables": {
"CMAKE_C_COMPILER": "gcc",
"CMAKE_CXX_COMPILER": "g++"
}
},
{
"name": "clang",
"hidden": true,
"inherits": "clang-gcc-flags",
"cacheVariables": {
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++"
}
},
{
"name": "windows",
"hidden": true,
"inherits": "base",
"vendor": {
"microsoft.com/VisualStudioSettings/CMake/1.0": {
"clangTidyChecks": "--config-file=${sourceDir}/.clang-tidy",
"enableMicrosoftCodeAnalysis": true,
"enableClangTidyCodeAnalysis": true
}
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}
},
{
"name": "linux",
"hidden": true,
"inherits": "base",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
},
"vendor": {
"microsoft.com/VisualStudioRemoteSettings/CMake/1.0": {
"sourceDir": "$env{HOME}/.vs/$ms{projectDirName}"
}
}
},
{
"name": "macos",
"hidden": true,
"inherits": "base",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
},
"vendor": {
"microsoft.com/VisualStudioRemoteSettings/CMake/1.0": {
"sourceDir": "$env{HOME}/.vs/$ms{projectDirName}"
}
}
},
{
"name": "windows-cl",
"hidden": true,
"inherits": [ "windows", "cl" ]
},
{
"name": "windows-clang",
"hidden": true,
"inherits": [ "windows", "clang" ]
},
{
"name": "linux-gcc",
"hidden": true,
"inherits": [ "linux", "gcc" ]
},
{
"name": "linux-clang",
"hidden": true,
"inherits": [ "linux", "clang" ]
},
{
"name": "x64-windows-debug",
"inherits": [ "x64", "windows-cl" ],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "x64-windows-asan",
"inherits": "x64-windows-debug",
"environment": {
"CFLAGS": "/fsanitize=address",
"CXXFLAGS": "/fsanitize=address"
}
},
{
"name": "x64-windows-release",
"inherits": "x64-windows-debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
}
},
{
"name": "x64-linux-debug",
"inherits": [ "x64", "linux-clang" ],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "linux-asan",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS": "-fsanitize=address,undefined",
"CMAKE_EXE_LINKER_FLAGS": "-fsanitize=address,undefined"
}
},
{
"name": "x64-linux-asan",
"inherits": [ "x64-linux-debug", "linux-asan"]
},
{
"name": "x64-linux-ci",
"inherits": [ "x64", "linux-gcc", "linux-asan" ],
"binaryDir": "${sourceDir}/build/",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
}
],
"buildPresets": [
{
"name": "x64-windows-debug",
"configurePreset": "x64-windows-debug"
},
{
"name": "x64-windows-asan",
"configurePreset": "x64-windows-asan"
},
{
"name": "x64-windows-release",
"configurePreset": "x64-windows-release"
},
{
"name": "x64-linux-debug",
"configurePreset": "x64-linux-debug"
},
{
"name": "x64-linux-asan",
"configurePreset": "x64-linux-asan"
},
{
"name": "x64-linux-ci",
"configurePreset": "x64-linux-ci"
}
]
}
......@@ -42,17 +42,6 @@ Building is done with:
cmake ..
cmake --build .
The directory hierarchy is as follows:
- sandbox
- implementation
- main.cc
- src
- common.h
- configuration.h
- dummygroup
- dummyclass.cc
- dummyclass.h
The few lines of code there strictly follows [Google Style](http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml).
Branch: GoogleTest
......
function(create_analysis_target target_name)
add_custom_target(${target_name}_analysis DEPENDS ${target_name}_clang_tidy)
# clang-tidy
find_program(CLANG_TIDY_COMMAND NAMES clang-tidy)
find_package(Python COMPONENTS Interpreter)
add_custom_target(
${target_name}_clang_tidy
COMMAND
${Python_EXECUTABLE} "${CMAKE_SOURCE_DIR}/scripts/run-clang-tidy.py"
"-p${CMAKE_BINARY_DIR}" "-config-file=${CMAKE_SOURCE_DIR}/.clang-tidy"
"-header-filter=.*sandbox(?!.*_deps).*"
"-source-filter=.*sandbox(?!.*_deps).*"
VERBATIM USES_TERMINAL)
endfunction()
# @brief SandBox standalone root cmake file
add_executable(sandbox_cmd)
target_sources(sandbox_cmd PRIVATE "main.cc")
target_link_libraries(sandbox_cmd PUBLIC sandbox_lib)
......@@ -20,14 +20,15 @@
#include <cstdio>
#include "sandbox/src/dummygroup/dummyclass.h"
#include "sandbox/dummygroup/dummygroup.h"
/// @brief Main function, of course.
int main(int /*argc*/, char ** /*argv*/) {
sandbox::dummygroup::DummyClass test;
const char* kText(test.GetSomething());
int main(int /*argc*/, char** /*argv*/)
{
auto test = sandbox::dummygroup::DummyGroup::Make();
const char* kText(test->GetSomething());
printf("Done: %s\n", kText);
std::printf("Done: %s\n", kText);
return 0;
}
......@@ -18,12 +18,11 @@
/// You should have received a copy of the GNU General Public License
/// along with SandBox. If not, see <http://www.gnu.org/licenses/>.
#ifndef SANDBOX_SRC_COMMON_H_
#define SANDBOX_SRC_COMMON_H_
#pragma once
#include <cassert>
#include "sandbox/src/configuration.h"
#include "sandbox/configuration.h"
namespace sandbox {
......@@ -46,5 +45,3 @@ namespace sandbox {
#endif
} // namespace sandbox
#endif // SANDBOX_SRC_COMMON_H_
......@@ -22,8 +22,7 @@
/// You should have received a copy of the GNU General Public License
/// along with SandBox. If not, see <http://www.gnu.org/licenses/>.
#ifndef SANDBOX_SRC_CONFIGURATION_H_
#define SANDBOX_SRC_CONFIGURATION_H_
#pragma once
/// @brief Compiler detection
#if (defined(__GNUC__)) && !defined(__clang__) && !defined(__INTEL_COMPILER)
......@@ -71,5 +70,3 @@
#if (ARCH_X86_64)
#define USE_SSE 1
#endif
#endif // SANDBOX_SRC_CONFIGURATION_H_
/// @file dummygroup.h
/// @author gm
/// @copyright gm
///
/// This file is part of SandBox
///
/// SandBox is free software: you can redistribute it and/or modify
/// it under the terms of the GNU General Public License as published by
/// the Free Software Foundation, either version 3 of the License, or
/// (at your option) any later version.
///
/// SandBox is distributed in the hope that it will be useful,
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/// GNU General Public License for more details.
///
/// You should have received a copy of the GNU General Public License
/// along with SandBox. If not, see <http://www.gnu.org/licenses/>.
#pragma once
#include <memory>
namespace sandbox
{
namespace dummygroup
{
/// @brief Base class for dummies
class DummyClassBase
{
public:
DummyClassBase() = default;
virtual ~DummyClassBase() = default;
DummyClassBase(const DummyClassBase&) = delete;
DummyClassBase(DummyClassBase&&) = delete;
DummyClassBase& operator=(const DummyClassBase&) = delete;
DummyClassBase& operator=(DummyClassBase&&) = delete;
/// @brief Outputs a C-style string
virtual const char* GetSomething(void) = 0;
};
/// @brief Dummy group module, main entry point
class DummyGroup
{
public:
static std::unique_ptr<DummyClassBase> Make();
};
} // namespace dummygroup
} // namespace sandbox
# @brief SandBox main source folder cmake file
add_library(sandbox_lib)
target_include_directories(
sandbox_lib
PUBLIC $<BUILD_INTERFACE:${SANDBOX_INCLUDE_DIR}/include>
PRIVATE ${SANDBOX_INCLUDE_DIR}/lib)
target_sources(sandbox_lib PRIVATE "dummygroup/dummyclass.cc"
"dummygroup/dummygroup.cc")
create_analysis_target(sandbox_lib)
......@@ -18,12 +18,12 @@
/// You should have received a copy of the GNU General Public License
/// along with SandBox. If not, see <http://www.gnu.org/licenses/>.
#include "sandbox/src/dummygroup/dummyclass.h"
#include <cstring>
#include <memory>
#include "sandbox/src/common.h"
#include "sandbox/common.h"
#include "dummygroup/dummyclass.h"
namespace sandbox
{
......@@ -34,18 +34,13 @@ static const char static_data[] = "Hello, World!";
DummyClass::DummyClass() : data_(new char[sizeof(static_data)])
{
std::memcpy(data_.get(), &static_data[0], sizeof(static_data));
}
DummyClass::~DummyClass()
{
// Nothing to do here for now
std::memcpy(data_.get(), &static_data[0], sizeof(static_data));
}
const char* DummyClass::GetSomething(void)
{
SANDBOX_ASSERT(data_);
return data_.get();
SANDBOX_ASSERT(data_);
return data_.get();
}
} // namespace dummygroup
......
......@@ -22,24 +22,26 @@
#include <memory>
#include "sandbox/dummygroup/dummygroup.h"
namespace sandbox
{
namespace dummygroup
{
/// @brief Dummy class
class DummyClass
class DummyClass : public DummyClassBase
{
public:
DummyClass();
~DummyClass();
~DummyClass() override = default;
DummyClass(const DummyClass&) = delete;
DummyClass(DummyClass&&) = delete;
DummyClass& operator=(const DummyClass&) = delete;
DummyClass& operator=(DummyClass&&) = delete;
/// @brief Outputs an "Hello, world!" into a C-style string
const char* GetSomething(void);
const char* GetSomething(void) override;
private:
const std::unique_ptr<char[]> data_; ///< Internal class data
......
/// @file dummygroup.cc
/// @brief Dummy class definition
/// @author gm
/// @copyright gm
///
/// This file is part of SandBox
///
/// SandBox is free software: you can redistribute it and/or modify
/// it under the terms of the GNU General Public License as published by
/// the Free Software Foundation, either version 3 of the License, or
/// (at your option) any later version.
///
/// SandBox is distributed in the hope that it will be useful,
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/// GNU General Public License for more details.
///
/// You should have received a copy of the GNU General Public License
/// along with SandBox. If not, see <http://www.gnu.org/licenses/>.
#include "sandbox/dummygroup/dummygroup.h"
#include <memory>
#include "dummygroup/dummyclass.h"
namespace sandbox
{
namespace dummygroup
{
std::unique_ptr<DummyClassBase> DummyGroup::Make()
{
return std::make_unique<DummyClass>();
}
} // namespace dummygroup
} // namespace sandbox
# @brief SandBox root cmake file
add_subdirectory(implementation)
add_subdirectory(src)
# @brief SandBox implementation root cmake file
add_subdirectory(common)
add_subdirectory(standalone)
# Retrieve all implementation common source files
file(GLOB
SANDBOX_IMPLEMENTATION_SRC
*.cc
*.cpp
)
# Expose variables to parent CMake files
set(SANDBOX_IMPLEMENTATION_SRC
${SANDBOX_IMPLEMENTATION_SRC}
PARENT_SCOPE
)
file(GLOB
SANDBOX_IMPLEMENTATION_HDR
*.h
)
# Expose variables to parent CMake files
set(SANDBOX_IMPLEMENTATION_HDR
${SANDBOX_IMPLEMENTATION_HDR}
PARENT_SCOPE
)
# @brief SandBox standalone root cmake file
set(SANDBOX_STANDALONE_DIR
${CMAKE_CURRENT_SOURCE_DIR}
)
include_directories(
${SANDBOX_INCLUDE_DIR}
)
# Look for all standalone-specific files
set (SANDBOX_STANDALONE_SRC
${SANDBOX_STANDALONE_DIR}/main.cc
)
set (SANDBOX_STANDALONE_HDR
# Nothing in here for now
)
source_group("sandbox" FILES ${SANDBOX_SRC} ${SANDBOX_HDR})
source_group("sandbox_implementation" FILES ${SANDBOX_IMPLEMENTATION_SRC} ${SANDBOX_IMPLEMENTATION_HDR})
source_group("standalone_specifics" FILES ${SANDBOX_STANDALONE_SRC} ${SANDBOX_STANDALONE_HDR})
add_executable(sandbox
${SANDBOX_SRC}
${SANDBOX_HDR}
${SANDBOX_IMPLEMENTATION_SRC}
${SANDBOX_IMPLEMENTATION_HDR}
${SANDBOX_STANDALONE_SRC}
${SANDBOX_STANDALONE_HDR}
)
target_link_libraries(sandbox
sandbox_lib
)
if (COMPILER_IS_MSVC)
set_target_properties(sandbox PROPERTIES
VS_GLOBAL_RunCodeAnalysis false
# Use visual studio core guidelines
VS_GLOBAL_EnableMicrosoftCodeAnalysis false
# Use clang-tidy
VS_GLOBAL_EnableClangTidyCodeAnalysis true
VS_GLOBAL_ClangTidyChecks "--config-file=${SANDBOX_LIB_INCLUDE_DIR}/.clang-tidy"
)
target_compile_options(sandbox PRIVATE $<$<CONFIG:Debug>:-fsanitize-address>)
elseif (COMPILER_IS_GCC OR COMPILER_IS_CLANG)
target_compile_options(sandbox PRIVATE $<$<CONFIG:Debug>:-fsanitize=address,leak>)
target_link_options(sandbox PRIVATE $<$<CONFIG:Debug>:-fsanitize=address,leak>)
endif()
set_target_mt(sandbox)
# @brief SandBox main source folder cmake file
include_directories(
${SANDBOX_INCLUDE_DIR}
)
# Retrieve source files from subdirectories
add_subdirectory(dummygroup)
# Group sources
source_group("dummygroup"
FILES
${SANDBOX_DUMMYGROUP_SRC}
${SANDBOX_DUMMYGROUP_HDR}
)
# Sources
set(SANDBOX_SRC
${SANDBOX_DUMMYGROUP_SRC}
)
set(SANDBOX_HDR
configuration.h
common.h
${SANDBOX_DUMMYGROUP_HDR}
)
# Target
add_library(sandbox_lib
${SANDBOX_SRC}
${SANDBOX_HDR}
)
# Those are configuration files we want to include in e.g. msvc generated files
set(SANDBOX_GLOBAL
${SANDBOX_INCLUDE_DIR}/.clang-format
${SANDBOX_INCLUDE_DIR}/.clang-tidy
${SANDBOX_INCLUDE_DIR}/.clangd
)
target_sources(sandbox_lib
PRIVATE
${SANDBOX_GLOBAL}
)
source_group(global
FILES
${SANDBOX_GLOBAL}
)
if (COMPILER_IS_MSVC)
set_target_properties(sandbox_lib PROPERTIES
VS_GLOBAL_RunCodeAnalysis false
# Use visual studio core guidelines
VS_GLOBAL_EnableMicrosoftCodeAnalysis false
# Use clang-tidy
VS_GLOBAL_EnableClangTidyCodeAnalysis true
VS_GLOBAL_ClangTidyChecks "--config-file=${SANDBOX_LIB_INCLUDE_DIR}/.clang-tidy"
)
target_compile_options(sandbox_lib PRIVATE $<$<CONFIG:Debug>:-fsanitize-address>)
elseif (COMPILER_IS_GCC OR COMPILER_IS_CLANG)
target_compile_options(sandbox_lib PRIVATE $<$<CONFIG:Debug>:-fsanitize=address,leak>)
target_link_options(sandbox_lib PRIVATE $<$<CONFIG:Debug>:-fsanitize=address,leak>)
endif()
set_target_mt(sandbox_lib)
# Retrieve all dummygroup source files
file(GLOB
SANDBOX_DUMMYGROUP_SRC
*.cc
)
# Expose variables to parent CMake files
set(SANDBOX_DUMMYGROUP_SRC
${SANDBOX_DUMMYGROUP_SRC}
PARENT_SCOPE
)
file(GLOB
SANDBOX_DUMMYGROUP_HDR
*.h
)
# Expose variables to parent CMake files
set(SANDBOX_DUMMYGROUP_HDR
${SANDBOX_DUMMYGROUP_HDR}
PARENT_SCOPE
)
#!/usr/bin/env python3
#
# ===- 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
"""
import argparse
import asyncio
from dataclasses import dataclass
import glob
import json
import multiprocessing
import os
import re
import shutil
import subprocess
import sys
import tempfile
import time
import traceback
from types import ModuleType
from typing import Any, Awaitable, Callable, List, Optional, TypeVar
yaml: Optional[ModuleType] = None
try:
import yaml
except ImportError:
yaml = None
def strtobool(val: str) -> bool:
"""Convert a string representation of truth to a bool following LLVM's CLI argument parsing."""
val = val.lower()
if val in ["", "true", "1"]:
return True
elif val in ["false", "0"]:
return False
# Return ArgumentTypeError so that argparse does not substitute its own error message
raise argparse.ArgumentTypeError(
f"'{val}' is invalid value for boolean argument! Try 0 or 1."
)
def find_compilation_database(path: str) -> str:
"""Adjusts the directory until a compilation database is found."""
result = os.path.realpath("./")
while not os.path.isfile(os.path.join(result, path)):
parent = os.path.dirname(result)
if result == parent:
print("Error: could not find compilation database.")
sys.exit(1)
result = parent
return result
def get_tidy_invocation(
f: str,
clang_tidy_binary: str,
checks: str,
tmpdir: Optional[str],
build_path: str,
header_filter: Optional[str],
allow_enabling_alpha_checkers: bool,
extra_arg: List[str],
extra_arg_before: List[str],
quiet: bool,
config_file_path: str,
config: str,
line_filter: Optional[str],
use_color: bool,
plugins: List[str],
warnings_as_errors: Optional[str],
exclude_header_filter: Optional[str],
allow_no_checks: bool,
) -> List[str]:
"""Gets a command line for clang-tidy."""
start = [clang_tidy_binary]
if allow_enabling_alpha_checkers:
start.append("-allow-enabling-analyzer-alpha-checkers")
if exclude_header_filter is not None:
start.append(f"--exclude-header-filter={exclude_header_filter}")
if header_filter is not None:
start.append(f"-header-filter={header_filter}")
if line_filter is not None:
start.append(f"-line-filter={line_filter}")
if use_color is not None:
if use_color:
start.append("--use-color")
else:
start.append("--use-color=false")
if checks:
start.append(f"-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(f"-extra-arg={arg}")
for arg in extra_arg_before:
start.append(f"-extra-arg-before={arg}")
start.append(f"-p={build_path}")
if quiet:
start.append("-quiet")
if config_file_path:
start.append(f"--config-file={config_file_path}")
elif config:
start.append(f"-config={config}")
for plugin in plugins:
start.append(f"-load={plugin}")
if warnings_as_errors:
start.append(f"--warnings-as-errors={warnings_as_errors}")
if allow_no_checks:
start.append("--allow-no-checks")
start.append(f)
return start
def merge_replacement_files(tmpdir: str, mergefile: str) -> None:
"""Merge all replacement files in a directory into a single file"""
assert yaml
# 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 find_binary(arg: str, name: str, build_path: str) -> str:
"""Get the path for a binary or exit"""
if arg:
if shutil.which(arg):
return arg
else:
raise SystemExit(
f"error: passed binary '{arg}' was not found or is not executable"
)
built_path = os.path.join(build_path, "bin", name)
binary = shutil.which(name) or shutil.which(built_path)
if binary:
return binary
else:
raise SystemExit(f"error: failed to find {name} in $PATH or at {built_path}")
def apply_fixes(
args: argparse.Namespace, clang_apply_replacements_binary: str, tmpdir: str
) -> None:
"""Calls clang-apply-fixes on a given directory."""
invocation = [clang_apply_replacements_binary]
invocation.append("-ignore-insert-conflict")
if args.format:
invocation.append("-format")
if args.style:
invocation.append(f"-style={args.style}")
invocation.append(tmpdir)
subprocess.call(invocation)
# FIXME Python 3.12: This can be simplified out with run_with_semaphore[T](...).
T = TypeVar("T")
async def run_with_semaphore(
semaphore: asyncio.Semaphore,
f: Callable[..., Awaitable[T]],
*args: Any,
**kwargs: Any,
) -> T:
async with semaphore:
return await f(*args, **kwargs)
@dataclass
class ClangTidyResult:
filename: str
invocation: List[str]
returncode: int
stdout: str
stderr: str
elapsed: float
async def run_tidy(
args: argparse.Namespace,
name: str,
clang_tidy_binary: str,
tmpdir: str,
build_path: str,
) -> ClangTidyResult:
"""
Runs clang-tidy on a single file and returns the result.
"""
invocation = get_tidy_invocation(
name,
clang_tidy_binary,
args.checks,
tmpdir,
build_path,
args.header_filter,
args.allow_enabling_alpha_checkers,
args.extra_arg,
args.extra_arg_before,
args.quiet,
args.config_file,
args.config,
args.line_filter,
args.use_color,
args.plugins,
args.warnings_as_errors,
args.exclude_header_filter,
args.allow_no_checks,
)
try:
process = await asyncio.create_subprocess_exec(
*invocation, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
start = time.time()
stdout, stderr = await process.communicate()
end = time.time()
except asyncio.CancelledError:
process.terminate()
await process.wait()
raise
assert process.returncode is not None
return ClangTidyResult(
name,
invocation,
process.returncode,
stdout.decode("UTF-8"),
stderr.decode("UTF-8"),
end - start,
)
async def main() -> None:
parser = argparse.ArgumentParser(
description="Runs clang-tidy over all files "
"in a compilation database. Requires "
"clang-tidy and clang-apply-replacements in "
"$PATH or in your build directory."
)
parser.add_argument(
"-allow-enabling-alpha-checkers",
action="store_true",
help="Allow alpha checkers from clang-analyzer.",
)
parser.add_argument(
"-clang-tidy-binary", metavar="PATH", help="Path to clang-tidy binary."
)
parser.add_argument(
"-clang-apply-replacements-binary",
metavar="PATH",
help="Path to clang-apply-replacements binary.",
)
parser.add_argument(
"-checks",
default=None,
help="Checks filter, when not specified, use clang-tidy default.",
)
config_group = parser.add_mutually_exclusive_group()
config_group.add_argument(
"-config",
default=None,
help="Specifies a configuration in YAML/JSON format: "
" -config=\"{Checks: '*', "
' CheckOptions: {x: 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.",
)
config_group.add_argument(
"-config-file",
default=None,
help="Specify the path of .clang-tidy or custom config "
"file: e.g. -config-file=/some/path/myTidyConfigFile. "
"This option internally works exactly the same way as "
"-config option after reading specified config file. "
"Use either -config-file or -config, not both.",
)
parser.add_argument(
"-exclude-header-filter",
default=None,
help="Regular expression matching the names of the "
"headers to exclude diagnostics from. Diagnostics from "
"the main file of each translation unit are always "
"displayed.",
)
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.",
)
parser.add_argument(
"-source-filter",
default=None,
help="Regular expression matching the names of the "
"source files from compilation database to output "
"diagnostics from.",
)
parser.add_argument(
"-line-filter",
default=None,
help="List of files with line ranges to filter the warnings.",
)
if yaml:
parser.add_argument(
"-export-fixes",
metavar="file_or_directory",
dest="export_fixes",
help="A directory or a yaml file to store suggested fixes in, "
"which can be applied with clang-apply-replacements. If the "
"parameter is a directory, the fixes of each compilation unit are "
"stored in individual yaml files in the directory.",
)
else:
parser.add_argument(
"-export-fixes",
metavar="directory",
dest="export_fixes",
help="A directory to store suggested fixes in, which can be applied "
"with clang-apply-replacements. The fixes of each compilation unit are "
"stored in individual yaml files in the directory.",
)
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(
"-use-color",
type=strtobool,
nargs="?",
const=True,
help="Use colors in diagnostics, overriding clang-tidy's"
" default behavior. This option overrides the 'UseColor"
"' option in .clang-tidy file, if any.",
)
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."
)
parser.add_argument(
"-load",
dest="plugins",
action="append",
default=[],
help="Load the specified plugin in clang-tidy.",
)
parser.add_argument(
"-warnings-as-errors",
default=None,
help="Upgrades warnings to errors. Same format as '-checks'.",
)
parser.add_argument(
"-allow-no-checks",
action="store_true",
help="Allow empty enabled checks.",
)
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)
clang_tidy_binary = find_binary(args.clang_tidy_binary, "clang-tidy", build_path)
if args.fix:
clang_apply_replacements_binary = find_binary(
args.clang_apply_replacements_binary, "clang-apply-replacements", build_path
)
combine_fixes = False
export_fixes_dir: Optional[str] = None
delete_fixes_dir = False
if args.export_fixes is not None:
# if a directory is given, create it if it does not exist
if args.export_fixes.endswith(os.path.sep) and not os.path.isdir(
args.export_fixes
):
os.makedirs(args.export_fixes)
if not os.path.isdir(args.export_fixes):
if not yaml:
raise RuntimeError(
"Cannot combine fixes in one yaml file. Either install PyYAML or specify an output directory."
)
combine_fixes = True
if os.path.isdir(args.export_fixes):
export_fixes_dir = args.export_fixes
if export_fixes_dir is None and (args.fix or combine_fixes):
export_fixes_dir = tempfile.mkdtemp()
delete_fixes_dir = True
try:
invocation = get_tidy_invocation(
"",
clang_tidy_binary,
args.checks,
None,
build_path,
args.header_filter,
args.allow_enabling_alpha_checkers,
args.extra_arg,
args.extra_arg_before,
args.quiet,
args.config_file,
args.config,
args.line_filter,
args.use_color,
args.plugins,
args.warnings_as_errors,
args.exclude_header_filter,
args.allow_no_checks,
)
invocation.append("-list-checks")
invocation.append("-")
# Even with -quiet we still want to check if we can call clang-tidy.
subprocess.check_call(
invocation, stdout=subprocess.DEVNULL if args.quiet else None
)
except:
print("Unable to run clang-tidy.", file=sys.stderr)
sys.exit(1)
# Load the database and extract all files.
with open(os.path.join(build_path, db_path)) as f:
database = json.load(f)
files = {os.path.abspath(os.path.join(e["directory"], e["file"])) for e in database}
number_files_in_database = len(files)
# Filter source files from compilation database.
if args.source_filter:
try:
source_filter_re = re.compile(args.source_filter)
except:
print(
"Error: unable to compile regex from arg -source-filter:",
file=sys.stderr,
)
traceback.print_exc()
sys.exit(1)
files = {f for f in files if source_filter_re.match(f)}
max_task = args.j
if max_task == 0:
max_task = multiprocessing.cpu_count()
# Build up a big regexy filter from all command line arguments.
file_name_re = re.compile("|".join(args.files))
files = {f for f in files if file_name_re.search(f)}
print(
"Running clang-tidy for",
len(files),
"files out of",
number_files_in_database,
"in compilation database ...",
)
returncode = 0
semaphore = asyncio.Semaphore(max_task)
tasks = [
asyncio.create_task(
run_with_semaphore(
semaphore,
run_tidy,
args,
f,
clang_tidy_binary,
export_fixes_dir,
build_path,
)
)
for f in files
]
try:
for i, coro in enumerate(asyncio.as_completed(tasks)):
result = await coro
if result.returncode != 0:
returncode = 1
if result.returncode < 0:
result.stderr += f"{result.filename}: terminated by signal {-result.returncode}\n"
progress = f"[{i + 1: >{len(f'{len(files)}')}}/{len(files)}]"
runtime = f"[{result.elapsed:.1f}s]"
print(f"{progress}{runtime} {' '.join(result.invocation)}")
if result.stdout:
print(result.stdout, end=("" if result.stderr else "\n"))
if result.stderr:
print(result.stderr)
except asyncio.CancelledError:
print("\nCtrl-C detected, goodbye.")
for task in tasks:
task.cancel()
if delete_fixes_dir:
assert export_fixes_dir
shutil.rmtree(export_fixes_dir)
return
if combine_fixes:
print(f"Writing fixes to {args.export_fixes} ...")
try:
assert export_fixes_dir
merge_replacement_files(export_fixes_dir, args.export_fixes)
except:
print("Error exporting fixes.\n", file=sys.stderr)
traceback.print_exc()
returncode = 1
if args.fix:
print("Applying fixes ...")
try:
assert export_fixes_dir
apply_fixes(args, clang_apply_replacements_binary, export_fixes_dir)
except:
print("Error applying fixes.\n", file=sys.stderr)
traceback.print_exc()
returncode = 1
if delete_fixes_dir:
assert export_fixes_dir
shutil.rmtree(export_fixes_dir)
sys.exit(returncode)
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
pass
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment