aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-10-20 01:30:20 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-10-20 01:30:20 +0800
commit3e8d7d1270ace353cce0080a80021dcc7532f2be (patch)
treea8bf9d52c4ce609c5d4d1e9c7249d148e4cb6271
parenta59ba0e3eda5381623a4929486b2f673a883d8d8 (diff)
downloadrcc-project-3e8d7d1270ace353cce0080a80021dcc7532f2be.tar.gz
rcc-project-3e8d7d1270ace353cce0080a80021dcc7532f2be.tar.xz
[CMake] Establish RCC and CFront builds and package exports
-rw-r--r--CMakeLists.txt32
-rw-r--r--cfront/CMakeLists.txt53
-rw-r--r--cfront/cmake/modules/CFrontConfig.cmake.in8
-rw-r--r--cfront/cmake/modules/CFrontConfigVersion.cmake.in13
-rw-r--r--cfront/cmake/modules/CMakeLists.txt35
-rw-r--r--cfront/include/cfront/.gitkeep0
-rw-r--r--cfront/lib/CMakeLists.txt3
-rw-r--r--cfront/tools/CMakeLists.txt3
-rw-r--r--cmake/modules/RCCPreventInSourceBuild.cmake8
-rw-r--r--cmake/modules/RCCProjects.cmake40
-rw-r--r--rcc/CMakeLists.txt55
-rw-r--r--rcc/cmake/modules/CMakeLists.txt47
-rw-r--r--rcc/cmake/modules/RCCConfig.cmake.in27
-rw-r--r--rcc/cmake/modules/RCCConfigVersion.cmake.in13
-rw-r--r--rcc/include/rcc/.gitkeep0
-rw-r--r--rcc/lib/CMakeLists.txt3
-rw-r--r--rcc/tools/CMakeLists.txt3
-rw-r--r--rcc/utils/CMakeLists.txt43
-rw-r--r--rcc/utils/dev.py277
19 files changed, 585 insertions, 78 deletions
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
+ "$<BUILD_INTERFACE:${CFRONT_SOURCE_DIR}/include>"
+ "$<BUILD_INTERFACE:${CFRONT_BINARY_DIR}/include>"
+ "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
+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
--- /dev/null
+++ b/cfront/include/cfront/.gitkeep
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
+ "$<BUILD_INTERFACE:${RCC_SOURCE_DIR}/include>"
+ "$<BUILD_INTERFACE:${RCC_BINARY_DIR}/include>"
+ "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
+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$<$<CONFIG:Debug>: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
--- /dev/null
+++ b/rcc/include/rcc/.gitkeep
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())