You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过CMake与Clang在预编译后编译前插入脚本处理带[[log]]属性的函数?

Great question! Let's break down how to implement your custom preprocessing workflow with CMake, including both a general approach and a more robust Clang-specific alternative.

Core Workflow Overview

First, let's align on the exact pipeline you want:

  1. Run standard preprocessing on source files
  2. Apply custom script to modify preprocessed output (wrap [[log]]-marked functions with a logger that records params/return values)
  3. Compile & link the modified files as usual

Here's how to make this happen with CMake, plus optimized options using Clang.


Approach 1: General CMake Implementation (Works with Any Compiler)

This method explicitly splits preprocessing, custom modification, and compilation into separate steps in your CMake script.

Step 1: CMake Script Setup

Add this to your CMakeLists.txt to intercept preprocessed files and run your custom script:

cmake_minimum_required(VERSION 3.18)
project(LogWrappedApp)

# Path to your custom processing script (e.g., Python, Bash)
set(CUSTOM_PROCESSOR "${CMAKE_SOURCE_DIR}/wrap_log_functions.py")

# Collect all source files
file(GLOB_RECURSE SOURCES "src/*.cpp")
set(PROCESSED_SOURCES "")

foreach(SOURCE_FILE ${SOURCES})
    # Generate paths for intermediate files
    get_filename_component(BASE_NAME ${SOURCE_FILE} NAME_WE)
    set(PREPROCESSED_FILE "${CMAKE_BINARY_DIR}/preprocessed/${BASE_NAME}.i")
    set(MODIFIED_FILE "${CMAKE_BINARY_DIR}/processed/${BASE_NAME}_modified.i")

    # 1. Run standard preprocessing to generate .i file
    add_custom_command(
        OUTPUT ${PREPROCESSED_FILE}
        COMMAND ${CMAKE_CXX_COMPILER} -E ${CMAKE_CXX_FLAGS} ${SOURCE_FILE} -o ${PREPROCESSED_FILE}
        DEPENDS ${SOURCE_FILE}
        COMMENT "Preprocessing ${BASE_NAME}.cpp..."
    )

    # 2. Run your custom script to modify the preprocessed output
    add_custom_command(
        OUTPUT ${MODIFIED_FILE}
        COMMAND python3 ${CUSTOM_PROCESSOR} ${PREPROCESSED_FILE} ${MODIFIED_FILE}
        DEPENDS ${PREPROCESSED_FILE} ${CUSTOM_PROCESSOR}
        COMMENT "Wrapping log functions in ${BASE_NAME}.i..."
    )

    # Track modified files for compilation
    list(APPEND PROCESSED_SOURCES ${MODIFIED_FILE})
endforeach()

# Compile modified preprocessed files into your executable
add_executable(my_app ${PROCESSED_SOURCES})
# Tell compiler to treat .i files as preprocessed C++ code (skip re-preprocessing)
target_compile_options(my_app PRIVATE -x c++)
# Ensure logger macros are available (add your log header path here)
target_include_directories(my_app PRIVATE "${CMAKE_SOURCE_DIR}/include")

Step 2: Custom Processing Script Example

Here's a Python script (wrap_log_functions.py) that wraps [[log]] functions with logger calls. It uses regex to match function signatures (adjust regex for your code style):

import re
import sys

def wrap_log_functions(input_path, output_path):
    with open(input_path, 'r') as f:
        content = f.read()

    # Match functions marked with [[log]] (adjust regex for complex signatures)
    log_func_pattern = re.compile(
        r'\[\[log\]\]\s*(?P<ret_type>\w+)\s*(?P<func_name>\w+)\s*\((?P<params>.*?)\)\s*{(?P<body>.*?)}',
        re.DOTALL
    )

    def inject_logging(match):
        ret_type = match.group('ret_type')
        func_name = match.group('func_name')
        params = match.group('params')
        body = match.group('body').strip()

        # Extract parameter names for logging
        param_names = [p.split()[-1] for p in params.split(',') if p.strip()] if params else []
        param_log = ', '.join(param_names)

        # Generate wrapped function body
        if ret_type == 'void':
            wrapped_body = f'''
    LOG_ENTER("{func_name}", {param_log});
    {body};
    LOG_EXIT("{func_name}");
'''
        else:
            wrapped_body = f'''
    LOG_ENTER("{func_name}", {param_log});
    {ret_type} result = {body};
    LOG_EXIT("{func_name}", result);
    return result;
'''
        return f'{ret_type} {func_name}({params}) {{{wrapped_body}}}'

    # Apply replacements and save output
    processed_content = log_func_pattern.sub(inject_logging, content)
    with open(output_path, 'w') as f:
        f.write(processed_content)

if __name__ == '__main__':
    if len(sys.argv) != 3:
        print("Usage: python wrap_log_functions.py <input_preprocessed_file> <output_modified_file>")
        sys.exit(1)
    wrap_log_functions(sys.argv[1], sys.argv[2])

Step 3: Define Logger Macros

Add a header file (include/log_macros.h) to define your logging functionality:

#ifndef LOG_MACROS_H
#define LOG_MACROS_H

#include <iostream>

#define LOG_ENTER(func_name, ...) \
    std::cout << "[LOG] Entering " << func_name << ": " << #__VA_ARGS__ << std::endl;

