From 8de696bdd04b5d5b9e0bc6fbfe2d1e03371817e2 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Thu, 21 Jan 2021 21:41:48 +0100 Subject: [PATCH] HeaderCheck: It checks some of your headers --- CMakeLists.txt | 3 ++ Meta/CMake/common_options.cmake | 1 + Meta/HeaderCheck/.gitignore | 1 + Meta/HeaderCheck/CMakeLists.txt | 4 ++ Meta/HeaderCheck/generate_all.py | 70 ++++++++++++++++++++++++++++++++ 5 files changed, 79 insertions(+) create mode 100644 Meta/HeaderCheck/.gitignore create mode 100644 Meta/HeaderCheck/CMakeLists.txt create mode 100755 Meta/HeaderCheck/generate_all.py diff --git a/CMakeLists.txt b/CMakeLists.txt index f061bf10ee..653c1fd757 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -250,6 +250,9 @@ if(NOT "${SERENITY_ARCH}" STREQUAL "aarch64") add_subdirectory(Userland) add_subdirectory(Tests) endif() +if (ENABLE_COMPILETIME_HEADER_CHECK) + add_subdirectory(Meta/HeaderCheck) +endif() export_components("${CMAKE_BINARY_DIR}/components.ini") diff --git a/Meta/CMake/common_options.cmake b/Meta/CMake/common_options.cmake index d46fe3f734..15f2d2a4b6 100644 --- a/Meta/CMake/common_options.cmake +++ b/Meta/CMake/common_options.cmake @@ -7,6 +7,7 @@ serenity_option(ENABLE_UNDEFINED_SANITIZER OFF CACHE BOOL "Enable undefined beha serenity_option(ENABLE_ALL_THE_DEBUG_MACROS OFF CACHE BOOL "Enable all debug macros to validate they still compile") serenity_option(ENABLE_ALL_DEBUG_FACILITIES OFF CACHE BOOL "Enable all noisy debug symbols and options. Not recommended for normal developer use") +serenity_option(ENABLE_COMPILETIME_HEADER_CHECK OFF CACHE BOOL "Enable compiletime check that each library header compiles stand-alone") serenity_option(ENABLE_UNICODE_DATABASE_DOWNLOAD ON CACHE BOOL "Enable download of Unicode UCD and CLDR files at build time") serenity_option(INCLUDE_WASM_SPEC_TESTS OFF CACHE BOOL "Download and include the WebAssembly spec testsuite") diff --git a/Meta/HeaderCheck/.gitignore b/Meta/HeaderCheck/.gitignore new file mode 100644 index 0000000000..a0f00082c8 --- /dev/null +++ b/Meta/HeaderCheck/.gitignore @@ -0,0 +1 @@ +CMakeLists.txt diff --git a/Meta/HeaderCheck/CMakeLists.txt b/Meta/HeaderCheck/CMakeLists.txt new file mode 100644 index 0000000000..86e0d165e1 --- /dev/null +++ b/Meta/HeaderCheck/CMakeLists.txt @@ -0,0 +1,4 @@ +execute_process(COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/generate_all.py" "${SERENITY_ARCH}" OUTPUT_VARIABLE SOURCES_STRING) +string(REPLACE "\n" ";" SOURCES_LIST ${SOURCES_STRING}) + +add_library(HeaderCheck OBJECT ${SOURCES_LIST}) diff --git a/Meta/HeaderCheck/generate_all.py b/Meta/HeaderCheck/generate_all.py new file mode 100755 index 0000000000..df7c9e9142 --- /dev/null +++ b/Meta/HeaderCheck/generate_all.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 + +import os +import sys +import subprocess + +TEST_FILE_TEMPLATE = '''\ +#include <{filename}> +// Check idempotency: +#include <{filename}> +''' + + +def get_headers_here(): + result = subprocess.run(['git', 'ls-files', 'Userland/Libraries/*.h'], check=True, capture_output=True, text=True) + assert result.stderr == '' + output = result.stdout.split('\n') + assert output[-1] == '' # Trailing newline + assert len(output) > 500, 'There should be well over a thousand headers, not only {}?!'.format(len(output)) + return output[:-1] + + +def as_filename(header_path): + return header_path.replace('/', '__') + '__test.cpp' + + +def verbosely_write(path, new_content): + print(path) + # FIXME: Ensure directory exists + if os.path.exists(path): + with open(path, 'r') as fp: + old_data = fp.read() + if old_data == new_content: + # Fast path! Don't trigger ninja + return + with open(path, 'w') as fp: + fp.write(new_content) + + +def generate_part(header): + content = TEST_FILE_TEMPLATE.format(filename=header) + if header.startswith('Kernel/'): + content += '#define KERNEL\n' + verbosely_write(as_filename(header), content) + + +def run(root_path, arch): + os.chdir(root_path) + headers_list = get_headers_here() + + generated_files_path = os.path.join(root_path, 'Build', arch, 'Meta', 'HeaderCheck') + if not os.path.exists(generated_files_path): + os.mkdir(generated_files_path) + os.chdir(generated_files_path) + for header in headers_list: + generate_part(header) + + +if __name__ == '__main__': + if 'SERENITY_SOURCE_DIR' not in os.environ: + print('Must set SERENITY_SOURCE_DIR first!', file=sys.stderr) + exit(1) + if len(sys.argv) == 2: + with open('/tmp/the_arg', 'w') as fp: + fp.write(sys.argv[1]) + run(os.environ['SERENITY_SOURCE_DIR'], sys.argv[1]) + else: + print('Usage: SERENITY_SOURCE_DIR=/path/to/serenity {} SERENITY_BUILD_ARCH' + .format(sys.argv[0]), file=sys.stderr) + exit(1)