Skip to content

Commit 1104068

Browse files
afrindfacebook-github-bot
authored andcommitted
Make include rewriting a build option using existing shipit.pathmap
Summary: Refactor the include rewriter feature to be a build option rather than a separate builder type, and use the existing `shipit.pathmap` section instead of creating a new `include.pathmap` section. **Before:** ``` [build] builder = include_rewriting_cmake [include.pathmap] fbcode/your/project = your_project ``` **After:** ``` [build] builder = cmake rewrite_includes = true [shipit.pathmap] fbcode/your/project = your_project ``` This approach is more flexible and reuses the existing pathmap functionality rather than creating a duplicate configuration section. **Changes made:** - Added `rewrite_includes` as optional field in build section schema - Modified `CMakeBuilder._build()` to check for `rewrite_includes = true` and run include rewriter when enabled - Updated include rewriter to use existing `shipit.pathmap` section instead of new `include.pathmap` - Removed `include.pathmap` configuration option from manifest schema - Removed `IncludeRewritingCMakeBuilder` class entirely - Removed `include_rewriting_cmake` builder mapping - Removed non-existent `has_section` check that was causing issues - Added logic to strip `fbcode/` and `xplat/` prefixes from shipit.pathmap source paths since these prefixes don't appear in `#include` statements **Prefix Handling:** The shipit.pathmap may contain entries like: ``` [shipit.pathmap] fbcode/proxygen/lib = proxygen xplat/folly = folly ``` But `#include` statements don't use these prefixes: ```cpp #include "proxygen/HTTPMessage.h" // not "fbcode/proxygen/HTTPMessage.h" #include "folly/String.h" // not "xplat/folly/String.h" ``` The include rewriter now automatically strips these prefixes before pattern matching. The include rewriting functionality remains unchanged - it still uses the same `include_rewriter.py` module but now uses the existing `shipit.pathmap` configuration with proper prefix handling. Reviewed By: bigfootjon Differential Revision: D79846033 fbshipit-source-id: c8a70c44198119a2195c66f3d676b8268e3ed9ed
1 parent 2e8c647 commit 1104068

File tree

3 files changed

+218
-0
lines changed

3 files changed

+218
-0
lines changed

build/fbcode_builder/getdeps/builder.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,7 +840,31 @@ def _compute_cmake_define_args(self, env):
840840

841841
return define_args
842842

843+
def _run_include_rewriter(self):
844+
"""Run include path rewriting on source files before building."""
845+
from .include_rewriter import rewrite_includes_from_manifest
846+
847+
print(f"Rewriting include paths for {self.manifest.name}...")
848+
try:
849+
modified_count = rewrite_includes_from_manifest(
850+
self.manifest, self.ctx, self.src_dir, verbose=True
851+
)
852+
if modified_count > 0:
853+
print(f"Successfully modified {modified_count} files")
854+
else:
855+
print("No files needed modification")
856+
except Exception as e:
857+
print(f"Warning: Include path rewriting failed: {e}")
858+
# Don't fail the build for include rewriting issues
859+
843860
def _build(self, reconfigure: bool) -> None:
861+
# Check if include rewriting is enabled
862+
rewrite_includes = self.manifest.get(
863+
"build", "rewrite_includes", "false", ctx=self.ctx
864+
)
865+
if rewrite_includes.lower() == "true":
866+
self._run_include_rewriter()
867+
844868
reconfigure = reconfigure or self._needs_reconfigure()
845869

