Skip to content

Commit

Permalink
Revamp IWYU linting
Browse files Browse the repository at this point in the history
  • Loading branch information
StarQTius committed Aug 2, 2024
1 parent 8dc48e9 commit 17cdc76
Show file tree
Hide file tree
Showing 21 changed files with 322 additions and 79 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
!include/
!py/
!test/
!tool/
!cmake/
!.clang-format
!.clang-tidy
!.gitignore
Expand Down
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.21)
cmake_minimum_required(VERSION 3.27)

project(Unpadded LANGUAGES CXX)
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/tool)
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/module)
option(${PROJECT_NAME}_INSTALL "Only make what is needed for installing" OFF)
option(${PROJECT_NAME}_PLATFORM_ENDIANESS
"Provide platform endianess and allow serialization optimization" OFF)
Expand Down
33 changes: 33 additions & 0 deletions cmake/module/Genex.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
include(NormalizePath)

# `properties_genexpr(<OUTPUT> <TARGET> <PROPERTIES...>)`
#
# Build a generator expression that expresses `(<property name from PROPERTIES>
# <corresponding property value of TARGET>)...` and store it in `OUTPUT`
function(properties_genex OUTPUT TARGET)
foreach(PROP IN LISTS ARGN)
list(APPEND RETVAL ${PROP} $<TARGET_PROPERTY:${TARGET},${PROP}>)
endforeach()

set(${OUTPUT}
${RETVAL}
PARENT_SCOPE)
endfunction()

# `is_source_of_target_genex(<OUTPUT> <TARGET> <FILE>)`
#
# Build a generator expression that checks whether `FILE` is a source of
# `TARGET` and store it in `OUTPUT`
function(is_source_of_target_genex OUTPUT TARGET FILE)
normalize_path(FILE)

set(SOURCES_GENEX $<TARGET_PROPERTY:${TARGET},SOURCES>)
set(SOURCE_DIR_GENEX $<TARGET_PROPERTY:${TARGET},SOURCE_DIR>)
set(ABSOLUTE_SOURCES_GENEX
$<PATH:ABSOLUTE_PATH,NORMALIZE,${SOURCES_GENEX},${SOURCE_DIR_GENEX}>)
set(FIND_FILE_IN_SOURCES_GENEX $<LIST:FIND,${ABSOLUTE_SOURCES_GENEX},${FILE}>)

set(${OUTPUT}
"$<NOT:$<EQUAL:${FIND_FILE_IN_SOURCES_GENEX},-1>>"
PARENT_SCOPE)
endfunction()
96 changes: 96 additions & 0 deletions cmake/module/Linting.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
include(NormalizePath)
include(Genex)
include(ScriptAsCommand)
include(Paths)

set(LINTING_PROPERTIES
INCLUDE_DIRECTORIES
INTERFACE_INCLUDE_DIRECTORIES
COMPILE_DEFINITIONS
INTERFACE_COMPILE_DEFINITIONS
COMPILE_FEATURES
INTERFACE_COMPILE_FEATURES
COMPILE_OPTIONS
INTERFACE_COMPILE_OPTIONS)

define_property(
TARGET
PROPERTY LINT_COMPLETION_MARKERS
BRIEF_DOCS "List of files which indicates linting operation completion"
FULL_DOCS "A target created by `add_lint_target()` will depend on these \
files. See `add_lint_target()` and `target_lintables()` respective \
documentations to learn how this property is populated.")

define_property(
TARGET
PROPERTY LINTER_COMMAND
BRIEF_DOCS "Command to invoke when linting"
FULL_DOCS
"The command is run when a target created by `add_lint_target()` is built.")

# `add_lint_target(<NAME> <LINTABLE_TARGET> <COMMAND...>)`
#
# Create the target `NAME` that lints the executable or library
# `LINTABLE_TARGET` with `COMMAND...`
#
# When `NAME` is built, `COMMAND...` is invoked on each source file of
# `LINTABLE_TARGET` that currently appears in its `SOURCES` property (sources
# added after will NOT be linted if not added with `target_lintables()`).
# `COMMAND...` is stored in the `LINTER_COMMAND` property of `NAME`.
#
# Linting-related properties of `LINTABLE_TARGET` are transfered to `NAME`
# (those are listed in `LINTING_PROPERTIES`).
#
# See `target_lintable()` for more information.
function(add_lint_target NAME LINTABLE_TARGET)
add_custom_target(${NAME}
DEPENDS $<TARGET_PROPERTY:${NAME},LINT_COMPLETION_MARKERS>)

