[Lldb-commits] [lldb] [lldb][headers] Create Python script to fix up framework headers (PR #142051)

Chelsea Cassanova via lldb-commits lldb-commits at lists.llvm.org
Fri May 30 14:58:07 PDT 2025


https://github.com/chelcassanova updated https://github.com/llvm/llvm-project/pull/142051

>From d39eeaae552395cd79324aaa38eccb3f9dfe4a79 Mon Sep 17 00:00:00 2001
From: Chelsea Cassanova <chelsea_cassanova at apple.com>
Date: Wed, 28 May 2025 15:45:45 -0700
Subject: [PATCH] [lldb][headers] Create Python script to fix up framework
 headers

This commit replaces the shell script that fixes up includes for the
LLDB framework with a Python script. This script will also be used when
fixing up includes for the LLDBRPC.framework.
---
 lldb/cmake/modules/LLDBFramework.cmake        |  34 ++---
 lldb/scripts/framework-header-fix.py          | 129 ++++++++++++++++++
 lldb/scripts/framework-header-fix.sh          |  17 ---
 .../Shell/Scripts/Inputs/Main/SBAddress.h     |  13 ++
 .../Shell/Scripts/Inputs/RPC/RPCSBAddress.h   |   9 ++
 .../Shell/Scripts/TestFrameworkFixScript.test |  16 +++
 .../Scripts/TestRPCFrameworkFixScript.test    |  14 ++
 7 files changed, 196 insertions(+), 36 deletions(-)
 create mode 100755 lldb/scripts/framework-header-fix.py
 delete mode 100755 lldb/scripts/framework-header-fix.sh
 create mode 100644 lldb/test/Shell/Scripts/Inputs/Main/SBAddress.h
 create mode 100644 lldb/test/Shell/Scripts/Inputs/RPC/RPCSBAddress.h
 create mode 100644 lldb/test/Shell/Scripts/TestFrameworkFixScript.test
 create mode 100644 lldb/test/Shell/Scripts/TestRPCFrameworkFixScript.test

diff --git a/lldb/cmake/modules/LLDBFramework.cmake b/lldb/cmake/modules/LLDBFramework.cmake
index 471aeaaad3c0d..7688fece162ff 100644
--- a/lldb/cmake/modules/LLDBFramework.cmake
+++ b/lldb/cmake/modules/LLDBFramework.cmake
@@ -68,24 +68,16 @@ if(NOT APPLE_EMBEDDED)
   )
 endif()
 