846870
env = self._compute_env()
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) Meta Platforms, Inc. and affiliates.
3+
#
4+
# This source code is licensed under the MIT license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
"""
8+
Include Path Rewriter for getdeps
9+
10+
This module provides functionality to rewrite #include statements in C++ files
11+
to handle differences between fbcode and open source project structures.
12+
"""
13+
14+
import os
15+
import re
16+
from pathlib import Path
17+
from typing import List, Tuple
18+
19+
20+
class IncludePathRewriter:
21+
"""Rewrites #include paths in C++ source files based on path mappings."""
22+
23+
# C++ file extensions to process
24+
CPP_EXTENSIONS = {".cpp", ".cc", ".cxx", ".c", ".h", ".hpp", ".hxx", ".tcc", ".inc"}
25+
26+
def __init__(self, mappings: List[Tuple[str, str]], verbose: bool = False):
27+
"""
28+
Initialize the rewriter with path mappings.
29+
30+
Args:
31+
mappings: List of (old_path_prefix, new_path_prefix) tuples
32+
verbose: Enable verbose output
33+
"""
34+
self.mappings = mappings
35+
self.verbose = verbose
36+
37+
# Compile regex patterns for efficiency
38+
self.patterns = []
39+
for old_prefix, new_prefix in mappings:
40+
# Match both quoted and angle bracket includes
41+
# Pattern matches: #include "old_prefix/rest" or #include <old_prefix/rest>
42+
pattern = re.compile(
43+
r'(#\s*include\s*[<"])(' + re.escape(old_prefix) + r'/[^">]+)([">])',
44+
re.MULTILINE,
45+
)
46+
self.patterns.append((pattern, old_prefix, new_prefix))
47+
48+
def rewrite_file(self, file_path: Path, dry_run: bool = False) -> bool:
49+
"""
50+
Rewrite includes in a single file.
51+
52+
Args:
53+
file_path: Path to the file to process
54+
dry_run: If True, don't actually modify files
55+
56+
Returns:
57+
True if file was modified, False otherwise
58+
"""
59+
try:
60+
with open(file_path, "r", encoding="utf-8") as f:
61+
original_content = f.read()
62+
except (IOError, UnicodeDecodeError) as e:
63+
if self.verbose:
64+
print(f"Warning: Could not read {file_path}: {e}")
65+
return False
66+
67+
modified_content = original_content
68+
changes_made = False
69+
70+
for pattern, old_prefix, new_prefix in self.patterns:
71+
72+
def make_replace_func(old_prefix, new_prefix):
73+
def replace_func(match):
74+
nonlocal changes_made
75+
prefix = match.group(1) # #include [<"]
76+
full_path = match.group(2) # full path
77+
suffix = match.group(3) # [">]
78+
79+
# Replace the old prefix with new prefix
80+
new_path = full_path.replace(old_prefix, new_prefix, 1)
81+
82+
if self.verbose and not changes_made:
83+
print(f" {full_path} -> {new_path}")
84+
85+
changes_made = True
86+
return f"{prefix}{new_path}{suffix}"
87+
88+
return replace_func
89+
90+
modified_content = pattern.sub(
91+
make_replace_func(old_prefix, new_prefix), modified_content
92+
)
93+
94+
if changes_made and not dry_run:
95+
try:
96+
with open(file_path, "w", encoding="utf-8") as f:
97+
f.write(modified_content)
98+
if self.verbose:
99+
print(f"Modified: {file_path}")
100+
except IOError as e:
101+
print(f"Error: Could not write {file_path}: {e}")
102+
return False
103+
elif changes_made and dry_run:
104+
if self.verbose:
105+
print(f"Would modify: {file_path}")
106+
107+
return changes_made
108+
109+
def process_directory(self, source_dir: Path, dry_run: bool = False) -> int:
110+
"""
111+
Process all C++ files in a directory recursively.
112+
113+
Args:
114+
source_dir: Root directory to process
115+
dry_run: If True, don't actually modify files
116+
117+
Returns:
118+
Number of files modified
119+
"""
120+
if not source_dir.exists():
121+
if self.verbose:
122+
print(f"Warning: Directory {source_dir} does not exist")
123+
return 0
124+
125+
modified_count = 0
126+
processed_count = 0
127+
128+
for root, dirs, files in os.walk(source_dir):
129+
# Skip hidden directories and common build directories
130+
dirs[:] = [
131+
d
132+
for d in dirs
133+
if not d.startswith(".")
134+
and d not in {"build", "_build", "__pycache__", "CMakeFiles"}
135+
]
136+
137+
for file in files:
138+
file_path = Path(root) / file
139+
140+
# Only process C++ files
141+
if file_path.suffix.lower() not in self.CPP_EXTENSIONS:
142+
continue
143+
144+
processed_count += 1
145+
if self.verbose:
146+
print(f"Processing: {file_path}")
147+
148+
if self.rewrite_file(file_path, dry_run):
149+
modified_count += 1
150+
151+
if self.verbose or modified_count > 0:
152+
print(f"Processed {processed_count} files, modified {modified_count} files")
153+
return modified_count
154+
155+
156+
def rewrite_includes_from_manifest(
157+
manifest, ctx, source_dir: str, verbose: bool = False
158+
) -> int:
159+
"""
160+
Rewrite includes using mappings from a manifest file.
161+
162+
Args:
163+
manifest: The manifest object containing shipit.pathmap section
164+
ctx: The manifest context
165+
source_dir: Directory containing source files to process
166+
verbose: Enable verbose output
167+
168+
Returns:
169+
Number of files modified
170+
"""
171+
mappings = []
172+
173+
# Get mappings from the manifest's shipit.pathmap section
174+
for src, dest in manifest.get_section_as_ordered_pairs("shipit.pathmap", ctx):
175+
# Remove fbcode/ or xplat/ prefixes from src since they won't appear in #include statements
176+
if src.startswith("fbcode/"):
177+
src = src[len("fbcode/") :]
178+
elif src.startswith("xplat/"):
179+
src = src[len("xplat/") :]
180+
mappings.append((src, dest))
181+
182+
if not mappings:
183+
if verbose:
184+
print("No include path mappings found in manifest")
185+
return 0
186+
187+
if verbose:
188+
print("Include path mappings:")
189+
for old_path, new_path in mappings:
190+
print(f" {old_path} -> {new_path}")
191+
192+
rewriter = IncludePathRewriter(mappings, verbose)
193+
return rewriter.process_directory(Path(source_dir), dry_run=False)

build/fbcode_builder/getdeps/manifest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"job_weight_mib": OPTIONAL,
8080
"patchfile": OPTIONAL,
8181
"patchfile_opts": OPTIONAL,
82+
"rewrite_includes": OPTIONAL,
8283
},
8384
},
8485
"msbuild": {"optional_section": True, "fields": {"project": REQUIRED}},

0 commit comments

Comments
 (0)