set_property(TARGET ${NAME} PROPERTY LINTER_COMMAND ${ARGN})
foreach(PROP IN LISTS LINTING_PROPERTIES)
set_property(TARGET ${NAME}
PROPERTY ${PROP} $<TARGET_PROPERTY:${LINTABLE_TARGET},${PROP}>)
endforeach()

get_property(
TARGET_SOURCES
TARGET ${LINTABLE_TARGET}
PROPERTY SOURCES)
target_lintables(${NAME} ${TARGET_SOURCES})
endfunction()

# `target_lintables(<TARGET> <SOURCE_FILES...>)`
#
# Add source files to lint to a target `TARGET` created with `add_lint_target()`
#
# The explicit command is `<TARGET LINTABLE_COMMAND value> <source file>
# (<linting-related property name> <TARGET linting-related property value>)...`.
function(target_lintables TARGET)
foreach(LINTABLE IN LISTS ARGN)
normalize_path(LINTABLE)
properties_genex(LINTING_PROPERTIES_GENEX ${TARGET} ${LINTING_PROPERTIES})
is_source_of_target_genex(IS_LINTABLE_SOURCE_GENEX ${TARGET} ${LINTABLE})
script_as_command(TOUCH_COMMAND ${TOUCH_SCRIPT})

cmake_path(RELATIVE_PATH LINTABLE BASE_DIRECTORY ${PROJECT_SOURCE_DIR}
OUTPUT_VARIABLE LINTABLE_RELATIVE_PATH)
cmake_path(ABSOLUTE_PATH LINTABLE_RELATIVE_PATH BASE_DIRECTORY
${PROJECT_BINARY_DIR} OUTPUT_VARIABLE COMPLETION_MARKER)
cmake_path(APPEND_STRING COMPLETION_MARKER .${TARGET})

add_custom_command(
OUTPUT ${COMPLETION_MARKER}
COMMAND
$<TARGET_PROPERTY:${TARGET},LINTER_COMMAND> ${LINTABLE}
${LINTING_PROPERTIES_GENEX} $<${IS_LINTABLE_SOURCE_GENEX}:IS_SOURCE>
COMMAND ${TOUCH_COMMAND} ${COMPLETION_MARKER}
IMPLICIT_DEPENDS CXX ${LINTABLE}
COMMENT "${TARGET} -- ${LINTABLE_RELATIVE_PATH}"
VERBATIM COMMAND_EXPAND_LISTS)

set_property(
TARGET ${TARGET}
APPEND
PROPERTY LINT_COMPLETION_MARKERS ${COMPLETION_MARKER})
endforeach()
endfunction()
19 changes: 19 additions & 0 deletions cmake/module/NormalizePath.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# `normalize_path(<PATH_VAR>)`
#
# Normalize `PATH_VAR` so that it is expressed as an absolute CMake-style path
#
# If `PATH_VAR` is relative, it is considered relative to the current source
# directory.
function(normalize_path PATH_VAR)
set(RETVAL ${${PATH_VAR}})
cmake_path(SET RETVAL NORMALIZE ${RETVAL})
cmake_path(IS_RELATIVE RETVAL IS_INPUT_RELATIVE)

if(IS_INPUT_RELATIVE)
cmake_path(ABSOLUTE_PATH RETVAL)
endif()

set(${PATH_VAR}
${RETVAL}
PARENT_SCOPE)
endfunction()
46 changes: 46 additions & 0 deletions cmake/module/ParseCliArguments.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
include(RequireDefined)

# `parse_cli_function(POSITIONALS <POSITIONAL_ARGUMENT_NAMES...> KEYWORDS
# <KEYWORD_ARGUMENT_NAMES...> OPTIONS <OPTIONAL_ARGUMENT_NAMES...>)`
#
# Parse the arguments passed from command line and set variables whose names
# belong to `POSITIONAL_ARGUMENT_NAMES`, `KEYWORD_ARGUMENT_NAMES` and
# `OPTIONAL_ARGUMENT_NAMES`
#
# The commandline invokation must comply with the following format: `<cmake
# command> <cmake command flags> -P <invoked script> -- <positional arguments>
# (<keyword from KEYWORD_ARGUMENT_NAMES> <value>)... <option from
# OPTIONAL_ARGUMENT_NAMES>...`. Positional arguments are mandatory, keyword and
# optional arguments are not.
function(parse_cli_arguments)
foreach(I RANGE ${CMAKE_ARGC})
list(APPEND CLI_ARGV ${CMAKE_ARGV${I}})
endforeach()