-# At configuration time, collect headers for the framework bundle and copy them
-# into a staging directory. Later we can copy over the entire folder.
-file(GLOB public_headers ${LLDB_SOURCE_DIR}/include/lldb/API/*.h)
-set(generated_public_headers ${LLDB_OBJ_DIR}/include/lldb/API/SBLanguages.h)
-file(GLOB root_public_headers ${LLDB_SOURCE_DIR}/include/lldb/lldb-*.h)
-file(GLOB root_private_headers ${LLDB_SOURCE_DIR}/include/lldb/lldb-private*.h)
-list(REMOVE_ITEM root_public_headers ${root_private_headers})
-
 find_program(unifdef_EXECUTABLE unifdef)
 
-set(lldb_header_staging ${CMAKE_CURRENT_BINARY_DIR}/FrameworkHeaders)
-foreach(header
-    ${public_headers}
-    ${generated_public_headers}
-    ${root_public_headers})
+# All necessary header files will be staged in the include directory in the build directory,
+# so just copy the files from there into the framework's staging directory.
+set(lldb_build_dir_header_staging ${CMAKE_BINARY_DIR}/include/lldb)
+set(lldb_framework_header_staging ${CMAKE_CURRENT_BINARY_DIR}/FrameworkHeaders)
+foreach(header ${lldb_build_dir_header_staging})
 
   get_filename_component(basename ${header} NAME)
-  set(staged_header ${lldb_header_staging}/${basename})
+  set(staged_header ${lldb_framework_header_staging}/${basename})
 
   if(unifdef_EXECUTABLE)
     # unifdef returns 0 when the file is unchanged and 1 if something was changed.
@@ -107,14 +99,18 @@ endforeach()
 # Wrap output in a target, so lldb-framework can depend on it.
 add_custom_target(liblldb-resource-headers DEPENDS lldb-sbapi-dwarf-enums ${lldb_staged_headers})
 set_target_properties(liblldb-resource-headers PROPERTIES FOLDER "LLDB/Resources")
+
+# We're taking the header files from where they've been staged in the build directory's include folder,
+# so create a dependency on the build step that creates that directory.
+add_dependencies(liblldb-resource-headers liblldb-header-staging)
 add_dependencies(liblldb liblldb-resource-headers)
 
-# At build time, copy the staged headers into the framework bundle (and do
-# some post-processing in-place).
+# Take the headers from the staging directory and fix up their includes for the framework.
+# Then write them to the output directory.
+# Also, run unifdef to remove any specified guards from the header files.
 add_custom_command(TARGET liblldb POST_BUILD
-  COMMAND ${CMAKE_COMMAND} -E copy_directory ${lldb_header_staging} $<TARGET_FILE_DIR:liblldb>/Headers
-  COMMAND ${LLDB_SOURCE_DIR}/scripts/framework-header-fix.sh $<TARGET_FILE_DIR:liblldb>/Headers ${LLDB_VERSION}
-  COMMENT "LLDB.framework: copy framework headers"
+  COMMAND ${LLDB_SOURCE_DIR}/scripts/framework-header-fix.py -f lldb_main -i ${lldb_framework_header_staging} -o $<TARGET_FILE_DIR:liblldb>/Headers USWIG
+  COMMENT "LLDB.framework: Fix up and copy framework headers"
 )
 
 # Copy vendor-specific headers from clang (without staging).
diff --git a/lldb/scripts/framework-header-fix.py b/lldb/scripts/framework-header-fix.py
new file mode 100755
index 0000000000000..c4bd06ba217d6
--- /dev/null
+++ b/lldb/scripts/framework-header-fix.py
@@ -0,0 +1,129 @@
+#!/usr/bin/env python3
+
+"""
+Usage: <path/to/input-directory> <path/to/output-directory>
+
+This script is used when building LLDB.framework or LLDBRPC.framework. For each framework, local includes are converted to their respective framework includes.
+
+This script is used in 2 ways:
+1. It is used on header files that are copied into LLDB.framework. For these files, local LLDB includes are converted into framework includes, e.g. #include "lldb/API/SBDefines.h" -> #include <LLDB/SBDefines.h>.
+
+2. It is used on header files for LLDBRPC.framework. For these files, includes of RPC common files will be converted to framework includes, e.g. #include <lldb-rpc/common/RPCCommon.h> -> #include <LLDBRPC/RPCCommon.h>. It will also change local includes to framework includes, e.g. #include "SBAddress.h" -> #include <LLDBRPC/SBAddress.h>
+"""
+
+import argparse
+import os
+import re
+import subprocess
+
+# Main header regexes
+INCLUDE_FILENAME_REGEX = re.compile(
+    r'#include "lldb/API/(?P<include_filename>.*){0,1}"'
+)
+
+# RPC header regexes
+RPC_COMMON_REGEX = re.compile(r"#include <lldb-rpc/common/(?P<include_filename>.*)>")
+RPC_INCLUDE_FILENAME_REGEX = re.compile(r'#include "(?P<include_filename>.*)"')
+
+
+def modify_rpc_includes(input_directory_path, output_directory_path):
+    for input_filepath in os.listdir(input_directory_path):
+        current_input_file = os.path.join(input_directory_path, input_filepath)
+        output_dest = os.path.join(output_directory_path, input_filepath)
+        if os.path.isfile(current_input_file):
+            with open(current_input_file, "r") as input_file:
+                lines = input_file.readlines()
+                file_buffer = "".join(lines)
+            with open(output_dest, "w") as output_file:
+                # Local includes must be changed to RPC framework level includes.
+                # e.g. #include "SBDefines.h" -> #include <LLDBRPC/SBDefines.h>
+                # Also, RPC common code includes must change to RPC framework level includes.
+                # e.g. #include "lldb-rpc/common/RPCPublic.h" -> #include <LLDBRPC/RPCPublic.h>
+                rpc_common_matches = RPC_COMMON_REGEX.finditer(file_buffer)
+                rpc_include_filename_matches = RPC_INCLUDE_FILENAME_REGEX.finditer(
+                    file_buffer
+                )
+                for match in rpc_common_matches:
+                    file_buffer = re.sub(
+                        match.group(),
+                        r"#include <LLDBRPC/" + match.group("include_filename") + ">",
+                        file_buffer,
+                    )
+                for match in rpc_include_filename_matches:
+                    file_buffer = re.sub(
+                        match.group(),
+                        r"#include <LLDBRPC/" + match.group("include_filename") + ">",
+                        file_buffer,
+                    )
+                output_file.write(file_buffer)
+
+
+def modify_main_includes(input_directory_path, output_directory_path):
+    for input_filepath in os.listdir(input_directory_path):
+        current_input_file = os.path.join(input_directory_path, input_filepath)
+        output_dest = os.path.join(output_directory_path, input_filepath)
+        if os.path.isfile(current_input_file):
+            with open(current_input_file, "r") as input_file:
+                lines = input_file.readlines()
+                file_buffer = "".join(lines)
+            with open(output_dest, "w") as output_file:
+                # Local includes must be changed to framework level includes.
+                # e.g. #include "lldb/API/SBDefines.h" -> #include <LLDB/SBDefines.h>
+                regex_matches = INCLUDE_FILENAME_REGEX.finditer(file_buffer)
+                for match in regex_matches:
+                    file_buffer = re.sub(
+                        match.group(),
+                        r"#include <LLDB/" + match.group("include_filename") + ">",
+                        file_buffer,
+                    )
+                output_file.write(file_buffer)
+
+
+def remove_guards(output_directory_path, unifdef_path, unifdef_guards):
+    # The unifdef path should be passed in from CMake. If it wasn't there in CMake,
+    # find it using shutil.
+    if not unifdef_path:
+        unifdef_path = shutil.which("unifdef")
+    for current_file in os.listdir(output_directory_path):
+        if (os.path.isfile(current_file)):
+            current_file = os.path.join(output_directory_path, current_file)
+            subprocess_command = (
+                [unifdef_path, "-o", current_file] + unifdef_guards + [current_file]
+            )
+            subprocess.run(subprocess_command)
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("-f","--framework", choices=["lldb_main", "lldb_rpc"])
+    parser.add_argument("-i", "--input_directory")
+    parser.add_argument("-o", "--output_directory")
+    parser.add_argument("-p", "--unifdef_path")
+    parser.add_argument(
+        "unifdef_guards",
+        nargs="+",
+        type=str,
+        help="Guards to be removed with unifdef. These must be specified in the same way as they would be when passed directly into unifdef.",
+    )
+    args = parser.parse_args()
+    input_directory_path = str(args.input_directory)
+    output_directory_path = str(args.output_directory)
+    framework_version = args.framework
+    unifdef_path = str(args.unifdef_path)
+    # Prepend dashes to the list of guards passed in from the command line.
+    # unifdef takes the guards to remove as arguments in their own right (e.g. -USWIG)
+    # but passing them in with dashes for this script causes argparse to think that they're
+    # arguments in and of themself, so they need to passed in without dashes.
+    unifdef_guards = ["-" + guard for guard in args.unifdef_guards]
+
+    if framework_version == "lldb_main":
+        modify_main_includes(input_directory_path, output_directory_path)
+    if framework_version == "lldb_rpc":
+        modify_rpc_includes(input_directory_path, output_directory_path)
+    # After the incldues have been modified, run unifdef on the headers to remove any guards
+    # specified at the command line.
+    remove_guards(output_directory_path, unifdef_path, unifdef_guards)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/lldb/scripts/framework-header-fix.sh b/lldb/scripts/framework-header-fix.sh
deleted file mode 100755
index 3459dd91c9ec1..0000000000000
--- a/lldb/scripts/framework-header-fix.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/sh
-# Usage: framework-header-fix.sh <source header dir> <LLDB Version>
-
-set -e
-
-for file in `find $1 -name "*.h"`
-do
-  /usr/bin/sed -i.bak 's/\(#include\)[ ]*"lldb\/\(API\/\)\{0,1\}\(.*\)"/\1 <LLDB\/\3>/1' "$file"
-  /usr/bin/sed -i.bak 's|<LLDB/Utility|<LLDB|' "$file"
-  LLDB_VERSION=`echo $2 | /usr/bin/sed -E 's/^([0-9]+).([0-9]+).([0-9]+)(.[0-9]+)?$/\\1/g'`
-  LLDB_REVISION=`echo $2 | /usr/bin/sed -E 's/^([0-9]+).([0-9]+).([0-9]+)(.[0-9]+)?$/\\3/g'`
-  LLDB_VERSION_STRING=`echo $2`
-  /usr/bin/sed -i.bak "s|//#define LLDB_VERSION$|#define LLDB_VERSION $LLDB_VERSION |" "$file"
-  /usr/bin/sed -i.bak "s|//#define LLDB_REVISION|#define LLDB_REVISION $LLDB_REVISION |" "$file"
-  /usr/bin/sed -i.bak "s|//#define LLDB_VERSION_STRING|#define LLDB_VERSION_STRING \"$LLDB_VERSION_STRING\" |" "$file"
-  rm -f "$file.bak"
-done
diff --git a/lldb/test/Shell/Scripts/Inputs/Main/SBAddress.h b/lldb/test/Shell/Scripts/Inputs/Main/SBAddress.h
new file mode 100644
index 0000000000000..fecc69687cd74
--- /dev/null
+++ b/lldb/test/Shell/Scripts/Inputs/Main/SBAddress.h
@@ -0,0 +1,13 @@
+// This is a truncated version of an SB API file
+// used to test framework-header-fix.py to make sure the includes are correctly fixed
+// up for the LLDB.framework.
+
+// Local includes must be changed to framework level includes.
+// e.g. #include "lldb/API/SBDefines.h" -> #include <LLDB/SBDefines.h>
+#include "lldb/API/SBDefines.h"
+#include "lldb/API/SBModule.h"
+
+// Any include guards specified at the command line must be removed.
+#ifndef SWIG
+int a = 10
+#endif
diff --git a/lldb/test/Shell/Scripts/Inputs/RPC/RPCSBAddress.h b/lldb/test/Shell/Scripts/Inputs/RPC/RPCSBAddress.h
new file mode 100644
index 0000000000000..556afa38a9225
--- /dev/null
+++ b/lldb/test/Shell/Scripts/Inputs/RPC/RPCSBAddress.h
@@ -0,0 +1,9 @@
+// This is a truncated version of an SB API file generated by lldb-rpc-gen
+// used to test framework-header-fix.py to make sure the includes are correctly fixed
+// up for the LLDBRPC.framework.
+
+// Local includes must be changed to framework level includes.
+// e.g. #include "lldb/API/SBDefines.h" -> #include <LLDB/SBDefines.h>
+#include "LLDBRPC.h"
+#include "SBDefines.h"
+#include <lldb-rpc/common/RPCPublic.h>
diff --git a/lldb/test/Shell/Scripts/TestFrameworkFixScript.test b/lldb/test/Shell/Scripts/TestFrameworkFixScript.test
new file mode 100644
index 0000000000000..c9b848c42092a
--- /dev/null
+++ b/lldb/test/Shell/Scripts/TestFrameworkFixScript.test
@@ -0,0 +1,16 @@
+# Create a temp dir for output and run the framework fix script on the truncated version of SBAddress.h in the inputs dir.
+RUN: mkdir -p %t/Outputs
+RUN: %python %p/../../../scripts/framework-header-fix.py lldb_main %p/Inputs/Main %t/Outputs/ --unifdef_guards=-USWIG
+
+# Check the output
+RUN: cat %t/Outputs/SBAddress.h | FileCheck %s
+
+# Local includes must be changed to framework level includes.
+# e.g. #include "lldb/API/SBDefines.h" -> #include <LLDB/SBDefines.h>
+CHECK: #include <LLDB/SBDefines.h>
+CHECK: #include <LLDB/SBModule.h>
+
+# Any include guards specified at the command line must be removed.
+CHECK-NOT: #ifndef SWIG
+CHECK: int a = 10
+CHECK-NOT: #endif
diff --git a/lldb/test/Shell/Scripts/TestRPCFrameworkFixScript.test b/lldb/test/Shell/Scripts/TestRPCFrameworkFixScript.test
new file mode 100644
index 0000000000000..be2f70f7a2461
--- /dev/null
+++ b/lldb/test/Shell/Scripts/TestRPCFrameworkFixScript.test
@@ -0,0 +1,14 @@
+# Create a temp dir for output and run the framework fix script on the truncated version of SBAddress.h in the inputs dir.
+RUN: mkdir -p %t/Outputs
+RUN: %python %p/../../../scripts/framework-header-fix.py lldb_rpc %p/Inputs/ %t/Outputs/
+
+# Check the output
+RUN: cat %t/Outputs/RPCSBAddress.h | FileCheck %s
+
+# Local includes must be changed to RPC framework level includes.
+# e.g. #include "SBDefines.h" -> #include <LLDBRPC/SBDefines.h>
+# Also, RPC common code includes must change to RPC framework level includes.
+# e.g. #include "lldb-rpc/common/RPCPublic.h" -> #include <LLDBRPC/RPCPublic.h>
+CHECK: #include <LLDBRPC/RPCPublic.h>
+CHECK: #include <LLDBRPC/SBDefines.h>
+CHECK: #include <LLDBRPC/LLDBRPC.h>



More information about the lldb-commits mailing list