Skip to content

Commit 0b240be

Browse files
jbower-fbmeta-codesync[bot]
authored andcommitted
Enable PGO + LTO in Sandcastle job
Summary: To make these work we need to make LLVM's runtime profiling library/llvm-ar or a newer version of GCC available on Sandcastle hosts. I've gone with making GCC-14 available as this also matches the cibuildwheel build we have on GitHub and so will help keep our internal and external builds consistent. Reviewed By: alexmalyshev Differential Revision: D87987135 fbshipit-source-id: 5670450efb6fdb6520c828f3ee7ea504a64b79dc
1 parent ae16e0d commit 0b240be

4 files changed

Lines changed: 67 additions & 20 deletions

File tree

build/fbcode_builder/getdeps/builder.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,6 +1415,10 @@ class SetupPyBuilder(BuilderBase):
14151415
def _build(self, reconfigure) -> None:
14161416
env = self._compute_env()
14171417

1418+
setup_env = self.manifest.get_section_as_dict("setup-py.env", self.ctx)
1419+
for key, value in setup_env.items():
1420+
env[key] = value
1421+
14181422
setup_py_path = os.path.join(self.src_dir, "setup.py")
14191423

14201424
if not os.path.exists(setup_py_path):

build/fbcode_builder/getdeps/manifest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
# fb-only
125125
"sandcastle": {"optional_section": True, "fields": {"run_tests": OPTIONAL}},
126126
"setup-py.test": {"optional_section": True, "fields": {"python_script": REQUIRED}},
127+
"setup-py.env": {"optional_section": True},
127128
}
128129

129130
# These sections are allowed to vary for different platforms

build/fbcode_builder/manifests/cinderx-3_14

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,7 @@ fbcode/cinderx/oss_toplevel = .
2222

2323
[setup-py.test]
2424
python_script = cinderx/PythonLib/test_cinderx/test_oss_quick.py
25+
26+
[setup-py.env]
27+
CINDERX_ENABLE_PGO=1
28+
CINDERX_ENABLE_LTO=1

setup.py

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
import glob
1111
import os
1212
import os.path
13+
import re
1314
import shutil
1415
import subprocess
1516
import sys
1617
import sysconfig
1718
from enum import Enum
19+
from functools import lru_cache
1820

1921
from typing import Callable
2022

@@ -29,6 +31,57 @@
2931
PYTHON_LIB_DIR = os.path.join(SOURCE_DIR, "PythonLib")
3032

3133

34+
@lru_cache(maxsize=1)
35+
def get_compiler() -> tuple[str, str]:
36+
"""
37+
Prefers GCC if a new enough version is installed as this is what the
38+
cibuildwheel environment uses.
39+
40+
Returns:
41+
A tuple of (c_compiler, cxx_compiler) paths.
42+
"""
43+
gcc_path = shutil.which("gcc")
44+
gxx_path = shutil.which("g++")
45+
46+
if gcc_path and gxx_path:
47+
try:
48+
result = subprocess.run(
49+
[gcc_path, "--version"],
50+
capture_output=True,
51+
text=True,
52+
check=True,
53+
timeout=5,
54+
)
55+
version_output = result.stdout
56+
57+
# Parse GCC version from output like "gcc (GCC) 14.1.0"
58+
# The version is typically in the first line
59+
match = re.search(
60+
r"gcc.*?(\d+)\.(\d+)(?:\.(\d+))?", version_output, re.IGNORECASE
61+
)
62+
if match:
63+
major_version = int(match.group(1))
64+
print(f"Found GCC version {major_version}.{match.group(2)}")
65+
66+
if major_version >= 14:
67+
print(f"Using GCC: {gcc_path}, {gxx_path}")
68+
return (gcc_path, gxx_path)
69+
else:
70+
print(f"GCC version {major_version} < 14, checking for Clang")
71+
except (subprocess.SubprocessError, subprocess.TimeoutExpired) as e:
72+
print(f"Failed to determine GCC version: {e}, checking for Clang")
73+
74+
# Fall back to Clang
75+
clang_path = shutil.which("clang")
76+
clangxx_path = shutil.which("clang++")
77+
78+
if clang_path and clangxx_path:
79+
print(f"Using Clang: {clang_path}, {clangxx_path}")
80+
return (clang_path, clangxx_path)
81+
82+
raise RuntimeError("Cannot find suitable C/C++ compiler (tried gcc and clang)")
83+
84+
3285
class PgoStage(Enum):
3386
DISABLED = 0
3487
GENERATE = 1
@@ -80,7 +133,7 @@ def print_section(title: str) -> None:
80133
print(title)
81134
print(separator)
82135

83-
cc = self._find_binary(["clang", "gcc"])
136+
cc, _ = get_compiler()
84137
is_clang = "clang" in cc
85138

86139
print_section("PGO STAGE 1/3: Building with profile generation instrumentation")
@@ -143,7 +196,9 @@ def main():
143196
if is_clang:
144197
print_section("PGO STAGE 2b: Merging profile data")
145198

146-
llvm_profdata = self._find_binary(["llvm-profdata"])
199+
llvm_profdata = shutil.which("llvm-profdata")
200+
if not llvm_profdata:
201+
raise RuntimeError("Cannot find llvm-profdata")
147202
profraw_files = glob.glob(os.path.join(clang_pgo_dir, "*.profraw"))
148203

149204
if not profraw_files:
@@ -208,13 +263,6 @@ def main():
208263

209264
print_section("PGO BUILD COMPLETE!")
210265

211-
def _find_binary(self, name_options: list[str]) -> str:
212-
for name in name_options:
213-
result = shutil.which(name)
214-
if result is not None:
215-
return result
216-
raise RuntimeError(f"Cannot find any binaries out of {name_options}")
217-
218266

219267
class BuildPy(build_py):
220268
def run(self) -> None:
@@ -284,10 +332,7 @@ def _run_cmake(self, extension: CMakeExtension) -> None:
284332
extension_dir = os.path.abspath(self.get_ext_fullpath(extension.name))
285333
os.makedirs(extension_dir, exist_ok=True)
286334

287-
# Prefer Clang because that's what we develop against but some systems
288-
# including the manylinux build environment only have GCC.
289-
cc = self._find_binary(["clang", "gcc"])
290-
cxx = self._find_binary(["clang++", "g++"])
335+
cc, cxx = get_compiler()
291336

292337
build_type = os.environ.get("CMAKE_BUILD_TYPE", "RelWithDebInfo")
293338
verbose_makefile = os.environ.get("CMAKE_VERBOSE_MAKEFILE", "OFF")
@@ -370,13 +415,6 @@ def set_option(var: str, default: object) -> None:
370415
self.spawn(["cmake"] + cmake_args + ["-B", build_dir, CHECKOUT_ROOT_DIR])
371416
self.spawn(["cmake", "--build", build_dir] + build_args)
372417

373-
def _find_binary(self, name_options: list[str]) -> str:
374-
for name in name_options:
375-
result = shutil.which(name)
376-
if result is not None:
377-
return result
378-
raise RuntimeError(f"Cannot find any binaries out of {name_options}")
379-
380418
def _find_python(self) -> str:
381419
# Normally this would use "data", but that goes to a temporary build directory
382420
# under uv. Work off of the include directory instead.

0 commit comments

Comments
 (0)