#define LOG_EXIT(func_name, ...) \
    std::cout << "[LOG] Exiting " << func_name << ": " << #__VA_ARGS__ << std::endl;

#endif // LOG_MACROS_H

Approach 2: CMake + Clang (Robust AST-Based Processing)

For more reliable code modification (avoiding regex pitfalls with complex C++ syntax), use Clang's AST matchers to directly parse and modify source code before preprocessing.

Step 1: Build a Custom Clang Tool

Write a C++ tool using Clang Tooling to find [[log]] functions and inject logging:

#include "clang/AST/ASTConsumer.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"

using namespace clang;
using namespace clang::ast_matchers;
using namespace clang::tooling;

static llvm::cl::OptionCategory LogWrapperToolCategory("Log Wrapper Tool Options");

class LogWrapperCallback : public MatchFinder::MatchCallback {
public:
    void run(const MatchFinder::MatchResult& Result) override {
        if (const FunctionDecl* FD = Result.Nodes.getNodeAs<FunctionDecl>("logFunc")) {
            // Skip functions without [[log]] attribute or body
            if (!FD->hasAttr<AnnotateAttr>("log") || !FD->getBody()) return;

            ASTContext* Ctx = Result.Context;
            SourceManager& SM = Ctx->getSourceManager();
            Replacements& Replaces = *Result.SourceManager;

            // Insert enter-log at start of function body
            std::string enterLog = "LOG_ENTER(\"" + FD->getNameAsString() + "\"";
            for (const ParmVarDecl* Param : FD->parameters()) {
                enterLog += ", " + Param->getNameAsString();
            }
            enterLog += ");\n";
            Replaces.insert(Replacement(SM, FD->getBody()->getBeginLoc(), enterLog));

            // Insert exit-log (handle void return type)
            if (!FD->getReturnType()->isVoidType()) {
                // Wrap body in result variable and exit log
                std::string bodyText = Lexer::getSourceText(
                    CharSourceRange::getTokenRange(FD->getBody()->getSourceRange()),
                    SM, Ctx->getLangOpts()
                ).str();
                std::string wrappedBody = FD->getReturnType().getAsString() + " result = " + bodyText + ";\n"
                    "LOG_EXIT(\"" + FD->getNameAsString() + "\", result);\nreturn result;";
                Replaces.insert(Replacement(SM, FD->getBody()->getSourceRange(), wrappedBody));
            } else {
                std::string exitLog = "LOG_EXIT(\"" + FD->getNameAsString() + "\");\n";
                Replaces.insert(Replacement(SM, FD->getBody()->getEndLoc().getLocWithOffset(-1), exitLog));
            }
        }
    }
};

class LogWrapperAction : public ASTFrontendAction {
public:
    std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance& CI, StringRef) override {
        auto finder = std::make_unique<MatchFinder>();
        finder->addMatcher(functionDecl(hasAttr("log")).bind("logFunc"), &callback);
        return finder->newASTConsumer();
    }
private:
    LogWrapperCallback callback;
};

int main(int argc, const char** argv) {
    auto parser = CommonOptionsParser::create(argc, argv, LogWrapperToolCategory);
    if (!parser) {
        llvm::errs() << parser.takeError();
        return 1;
    }
    ClangTool tool(parser->getCompilations(), parser->getSourcePathList());
    return tool.run(newFrontendActionFactory<LogWrapperAction>().get());
}

Step 2: Integrate Clang Tool into CMake

Add this to your CMakeLists.txt to compile the tool and use it to modify source files:

# Build the Clang Log Wrapper Tool
add_executable(LogWrapperTool src/log_wrapper_tool.cpp)
find_package(LLVM REQUIRED CONFIG)
find_package(Clang REQUIRED CONFIG)
target_link_libraries(LogWrapperTool PRIVATE clangTooling clangASTMatchers)

# Process source files with the Clang tool
set(MODIFIED_SOURCES "")
foreach(SOURCE_FILE ${SOURCES})
    get_filename_component(BASE_NAME ${SOURCE_FILE} NAME_WE)
    set(MODIFIED_FILE "${CMAKE_BINARY_DIR}/modified/${BASE_NAME}_modified.cpp")

    add_custom_command(
        OUTPUT ${MODIFIED_FILE}
        COMMAND LogWrapperTool ${SOURCE_FILE} -o ${MODIFIED_FILE}
        DEPENDS ${SOURCE_FILE} LogWrapperTool
        COMMENT "Injecting logging into ${BASE_NAME}.cpp..."
    )
    list(APPEND MODIFIED_SOURCES ${MODIFIED_FILE})
endforeach()

# Compile modified files into your app
add_executable(my_app ${MODIFIED_SOURCES})
target_include_directories(my_app PRIVATE "${CMAKE_SOURCE_DIR}/include")
target_compile_features(my_app PRIVATE cxx_std_17)

Key Notes

  • Incremental Builds: CMake automatically handles dependencies, so only modified source files/scripts will re-run through the pipeline.
  • Preprocessing Flags: Ensure all necessary -I include paths and -D macros are passed to the preprocessing step in the general approach.
  • Logger Customization: Extend the LOG_ENTER/LOG_EXIT macros to write to files, use a logging library, or add timestamps as needed.

内容的提问来源于stack exchange,提问作者Michael Litvin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 04:27:44