cmake_parse_arguments(PARSE_ARGV 0 "PARSED" "" ""
"POSITIONALS;KEYWORDS;OPTIONS")
cmake_parse_arguments("ARGV" "${PARSED_OPTIONS}" "" "--;${PARSED_KEYWORDS}"
${CLI_ARGV})

set(POSITIONALS ${ARGV_--})
foreach(KEYWORD VALUE IN ZIP_LISTS PARSED_POSITIONALS POSITIONALS)
require_defined_with_message(KEYWORD "Too many positional arguments")
require_defined_with_message(
VALUE "`${KEYWORD}` positional argument must be provided")
set(${KEYWORD}
${VALUE}
PARENT_SCOPE)
endforeach()

foreach(KEYWORD IN LISTS PARSED_KEYWORDS)
set(${KEYWORD}
${ARGV_${KEYWORD}}
PARENT_SCOPE)
endforeach()

foreach(OPTION IN LISTS PARSED_OPTIONS)
set(${OPTION}
${ARGV_${OPTION}}
PARENT_SCOPE)
endforeach()
endfunction()
5 changes: 5 additions & 0 deletions cmake/module/Paths.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
cmake_path(APPEND MODULE_PATH ${PROJECT_SOURCE_DIR} cmake module)

cmake_path(APPEND IWYU_LINTER_SCRIPT ${PROJECT_SOURCE_DIR} cmake script
run-include-what-you-use.cmake)
cmake_path(APPEND TOUCH_SCRIPT ${PROJECT_SOURCE_DIR} cmake script touch.cmake)
17 changes: 17 additions & 0 deletions cmake/module/RequireDefined.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# `require_defined(<VARIABLE>)`
#
# Assert that `VARIABLE` is defined
macro(require_defined VARIABLE)
if(NOT DEFINED ${VARIABLE})
message(FATAL_ERROR "`${VARIABLE}` should have been defined")
endif()
endmacro()

# `require_defined_with_message(<VARIABLE> <MESSAGE>)`
#
# Assert that `VARIABLE` is defined and show `MESSAGE` if not
macro(require_defined_with_message VARIABLE MESSAGE)
if(NOT DEFINED ${VARIABLE})
message(FATAL_ERROR "${MESSAGE}")
endif()
endmacro()
11 changes: 11 additions & 0 deletions cmake/module/ScriptAsCommand.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
include(Paths)

# `script_as_command(<OUTPUT> <SCRIPT>)`
#
# Take a CMake script, build the corresponding command to invoke it and store it
# in `OUTPUT`
function(script_as_command OUTPUT SCRIPT)
set(${OUTPUT}
${CMAKE_COMMAND} -DCMAKE_MODULE_PATH=${MODULE_PATH} -P ${SCRIPT} --
PARENT_SCOPE)
endfunction()
11 changes: 11 additions & 0 deletions cmake/module/Unpadded.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
include(Paths)
include(Linting)

# `add_iwyu_target(<NAME> <LINTABLE_TARGET>)`
#
# Create the lint target `NAME` that lints `LINTABLE_TARGET` with Include What
# You Use
function(add_iwyu_target NAME LINTABLE_TARGET)
script_as_command(IWYU_LINTER_COMMAND ${IWYU_LINTER_SCRIPT})
add_lint_target(${NAME} ${LINTABLE_TARGET} ${IWYU_LINTER_COMMAND})
endfunction()
59 changes: 59 additions & 0 deletions cmake/script/run-include-what-you-use.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
cmake_minimum_required(VERSION 3.5)

include(ParseCliArguments)

parse_cli_arguments(
POSITIONALS
SOURCE_FILE
KEYWORDS
INCLUDE_DIRECTORIES
INTERFACE_INCLUDE_DIRECTORIES
COMPILE_DEFINITIONS
INTERFACE_COMPILE_DEFINITIONS
COMPILE_FEATURES
INTERFACE_COMPILE_FEATURES
COMPILE_OPTIONS
INTERFACE_COMPILE_OPTIONS
OPTIONS
IS_SOURCE)

find_program(IWYU_COMMAND include-what-you-use
DOC "Check inclusion in C and C++ programs" REQUIRED)

