From 3e8d7d1270ace353cce0080a80021dcc7532f2be Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Tue, 20 Oct 2026 01:30:20 +0800 Subject: [CMake] Establish RCC and CFront builds and package exports --- CMakeLists.txt | 32 +-- cfront/CMakeLists.txt | 53 +++++ cfront/cmake/modules/CFrontConfig.cmake.in | 8 + cfront/cmake/modules/CFrontConfigVersion.cmake.in | 13 + cfront/cmake/modules/CMakeLists.txt | 35 +++ cfront/include/cfront/.gitkeep | 0 cfront/lib/CMakeLists.txt | 3 + cfront/tools/CMakeLists.txt | 3 + cmake/modules/RCCPreventInSourceBuild.cmake | 8 - cmake/modules/RCCProjects.cmake | 40 ---- rcc/CMakeLists.txt | 55 +++++ rcc/cmake/modules/CMakeLists.txt | 47 ++++ rcc/cmake/modules/RCCConfig.cmake.in | 27 +++ rcc/cmake/modules/RCCConfigVersion.cmake.in | 13 + rcc/include/rcc/.gitkeep | 0 rcc/lib/CMakeLists.txt | 3 + rcc/tools/CMakeLists.txt | 3 + rcc/utils/CMakeLists.txt | 43 ++++ rcc/utils/dev.py | 277 ++++++++++++++++++++++ 19 files changed, 585 insertions(+), 78 deletions(-) create mode 100644 cfront/CMakeLists.txt create mode 100644 cfront/cmake/modules/CFrontConfig.cmake.in create mode 100644 cfront/cmake/modules/CFrontConfigVersion.cmake.in create mode 100644 cfront/cmake/modules/CMakeLists.txt create mode 100644 cfront/include/cfront/.gitkeep create mode 100644 cfront/lib/CMakeLists.txt create mode 100644 cfront/tools/CMakeLists.txt delete mode 100644 cmake/modules/RCCPreventInSourceBuild.cmake delete mode 100644 cmake/modules/RCCProjects.cmake create mode 100644 rcc/CMakeLists.txt create mode 100644 rcc/cmake/modules/CMakeLists.txt create mode 100644 rcc/cmake/modules/RCCConfig.cmake.in create mode 100644 rcc/cmake/modules/RCCConfigVersion.cmake.in create mode 100644 rcc/include/rcc/.gitkeep create mode 100644 rcc/lib/CMakeLists.txt create mode 100644 rcc/tools/CMakeLists.txt create mode 100644 rcc/utils/CMakeLists.txt create mode 100644 rcc/utils/dev.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 597963a..4477777 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,34 +1,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later cmake_minimum_required(VERSION 3.25) +project(RCCWorkspace LANGUAGES NONE) -project( - RCC - VERSION 0.1.0 - DESCRIPTION "RCC compiler collection" - LANGUAGES CXX -) - -list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules") - -include(RCCPreventInSourceBuild) -include(RCCProjects) -include(CTest) - -rcc_add_enabled_projects("${CMAKE_CURRENT_SOURCE_DIR}") - -if(TARGET rcc-backend-format) - add_custom_target(format DEPENDS rcc-backend-format) -endif() - -if(TARGET rcc-backend-format-check) - add_custom_target(format-check DEPENDS rcc-backend-format-check) -endif() - -if(TARGET rcc-backend-tidy) - add_custom_target(tidy DEPENDS rcc-backend-tidy) -endif() - -if(TARGET rcc-backend-analyze) - add_custom_target(analyze DEPENDS rcc-backend-analyze) -endif() +add_subdirectory(rcc) diff --git a/cfront/CMakeLists.txt b/cfront/CMakeLists.txt new file mode 100644 index 0000000..f0c4d0a --- /dev/null +++ b/cfront/CMakeLists.txt @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +cmake_minimum_required(VERSION 3.25) +include("${CMAKE_CURRENT_LIST_DIR}/../cmake/Modules/CMakePolicy.cmake" NO_POLICY_SCOPE) +include("${CMAKE_CURRENT_LIST_DIR}/../cmake/Modules/RCCVersion.cmake") + +project(CFront VERSION "${RCC_VERSION}" LANGUAGES C CXX) + +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) + message(FATAL_ERROR "CFront requires an out-of-source build.") +endif() + +if(NOT TARGET RCCHeaders) + set(CFRONT_STANDALONE_BUILD ON) + find_package(RCC ${RCC_VERSION} EXACT CONFIG REQUIRED HINTS "${RCC_CMAKE_DIR}") +else() + set(CFRONT_STANDALONE_BUILD OFF) +endif() +list(PREPEND CMAKE_MODULE_PATH + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" "${RCC_CMAKE_DIR}") +include(GNUInstallDirs) +include(HandleRCCOptions) +include(AddCFront) +if(CFRONT_STANDALONE_BUILD) + add_compile_definitions(${RCC_DEFINITIONS}) + foreach(cfrontDefinition IN LISTS RCC_DEFINITIONS) + list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D${cfrontDefinition}") + endforeach() + rcc_configure_options() + rcc_configure_output_directories() +endif() +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "Export the compilation database") + +set(CFRONT_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +set(CFRONT_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}") +file(MAKE_DIRECTORY "${CFRONT_BINARY_DIR}/include/cfront") +add_library(CFrontHeaders INTERFACE) +target_compile_features(CFrontHeaders INTERFACE cxx_std_23) +target_include_directories(CFrontHeaders INTERFACE + "$" + "$" + "$") +target_link_libraries(CFrontHeaders INTERFACE RCCHeaders) +install(TARGETS CFrontHeaders EXPORT CFrontTargets) +install(DIRECTORY include/cfront DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + FILES_MATCHING PATTERN "*.h" PATTERN "*.inc" PATTERN "*.def") +install(DIRECTORY "${CFRONT_BINARY_DIR}/include/cfront" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + FILES_MATCHING PATTERN "*.h" PATTERN "*.inc" PATTERN "*.def" + PATTERN "CMakeFiles" EXCLUDE PATTERN "config.h" EXCLUDE) + +add_subdirectory(lib) +add_subdirectory(tools) +add_subdirectory(cmake/modules) diff --git a/cfront/cmake/modules/CFrontConfig.cmake.in b/cfront/cmake/modules/CFrontConfig.cmake.in new file mode 100644 index 0000000..3b1a800 --- /dev/null +++ b/cfront/cmake/modules/CFrontConfig.cmake.in @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(RCC @RCC_VERSION@ EXACT CONFIG HINTS "@CFRONT_PACKAGE_RCC_CMAKE_DIR@") +set(CFRONT_CMAKE_DIR "@CFRONT_PACKAGE_MODULE_DIR@") +include("${CMAKE_CURRENT_LIST_DIR}/CFrontTargets.cmake") diff --git a/cfront/cmake/modules/CFrontConfigVersion.cmake.in b/cfront/cmake/modules/CFrontConfigVersion.cmake.in new file mode 100644 index 0000000..f7d5ea4 --- /dev/null +++ b/cfront/cmake/modules/CFrontConfigVersion.cmake.in @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +set(PACKAGE_VERSION "@PROJECT_VERSION@") + +# Match major and minor versions; accept the requested patch version or newer. +if("@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@" VERSION_EQUAL + "${PACKAGE_FIND_VERSION_MAJOR}.${PACKAGE_FIND_VERSION_MINOR}" + AND NOT "@PROJECT_VERSION_PATCH@" VERSION_LESS "${PACKAGE_FIND_VERSION_PATCH}") + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if("@PROJECT_VERSION_PATCH@" VERSION_EQUAL "${PACKAGE_FIND_VERSION_PATCH}") + set(PACKAGE_VERSION_EXACT TRUE) + endif() +endif() diff --git a/cfront/cmake/modules/CMakeLists.txt b/cfront/cmake/modules/CMakeLists.txt new file mode 100644 index 0000000..634ba34 --- /dev/null +++ b/cfront/cmake/modules/CMakeLists.txt @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +include(CMakePackageConfigHelpers) + +set(cfrontPackageDir "${CFRONT_BINARY_DIR}/lib/cmake/CFront") +set(cfrontInstallPackageDir "${CMAKE_INSTALL_LIBDIR}/cmake/CFront") +set(CFRONT_PACKAGE_MODULE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +if(CFRONT_STANDALONE_BUILD) + set(CFRONT_PACKAGE_RCC_CMAKE_DIR "${RCC_DIR}") +else() + set(CFRONT_PACKAGE_RCC_CMAKE_DIR "${RCC_BINARY_DIR}/lib/cmake/RCC") +endif() +configure_package_config_file(CFrontConfig.cmake.in + "${cfrontPackageDir}/CFrontConfig.cmake" + INSTALL_DESTINATION "${cfrontPackageDir}" + NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO) +export(EXPORT CFrontTargets FILE "${cfrontPackageDir}/CFrontTargets.cmake") + +set(CFRONT_PACKAGE_MODULE_DIR "\${CMAKE_CURRENT_LIST_DIR}") +if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") + set(CFRONT_PACKAGE_RCC_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/RCC") +else() + set(CFRONT_PACKAGE_RCC_CMAKE_DIR "\${PACKAGE_PREFIX_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/RCC") +endif() +configure_package_config_file(CFrontConfig.cmake.in CFrontConfig.install.cmake + INSTALL_DESTINATION "${cfrontInstallPackageDir}" + NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO) +configure_file(CFrontConfigVersion.cmake.in + "${cfrontPackageDir}/CFrontConfigVersion.cmake" @ONLY) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/CFrontConfig.install.cmake" + DESTINATION "${cfrontInstallPackageDir}" RENAME CFrontConfig.cmake) +install(FILES "${cfrontPackageDir}/CFrontConfigVersion.cmake" AddCFront.cmake + DESTINATION "${cfrontInstallPackageDir}") +install(EXPORT CFrontTargets FILE CFrontTargets.cmake + DESTINATION "${cfrontInstallPackageDir}") diff --git a/cfront/include/cfront/.gitkeep b/cfront/include/cfront/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/cfront/lib/CMakeLists.txt b/cfront/lib/CMakeLists.txt new file mode 100644 index 0000000..2efe23c --- /dev/null +++ b/cfront/lib/CMakeLists.txt @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# Add frontend libraries here as their implementations are introduced. diff --git a/cfront/tools/CMakeLists.txt b/cfront/tools/CMakeLists.txt new file mode 100644 index 0000000..dad636b --- /dev/null +++ b/cfront/tools/CMakeLists.txt @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# Add frontend tools here as their implementations are introduced. diff --git a/cmake/modules/RCCPreventInSourceBuild.cmake b/cmake/modules/RCCPreventInSourceBuild.cmake deleted file mode 100644 index 97b3392..0000000 --- a/cmake/modules/RCCPreventInSourceBuild.cmake +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later - -if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) - message( - FATAL_ERROR - "RCC does not support in-source builds. Configure a separate build directory." - ) -endif() diff --git a/cmake/modules/RCCProjects.cmake b/cmake/modules/RCCProjects.cmake deleted file mode 100644 index 59c4fc5..0000000 --- a/cmake/modules/RCCProjects.cmake +++ /dev/null @@ -1,40 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later - -set(RCC_KNOWN_PROJECTS backend) -set(RCC_DEFAULT_PROJECTS backend) - -set( - RCC_ENABLE_PROJECTS - "${RCC_DEFAULT_PROJECTS}" - CACHE STRING - "Semicolon-separated RCC projects to build, or 'all'" -) - -function(rcc_add_enabled_projects RCCSourceRoot) - if(RCC_ENABLE_PROJECTS STREQUAL "all") - set(EnabledProjects ${RCC_KNOWN_PROJECTS}) - else() - set(EnabledProjects ${RCC_ENABLE_PROJECTS}) - endif() - - if(NOT EnabledProjects) - message(FATAL_ERROR "RCC_ENABLE_PROJECTS must name at least one project") - endif() - - list(REMOVE_DUPLICATES EnabledProjects) - - foreach(Project IN LISTS EnabledProjects) - if(NOT Project IN_LIST RCC_KNOWN_PROJECTS) - message( - FATAL_ERROR - "Unknown RCC project '${Project}'. Known projects: ${RCC_KNOWN_PROJECTS}" - ) - endif() - endforeach() - - # Component order is controlled here rather than by the user-provided list. - # Future inter-project dependency closure will also be resolved here. - if("backend" IN_LIST EnabledProjects) - add_subdirectory("${RCCSourceRoot}/backend" "${CMAKE_BINARY_DIR}/backend") - endif() -endfunction() diff --git a/rcc/CMakeLists.txt b/rcc/CMakeLists.txt new file mode 100644 index 0000000..c9087ad --- /dev/null +++ b/rcc/CMakeLists.txt @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +cmake_minimum_required(VERSION 3.25) +include("${CMAKE_CURRENT_LIST_DIR}/../cmake/Modules/CMakePolicy.cmake" NO_POLICY_SCOPE) +include("${CMAKE_CURRENT_LIST_DIR}/../cmake/Modules/RCCVersion.cmake") + +project(RCC VERSION "${RCC_VERSION}" LANGUAGES C CXX) + +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) + message(FATAL_ERROR "RCC requires an out-of-source build.") +endif() + +set(RCC_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +set(RCC_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}") +set(RCC_CMAKE_DIR "${RCC_SOURCE_DIR}/cmake/modules") +set(RCC_INCLUDE_DIRS "${RCC_SOURCE_DIR}/include;${RCC_BINARY_DIR}/include") +set(RCC_ENABLE_PROJECTS "" CACHE STRING "Additional projects to build: cfront") +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "Export the compilation database") +file(MAKE_DIRECTORY "${RCC_BINARY_DIR}/include/rcc") + +list(PREPEND CMAKE_MODULE_PATH "${RCC_CMAKE_DIR}") +include(GNUInstallDirs) +include(HandleRCCOptions) +include(AddRCC) +rcc_configure_options() +rcc_configure_output_directories() +set(RCC_LIBRARY_DIR "${RCC_LIBRARY_OUTPUT_INTDIR}") + +add_library(RCCHeaders INTERFACE) +target_compile_features(RCCHeaders INTERFACE cxx_std_23) +target_include_directories(RCCHeaders INTERFACE + "$" + "$" + "$") +install(TARGETS RCCHeaders EXPORT RCCTargets) +install(DIRECTORY include/rcc DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + FILES_MATCHING PATTERN "*.h" PATTERN "*.inc" PATTERN "*.def") +install(DIRECTORY "${RCC_BINARY_DIR}/include/rcc" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + FILES_MATCHING PATTERN "*.h" PATTERN "*.inc" PATTERN "*.def" + PATTERN "CMakeFiles" EXCLUDE PATTERN "config.h" EXCLUDE) + +add_subdirectory(lib) +add_subdirectory(tools) +add_subdirectory(utils) + +foreach(rccProject IN LISTS RCC_ENABLE_PROJECTS) + if(NOT rccProject STREQUAL "cfront") + message(FATAL_ERROR "Unknown RCC project: ${rccProject}. Available: cfront") + endif() +endforeach() +if("cfront" IN_LIST RCC_ENABLE_PROJECTS) + add_subdirectory("${RCC_SOURCE_DIR}/../cfront" "${CMAKE_BINARY_DIR}/cfront") +endif() + +add_subdirectory(cmake/modules) diff --git a/rcc/cmake/modules/CMakeLists.txt b/rcc/cmake/modules/CMakeLists.txt new file mode 100644 index 0000000..39b6d97 --- /dev/null +++ b/rcc/cmake/modules/CMakeLists.txt @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +include(CMakePackageConfigHelpers) + +get_directory_property(RCC_DEFINITIONS DIRECTORY "${RCC_SOURCE_DIR}" COMPILE_DEFINITIONS) +set(RCC_PACKAGE_MSVC_RUNTIME_LIBRARY "${CMAKE_MSVC_RUNTIME_LIBRARY}") +if(NOT DEFINED CMAKE_MSVC_RUNTIME_LIBRARY) + set(RCC_PACKAGE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") +endif() +set(rccPackageDir "${RCC_BINARY_DIR}/lib/cmake/RCC") +set(rccInstallPackageDir "${CMAKE_INSTALL_LIBDIR}/cmake/RCC") +set(RCC_PACKAGE_MODULE_DIR "${RCC_CMAKE_DIR}") +set(RCC_PACKAGE_INCLUDE_DIRS "${RCC_INCLUDE_DIRS}") +set(RCC_PACKAGE_LIBRARY_DIR "${RCC_LIBRARY_DIR}") +set(RCC_PACKAGE_BUILD_LIBRARY_DIR "${RCC_LIBRARY_DIR}") +set(RCC_PACKAGE_INSTALL_PREFIX "") +configure_package_config_file(RCCConfig.cmake.in "${rccPackageDir}/RCCConfig.cmake" + INSTALL_DESTINATION "${rccPackageDir}" + NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO) +export(EXPORT RCCTargets FILE "${rccPackageDir}/RCCTargets.cmake") + +set(RCC_PACKAGE_MODULE_DIR "\${CMAKE_CURRENT_LIST_DIR}") +if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") + set(RCC_PACKAGE_INCLUDE_DIRS "${CMAKE_INSTALL_INCLUDEDIR}") +else() + set(RCC_PACKAGE_INCLUDE_DIRS "\${PACKAGE_PREFIX_DIR}/${CMAKE_INSTALL_INCLUDEDIR}") +endif() +if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") + set(RCC_PACKAGE_LIBRARY_DIR "${CMAKE_INSTALL_LIBDIR}") +else() + set(RCC_PACKAGE_LIBRARY_DIR "\${PACKAGE_PREFIX_DIR}/${CMAKE_INSTALL_LIBDIR}") +endif() +set(RCC_PACKAGE_BUILD_LIBRARY_DIR "") +set(RCC_PACKAGE_INSTALL_PREFIX "\${PACKAGE_PREFIX_DIR}") +configure_package_config_file(RCCConfig.cmake.in RCCConfig.install.cmake + INSTALL_DESTINATION "${rccInstallPackageDir}" + NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO) +configure_file(RCCConfigVersion.cmake.in + "${rccPackageDir}/RCCConfigVersion.cmake" @ONLY) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/RCCConfig.install.cmake" + DESTINATION "${rccInstallPackageDir}" RENAME RCCConfig.cmake) +install(FILES + "${rccPackageDir}/RCCConfigVersion.cmake" + AddRCC.cmake HandleRCCOptions.cmake + DESTINATION "${rccInstallPackageDir}") +install(EXPORT RCCTargets FILE RCCTargets.cmake + DESTINATION "${rccInstallPackageDir}") diff --git a/rcc/cmake/modules/RCCConfig.cmake.in b/rcc/cmake/modules/RCCConfig.cmake.in new file mode 100644 index 0000000..3454a58 --- /dev/null +++ b/rcc/cmake/modules/RCCConfig.cmake.in @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +@PACKAGE_INIT@ + +set(RCC_VERSION "@RCC_VERSION@") +set(RCC_VERSION_MAJOR "@RCC_VERSION_MAJOR@") +set(RCC_VERSION_MINOR "@RCC_VERSION_MINOR@") +set(RCC_VERSION_PATCH "@RCC_VERSION_PATCH@") +set(RCC_CMAKE_DIR "@RCC_PACKAGE_MODULE_DIR@") +set(RCC_INCLUDE_DIRS "@RCC_PACKAGE_INCLUDE_DIRS@") +set(RCC_LIBRARY_DIR "@RCC_PACKAGE_LIBRARY_DIR@") +set(RCC_BUILD_LIBRARY_DIR "@RCC_PACKAGE_BUILD_LIBRARY_DIR@") +set(RCC_INSTALL_PREFIX "@RCC_PACKAGE_INSTALL_PREFIX@") +set(RCC_ENABLE_ASSERTIONS "@RCC_ENABLE_ASSERTIONS@") +set(RCC_ENABLE_EH "@RCC_ENABLE_EH@") +set(RCC_ENABLE_RTTI "@RCC_ENABLE_RTTI@") +set(RCC_ENABLE_UNWIND_TABLES "@RCC_ENABLE_UNWIND_TABLES@") +set(RCC_ENABLE_WARNINGS "@RCC_ENABLE_WARNINGS@") +set(RCC_ENABLE_WARNING_SUPPRESSIONS "@RCC_ENABLE_WARNING_SUPPRESSIONS@") +set(RCC_ENABLE_LIBCXX "@RCC_LIBCXX_USED@") +set(RCC_STATIC_LINK_CXX_STDLIB "@RCC_STATIC_LINK_CXX_STDLIB@") +set(GLIBCXX_USE_CXX11_ABI "@GLIBCXX_USE_CXX11_ABI@") +set(RCC_DEFINITIONS "@RCC_DEFINITIONS@") +if(MSVC) + set(CMAKE_MSVC_RUNTIME_LIBRARY "@RCC_PACKAGE_MSVC_RUNTIME_LIBRARY@") +endif() +include("${CMAKE_CURRENT_LIST_DIR}/RCCTargets.cmake") diff --git a/rcc/cmake/modules/RCCConfigVersion.cmake.in b/rcc/cmake/modules/RCCConfigVersion.cmake.in new file mode 100644 index 0000000..f7d5ea4 --- /dev/null +++ b/rcc/cmake/modules/RCCConfigVersion.cmake.in @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +set(PACKAGE_VERSION "@PROJECT_VERSION@") + +# Match major and minor versions; accept the requested patch version or newer. +if("@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@" VERSION_EQUAL + "${PACKAGE_FIND_VERSION_MAJOR}.${PACKAGE_FIND_VERSION_MINOR}" + AND NOT "@PROJECT_VERSION_PATCH@" VERSION_LESS "${PACKAGE_FIND_VERSION_PATCH}") + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if("@PROJECT_VERSION_PATCH@" VERSION_EQUAL "${PACKAGE_FIND_VERSION_PATCH}") + set(PACKAGE_VERSION_EXACT TRUE) + endif() +endif() diff --git a/rcc/include/rcc/.gitkeep b/rcc/include/rcc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/rcc/lib/CMakeLists.txt b/rcc/lib/CMakeLists.txt new file mode 100644 index 0000000..f515bc8 --- /dev/null +++ b/rcc/lib/CMakeLists.txt @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# Add library subdirectories here as their implementations are introduced. diff --git a/rcc/tools/CMakeLists.txt b/rcc/tools/CMakeLists.txt new file mode 100644 index 0000000..5702399 --- /dev/null +++ b/rcc/tools/CMakeLists.txt @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# Add tool subdirectories here as their implementations are introduced. diff --git a/rcc/utils/CMakeLists.txt b/rcc/utils/CMakeLists.txt new file mode 100644 index 0000000..bb13e46 --- /dev/null +++ b/rcc/utils/CMakeLists.txt @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# Resolve developer tools only when the corresponding target is invoked. +set(RCC_PYTHON_EXECUTABLE python3 CACHE STRING "Python interpreter for developer tools") +set(RCC_CLANG_FORMAT_EXECUTABLE clang-format CACHE STRING "clang-format executable") +set(RCC_CLANG_TIDY_EXECUTABLE clang-tidy CACHE STRING "clang-tidy executable") +set(RCC_ANALYZER_EXECUTABLE clang++ CACHE STRING "Clang static analyzer executable") +set(RCC_CLANG_FORMAT_VERSION "" CACHE STRING "Required clang-format version, if specified") +set(RCC_CLANG_TIDY_VERSION "" CACHE STRING "Required clang-tidy version, if specified") +set(RCC_ANALYZER_VERSION "" CACHE STRING "Required Clang analyzer version, if specified") +set(RCC_DEV_BASE HEAD CACHE STRING "Git revision used for changed-file checks") + +set(rccDevScript "${CMAKE_CURRENT_SOURCE_DIR}/dev.py") +add_custom_target(format-check + COMMAND "${RCC_PYTHON_EXECUTABLE}" "${rccDevScript}" format + --build-dir "${CMAKE_BINARY_DIR}" + --base "${RCC_DEV_BASE}" + --tool "${RCC_CLANG_FORMAT_EXECUTABLE}" + --tool-version "${RCC_CLANG_FORMAT_VERSION}" + WORKING_DIRECTORY "${RCC_SOURCE_DIR}/.." + USES_TERMINAL VERBATIM) +add_custom_target(format + COMMAND "${RCC_PYTHON_EXECUTABLE}" "${rccDevScript}" format --fix + --build-dir "${CMAKE_BINARY_DIR}" + --base "${RCC_DEV_BASE}" + --tool "${RCC_CLANG_FORMAT_EXECUTABLE}" + --tool-version "${RCC_CLANG_FORMAT_VERSION}" + WORKING_DIRECTORY "${RCC_SOURCE_DIR}/.." + USES_TERMINAL VERBATIM) +add_custom_target(tidy + COMMAND "${RCC_PYTHON_EXECUTABLE}" "${rccDevScript}" tidy + --build-dir "${CMAKE_BINARY_DIR}" --base "${RCC_DEV_BASE}" + --tool "${RCC_CLANG_TIDY_EXECUTABLE}" + --tool-version "${RCC_CLANG_TIDY_VERSION}" + WORKING_DIRECTORY "${RCC_SOURCE_DIR}/.." + USES_TERMINAL VERBATIM) +add_custom_target(analyze + COMMAND "${RCC_PYTHON_EXECUTABLE}" "${rccDevScript}" analyze + --build-dir "${CMAKE_BINARY_DIR}" --base "${RCC_DEV_BASE}" + --tool "${RCC_ANALYZER_EXECUTABLE}" + --tool-version "${RCC_ANALYZER_VERSION}" + WORKING_DIRECTORY "${RCC_SOURCE_DIR}/.." + USES_TERMINAL VERBATIM) diff --git a/rcc/utils/dev.py b/rcc/utils/dev.py new file mode 100644 index 0000000..b14276a --- /dev/null +++ b/rcc/utils/dev.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-3.0-or-later + +"""Run formatting, clang-tidy, or the Clang analyzer for RCC and CFront.""" + +import argparse +import difflib +import hashlib +import json +from pathlib import Path +import re +import shlex +import subprocess +import sys + + +ROOT = Path(__file__).resolve().parents[2] +PROJECTS = ("rcc", "cfront") +SOURCE_SUFFIXES = {".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx", ".inc"} +HEADER_SUFFIXES = {".h", ".hh", ".hpp", ".hxx", ".inc"} +HUNK = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@", re.MULTILINE) + + +def git_output(*arguments): + result = subprocess.run( + ["git", "-C", str(ROOT), *arguments], + check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + ) + return result.stdout + + +def is_source(path, build_dir): + # The allowlist prevents checks from reaching independent or legacy projects. + resolved = path.resolve() + if not any(resolved.is_relative_to(ROOT / project) for project in PROJECTS): + return False + if build_dir is not None and resolved.is_relative_to(build_dir.resolve()): + return False + relative = resolved.relative_to(ROOT) + for part in relative.parts[:-1]: + if part in {"generated", "CMakeFiles", "rcc-legacy"}: + return False + return path.suffix in SOURCE_SUFFIXES and path.is_file() + + +def git_files(*arguments): + names = git_output(*arguments, "--", *PROJECTS).split(b"\0") + return {ROOT / name.decode() for name in names if name} + + +def selected_files(args): + scopes = [Path(name).resolve() for name in args.paths] + for scope in scopes: + if scope != ROOT and not any(scope.is_relative_to(ROOT / p) for p in PROJECTS): + raise ValueError(f"Selection must be inside rcc/ or cfront/: {scope}") + + whole_files = args.all or (args.paths and not args.changed) + if whole_files: + candidates = git_files("ls-files", "--cached", "--others", "--exclude-standard", "-z") + untracked = set() + else: + candidates = git_files( + "diff", "--no-ext-diff", "--no-renames", "--name-only", "-z", args.base + ) + untracked = git_files("ls-files", "--others", "--exclude-standard", "-z") + candidates |= untracked + + selected = {} + for path in sorted(candidates): + if not is_source(path, args.build_dir): + continue + if scopes and not any(path == scope or path.is_relative_to(scope) for scope in scopes): + continue + if whole_files or path in untracked: + selected[path] = None + continue + patch = git_output( + "diff", "--no-color", "--no-ext-diff", "--no-textconv", "--no-renames", "--unified=0", + args.base, "--", str(path.relative_to(ROOT)), + ).decode() + ranges = [] + for start, count in HUNK.findall(patch): + length = int(count) if count else 1 + if length == 0: + continue + first = int(start) + ranges.append((first, first + length - 1)) + selected[path] = ranges + return selected + + +def check_tool(executable, expected_version, directory=ROOT): + result = subprocess.run( + [executable, "--version"], cwd=directory, + check=True, text=True, stdout=subprocess.PIPE, + ) + print(result.stdout.strip(), flush=True) + if expected_version: + version = re.search(r"\bversion\s+(\d+(?:\.\d+)*)", result.stdout) + if version is None or version.group(1) != expected_version: + raise ValueError(f"Expected {executable} version {expected_version}") + + +def format_sources(args): + files = selected_files(args) + if not files: + print("No matching source files.") + return 0 + check_tool(args.tool, args.tool_version) + different = False + for path, ranges in files.items(): + if ranges == []: + continue + command = [args.tool, "--style=file"] + if ranges is not None: + command.extend(f"--lines={start}:{end}" for start, end in ranges) + command.append(str(path)) + original = path.read_text() + formatted = subprocess.run( + command, check=True, text=True, stdout=subprocess.PIPE, + ).stdout + if original == formatted: + continue + different = True + name = str(path.relative_to(ROOT)) + if args.fix: + path.write_text(formatted) + print(f"Formatted {name}") + else: + sys.stdout.writelines(difflib.unified_diff( + original.splitlines(keepends=True), formatted.splitlines(keepends=True), + fromfile=f"a/{name}", tofile=f"b/{name}", + )) + return int(different and not args.fix) + + +def compilation_entries(args, files): + database_dir = args.build_dir.resolve() + with (database_dir / "compile_commands.json").open() as stream: + database = json.load(stream) + entries = [] + for entry in database: + directory = (database_dir / entry["directory"]).resolve() + source = (directory / entry["file"]).resolve() + if not is_source(source, args.build_dir): + continue + if source in files: + entries.append((source, directory, entry)) + return entries + + +def tidy_sources(args): + files = selected_files(args) + if not files: + print("No matching source files.") + return 0 + if args.all: + sources = {source for source, _, _ in compilation_entries(args, files)} + else: + # Like clang-tidy-diff, pass selected headers directly to clang-tidy. + # The tool infers their compile commands from the compilation database. + sources = set(files) + if not sources: + raise ValueError("No compilation database entries match the selected sources.") + check_tool(args.tool, args.tool_version) + line_filter = [] + if args.line_filter: + for path, ranges in files.items(): + if ranges == []: + continue + item = {"name": str(path)} + if ranges is not None: + item["lines"] = ranges + line_filter.append(item) + if not line_filter: + print("No changed lines to report.") + return 0 + + failed = False + for source in sorted(sources): + command = [args.tool, str(source), "-p", str(args.build_dir.resolve())] + if args.line_filter: + command.append("--line-filter=" + json.dumps(line_filter)) + command.extend(args.extra_arg) + print(shlex.join(command), flush=True) + result = subprocess.run(command, cwd=ROOT) + failed |= result.returncode != 0 + return int(failed) + + +def compilation_command(entry): + arguments = entry.get("arguments") + if arguments is None: + arguments = shlex.split(entry["command"]) + return arguments + + +def analyze_sources(args): + files = selected_files(args) + if not files: + print("No matching source files.") + return 0 + entries = compilation_entries(args, files) + if not entries: + raise ValueError( + "No selected translation units are in the compilation database. " + "Select source files or use --all to analyze all project translation units." + ) + if any(path.suffix in HEADER_SUFFIXES for path in files): + print("Header changes do not expand the selected translation units; use --all for full analysis.") + reports = (args.output_dir or args.build_dir / "analyzer-reports").resolve() + failed = False + check_tool(args.tool, args.tool_version) + for source, directory, entry in entries: + arguments = compilation_command(entry) + driver_index = 1 if Path(arguments[0]).name in {"ccache", "sccache"} else 0 + # Distinguish compilation variants of the same source file. + identity = json.dumps(entry, sort_keys=True).encode() + suffix = hashlib.sha256(identity).hexdigest()[:12] + output = reports / f"{source.stem}-{suffix}" + output.mkdir(parents=True, exist_ok=True) + # Run the selected Clang analyzer with the recorded compilation options. + # Appending -o selects the report directory instead of the object output. + command = [args.tool, *arguments[driver_index + 1:], + "--analyze", "--analyzer-output", "html", "-o", str(output)] + command.extend(args.extra_arg) + print(shlex.join(command), flush=True) + result = subprocess.run(command, cwd=directory) + failed |= result.returncode != 0 + print(f"Analyzer reports: {reports}") + return int(failed) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + commands = parser.add_subparsers(dest="command", required=True) + for name, handler, executable in ( + ("format", format_sources, "clang-format"), + ("tidy", tidy_sources, "clang-tidy"), + ("analyze", analyze_sources, "clang++"), + ): + command = commands.add_parser(name) + command.set_defaults(handler=handler) + command.add_argument("paths", nargs="*", help="Files or module directories; paths select whole files by default") + mode = command.add_mutually_exclusive_group() + mode.add_argument("--all", action="store_true", help="Select all tracked and untracked source files") + mode.add_argument("--changed", action="store_true", help="Select changes since --base, also within explicit paths") + command.add_argument("--base", default="HEAD", help="Diff base for the working tree (default: HEAD)") + command.add_argument("--tool", default=executable, help="Tool executable or absolute path") + command.add_argument("--tool-version", default="", help="Require this exact tool version when provided") + if name == "format": + command.add_argument("--build-dir", type=Path, help="Build directory to exclude from source selection") + command.add_argument("--fix", action="store_true", help="Write formatting changes; otherwise only print a diff") + else: + command.add_argument("--build-dir", type=Path, required=True, help="Directory containing compile_commands.json") + command.add_argument("--extra-arg", action="append", default=[], help="Additional tool option; use --extra-arg=VALUE") + if name == "tidy": + command.add_argument("--line-filter", action="store_true", help="Report diagnostics on selected lines; translation units are still fully analyzed") + if name == "analyze": + command.description = "Analyze selected translation units with Clang using their recorded compilation options. Use --all for full analysis." + command.add_argument("--output-dir", type=Path, help="HTML report directory (default: BUILD/analyzer-reports)") + + args = parser.parse_args() + try: + return args.handler(args) + except subprocess.CalledProcessError as error: + if error.stderr: + sys.stderr.write(error.stderr.decode()) + print(f"Command failed: {shlex.join(error.cmd)}", file=sys.stderr) + return 1 + except (OSError, ValueError) as error: + print(str(error), file=sys.stderr) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) -- cgit v1.2.3