if(NOT IS_SOURCE)
set(INCLUDE_DIRECTORIES ${INTERFACE_INCLUDE_DIRECTORIES})
set(COMPILE_DEFINITIONS ${INTERFACE_COMPILE_DEFINITIONS})
set(COMPILE_FEATURES ${INTERFACE_COMPILE_FEATURES})
set(COMPILE_OPTIONS ${INTERFACE_COMPILE_OPTIONS})
endif()

cmake_path(CONVERT "${SOURCE_FILE}" TO_NATIVE_PATH_LIST SOURCE_FILE)
cmake_path(CONVERT "${INCLUDE_DIRECTORIES}" TO_NATIVE_PATH_LIST
INCLUDE_DIRECTORIES)

set(ISYSTEM_FLAGS ${INCLUDE_DIRECTORIES})
list(TRANSFORM ISYSTEM_FLAGS PREPEND "-isystem ")

set(DEFINITION_FLAGS ${COMPILE_DEFINITIONS})
list(TRANSFORM DEFINITION_FLAGS PREPEND "-D ")

set(COMPILER_OPTION_FLAGS ${COMPILE_OPTIONS})

if(cxx_std_17 IN_LIST COMPILE_FEATURES)
list(APPEND COMPILE_OPTION_FLAGS -std=c++17)
endif()

execute_process(
COMMAND ${IWYU_COMMAND} ${SOURCE_FILE} ${ISYSTEM_FLAGS} ${DEFINITION_FLAGS}
${COMPILE_OPTION_FLAGS} -Xiwyu --error=1 -Xiwyu --no_fwd_decls
RESULT_VARIABLE COMMAND_RESULT
OUTPUT_VARIABLE COMMAND_OUTPUT
ERROR_VARIABLE COMMAND_OUTPUT)

if(NOT COMMAND_RESULT EQUAL 0)
message(${COMMAND_OUTPUT})
message(
FATAL_ERROR
"Include What You Use failed on ${SOURCE_FILE} with error code ${COMMAND_RESULT}"
)
endif()
12 changes: 12 additions & 0 deletions cmake/script/touch.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 3.5)

include(ParseCliArguments)

parse_cli_arguments(POSITIONALS FILE)

cmake_path(REMOVE_FILENAME FILE OUTPUT_VARIABLE DIR)
cmake_path(NATIVE_PATH FILE FILE)
cmake_path(NATIVE_PATH DIR DIR)

file(MAKE_DIRECTORY ${DIR})
file(TOUCH ${FILE})
7 changes: 4 additions & 3 deletions include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ file(
GLOB_RECURSE HEADERS
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
CONFIGURE_DEPENDS *.hpp)
list(REMOVE_ITEM HEADERS upd/python.hpp)

add_library(${PROJECT_NAME} INTERFACE)
target_include_directories(
${PROJECT_NAME} INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIRS}>)
target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_17)

if(${PROJECT_NAME}_PLATFORM_ENDIANESS)
target_compile_definitions(
Expand All @@ -23,7 +25,6 @@ endif()
if(${PROJECT_NAME}_IS_TOP_LEVEL)
include(Unpadded)

foreach(HEADER IN LISTS HEADERS)
add_iwyu_target(${HEADER} ${PROJECT_NAME})
endforeach()
add_iwyu_target(iwyu ${PROJECT_NAME})
target_lintables(iwyu ${HEADERS})
endif()
6 changes: 3 additions & 3 deletions include/upd/basic_obytestream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
#include <array>
#include <cstddef>

#include "upd/detail/always_false.hpp"
#include "upd/detail/is_bounded_array.hpp" // IWYU pragma: keep
#include "upd/upd.hpp"
#include "detail/always_false.hpp"
#include "detail/is_bounded_array.hpp" // IWYU pragma: keep
#include "upd.hpp"

namespace upd {

Expand Down
1 change: 0 additions & 1 deletion include/upd/description.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
#include "detail/variadic/map.hpp"
#include "detail/variadic/product.hpp"
#include "upd.hpp"
#include "upd/description.hpp"

namespace upd {

Expand Down
2 changes: 1 addition & 1 deletion include/upd/detail/variadic/leaf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#include "../../index_type.hpp"
#include "../../literals.hpp"
#include "upd/detail/range.hpp"
#include "../range.hpp"

namespace upd::detail::variadic {

Expand Down
Loading

0 comments on commit 17cdc76

Please sign in to comment.