Skip to content

Commit 3d8fa85

Browse files
committed
build: installation workflow supports sanitizers
1 parent 0d179e8 commit 3d8fa85

File tree

2 files changed

+163
-15
lines changed

2 files changed

+163
-15
lines changed

CMakeUserPresets.json.example

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,74 @@
244244
"rhs": "Darwin"
245245
},
246246
"generator": "Ninja"
247+
},
248+
{
249+
"name": "debug-macos-gcc-asan",
250+
"displayName": "Debug (macOS: gcc) with ASan",
251+
"description": "Preset for building MrDocs in Debug mode with the gcc compiler in macOS.",
252+
"inherits": "debug",
253+
"binaryDir": "${sourceDir}/build/${presetName}",
254+
"cacheVariables": {
255+
"CMAKE_BUILD_TYPE": "Debug",
256+
"LLVM_ROOT": "$env{HOME}/Developer/cpp-libs/llvm-project/install/debug-gcc-asan",
257+
"Clang_ROOT": "$env{HOME}/Developer/cpp-libs/llvm-project/install/debug-gcc-asan",
258+
"duktape_ROOT": "$env{HOME}/Developer/cpp-libs/duktape/install/debug-gcc-asan",
259+
"Duktape_ROOT": "$env{HOME}/Developer/cpp-libs/duktape/install/debug-gcc-asan",
260+
"libxml2_ROOT": "$env{HOME}/Developer/cpp-libs/libxml2/install/release-gcc",
261+
"LibXml2_ROOT": "$env{HOME}/Developer/cpp-libs/libxml2/install/release-gcc",
262+
"MRDOCS_BUILD_TESTS": true,
263+
"MRDOCS_BUILD_DOCS": false,
264+
"MRDOCS_GENERATE_REFERENCE": false,
265+
"MRDOCS_GENERATE_ANTORA_REFERENCE": false,
266+
"CMAKE_C_COMPILER": "/usr/bin/gcc",
267+
"CMAKE_CXX_COMPILER": "/usr/bin/g++",
268+
"CMAKE_MAKE_PROGRAM": "$env{HOME}/Developer/cpp-libs/ninja/ninja",
269+
"CMAKE_C_FLAGS": "-fsanitize=address -fno-sanitize-recover=address -fno-omit-frame-pointer",
270+
"CMAKE_CXX_FLAGS": "-fsanitize=address -fno-sanitize-recover=address -fno-omit-frame-pointer"
271+
},
272+
"warnings": {
273+
"unusedCli": false
274+
},
275+
"condition": {
276+
"type": "equals",
277+
"lhs": "${hostSystemName}",
278+
"rhs": "Darwin"
279+
},
280+
"generator": "Ninja"
281+
},
282+
{
283+
"name": "debug-macos-gcc-ubsan",
284+
"displayName": "Debug (macOS: gcc) with UBSan",
285+
"description": "Preset for building MrDocs in Debug mode with the gcc compiler in macOS.",
286+
"inherits": "debug",
287+
"binaryDir": "${sourceDir}/build/${presetName}",
288+
"cacheVariables": {
289+
"CMAKE_BUILD_TYPE": "Debug",
290+
"LLVM_ROOT": "$env{HOME}/Developer/cpp-libs/llvm-project/install/debug-gcc-ubsan",
291+
"Clang_ROOT": "$env{HOME}/Developer/cpp-libs/llvm-project/install/debug-gcc-ubsan",
292+
"duktape_ROOT": "$env{HOME}/Developer/cpp-libs/duktape/install/debug-gcc-ubsan",
293+
"Duktape_ROOT": "$env{HOME}/Developer/cpp-libs/duktape/install/debug-gcc-ubsan",
294+
"libxml2_ROOT": "$env{HOME}/Developer/cpp-libs/libxml2/install/release-gcc",
295+
"LibXml2_ROOT": "$env{HOME}/Developer/cpp-libs/libxml2/install/release-gcc",
296+
"MRDOCS_BUILD_TESTS": true,
297+
"MRDOCS_BUILD_DOCS": false,
298+
"MRDOCS_GENERATE_REFERENCE": false,
299+
"MRDOCS_GENERATE_ANTORA_REFERENCE": false,
300+
"CMAKE_C_COMPILER": "/usr/bin/gcc",
301+
"CMAKE_CXX_COMPILER": "/usr/bin/g++",
302+
"CMAKE_MAKE_PROGRAM": "$env{HOME}/Developer/cpp-libs/ninja/ninja",
303+
"CMAKE_C_FLAGS": "-fsanitize=undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer",
304+
"CMAKE_CXX_FLAGS": "-fsanitize=undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer"
305+
},
306+
"warnings": {
307+
"unusedCli": false
308+
},
309+
"condition": {
310+
"type": "equals",
311+
"lhs": "${hostSystemName}",
312+
"rhs": "Darwin"
313+
},
314+
"generator": "Ninja"
247315
}
248316
]
249317
}

bootstrap.py

Lines changed: 95 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class InstallOptions:
5252
# Compiler
5353
cc: str = ''
5454
cxx: str = ''
55+
sanitizer: str = ''
5556

5657
# Required tools
5758
git_path: str = ''
@@ -69,12 +70,12 @@ class InstallOptions:
6970
mrdocs_repo: str = "https://github.com/cppalliance/mrdocs"
7071
mrdocs_branch: str = "develop"
7172
mrdocs_use_user_presets: bool = True
72-
mrdocs_preset_name: str = "<mrdocs-build-type:lower>-<os:lower><\"-\":if(cc)><cc:basename>"
73-
mrdocs_build_dir: str = "<mrdocs-src-dir>/build/<mrdocs-build-type:lower>-<os:lower><\"-\":if(cc)><cc:basename>"
73+
mrdocs_preset_name: str = "<mrdocs-build-type:lower>-<os:lower><\"-\":if(cc)><cc:basename><\"-\":if(sanitizer)><sanitizer:lower>"
74+
mrdocs_build_dir: str = "<mrdocs-src-dir>/build/<mrdocs-build-type:lower>-<os:lower><\"-\":if(cc)><cc:basename><\"-\":if(sanitizer)><sanitizer:lower><\"-\":if(sanitizer)><sanitizer:lower>"
7475
mrdocs_build_tests: bool = True
7576
mrdocs_system_install: bool = field(default_factory=lambda: not running_from_mrdocs_source_dir())
7677
mrdocs_install_dir: str = field(
77-
default_factory=lambda: "<mrdocs-src-dir>/install/<mrdocs-build-type:lower>-<os:lower><\"-\":if(cc)><cc:basename>" if running_from_mrdocs_source_dir() else "")
78+
default_factory=lambda: "<mrdocs-src-dir>/install/<mrdocs-build-type:lower>-<os:lower><\"-\":if(cc)><cc:basename><\"-\":if(sanitizer)><sanitizer:lower>" if running_from_mrdocs_source_dir() else "")
7879
mrdocs_run_tests: bool = True
7980

8081
# Third-party dependencies
@@ -84,14 +85,14 @@ class InstallOptions:
8485
duktape_src_dir: str = "<third-party-src-dir>/duktape"
8586
duktape_url: str = "https://github.com/svaarala/duktape/releases/download/v2.7.0/duktape-2.7.0.tar.xz"
8687
duktape_build_type: str = "<mrdocs-build-type>"
87-
duktape_build_dir: str = "<duktape-src-dir>/build/<duktape-build-type:lower><\"-\":if(cc)><cc:basename>"
88-
duktape_install_dir: str = "<duktape-src-dir>/install/<duktape-build-type:lower><\"-\":if(cc)><cc:basename>"
88+
duktape_build_dir: str = "<duktape-src-dir>/build/<duktape-build-type:lower><\"-\":if(cc)><cc:basename><\"-\":if(sanitizer)><sanitizer:lower>"
89+
duktape_install_dir: str = "<duktape-src-dir>/install/<duktape-build-type:lower><\"-\":if(cc)><cc:basename><\"-\":if(sanitizer)><sanitizer:lower>"
8990

9091
# LLVM
9192
llvm_src_dir: str = "<third-party-src-dir>/llvm-project"
9293
llvm_build_type: str = "<mrdocs-build-type>"
93-
llvm_build_dir: str = "<llvm-src-dir>/build/<llvm-build-type:lower><\"-\":if(cc)><cc:basename>"
94-
llvm_install_dir: str = "<llvm-src-dir>/install/<llvm-build-type:lower><\"-\":if(cc)><cc:basename>"
94+
llvm_build_dir: str = "<llvm-src-dir>/build/<llvm-build-type:lower><\"-\":if(cc)><cc:basename><\"-\":if(sanitizer)><sanitizer:lower>"
95+
llvm_install_dir: str = "<llvm-src-dir>/install/<llvm-build-type:lower><\"-\":if(cc)><cc:basename><\"-\":if(sanitizer)><sanitizer:lower>"
9596
llvm_repo: str = "https://github.com/llvm/llvm-project.git"
9697
llvm_commit: str = "dd7a3d4d798e30dfe53b5bbbbcd9a23c24ea1af9"
9798

@@ -116,6 +117,7 @@ class InstallOptions:
116117
INSTALL_OPTION_DESCRIPTIONS = {
117118
"cc": "Path to the C compiler executable. Leave empty for default.",
118119
"cxx": "Path to the C++ compiler executable. Leave empty for default.",
120+
"sanitizer": "Sanitizer to use for the build. Leave empty for no sanitizer. (ASan, UBSan, MSan, TSan)",
119121
"git_path": "Path to the git executable, if not in system PATH.",
120122
"cmake_path": "Path to the cmake executable, if not in system PATH.",
121123
"java_path": "Path to the java executable, if not in system PATH.",
@@ -301,8 +303,9 @@ def repl(match):
301303
val = val.upper()
302304
elif transform_fn == "basename":
303305
val = os.path.basename(val)
304-
elif transform_fn == "if(cc)":
305-
if self.options.cc:
306+
elif transform_fn.startswith("if(") and transform_fn.endswith(")"):
307+
var_name = transform_fn[3:-1]
308+
if getattr(self.options, var_name, None):
306309
val = val.lower()
307310
else:
308311
val = ""
@@ -356,6 +359,25 @@ def prompt_build_type_option(self, name):
356359
print(f"Invalid build type '{value}'. Must be one of: {', '.join(valid_build_types)}.")
357360
raise ValueError(f"Invalid build type '{value}'. Must be one of: {', '.join(valid_build_types)}.")
358361

362+
def prompt_sanitizer_option(self, name):
363+
value = self.prompt_option(name)
364+
if not value:
365+
value = ''
366+
return value
367+
valid_sanitizers = ["ASan", "UBSan", "MSan", "TSan"]
368+
for t in valid_sanitizers:
369+
if t.lower() == value.lower():
370+
setattr(self.options, name, t)
371+
return value
372+
print(f"Invalid sanitizer '{value}'. Must be one of: {', '.join(valid_sanitizers)}.")
373+
value = self.reprompt_option(name)
374+
for t in valid_sanitizers:
375+
if t.lower() == value.lower():
376+
setattr(self.options, name, t)
377+
return value
378+
print(f"Invalid sanitizer '{value}'. Must be one of: {', '.join(valid_sanitizers)}.")
379+
raise ValueError(f"Invalid sanitizer '{value}'. Must be one of: {', '.join(valid_sanitizers)}.")
380+
359381
def supports_ansi(self):
360382
if os.name == "posix":
361383
return True
@@ -552,9 +574,12 @@ def setup_mrdocs_src_dir(self):
552574

553575
# MrDocs build type
554576
self.prompt_build_type_option("mrdocs_build_type")
577+
self.prompt_sanitizer_option("sanitizer")
555578
if self.prompt_option("mrdocs_build_tests"):
556579
self.check_tool("java")
557580

581+
582+
558583
def is_inside_mrdocs_dir(self, path):
559584
"""
560585
Checks if the given path is inside the MrDocs source directory.
@@ -666,6 +691,23 @@ def install_ninja(self):
666691
os.chmod(ninja_path, 0o755)
667692
self.options.ninja_path = ninja_path
668693

694+
def sanitizer_flag_name(self, sanitizer):
695+
"""
696+
Returns the flag name for the given sanitizer.
697+
:param sanitizer: The sanitizer name (ASan, UBSan, MSan, TSan).
698+
:return: str: The flag name for the sanitizer.
699+
"""
700+
if sanitizer.lower() == "asan":
701+
return "address"
702+
elif sanitizer.lower() == "ubsan":
703+
return "undefined"
704+
elif sanitizer.lower() == "msan":
705+
return "memory"
706+
elif sanitizer.lower() == "tsan":
707+
return "thread"
708+
else:
709+
raise ValueError(f"Unknown sanitizer '{sanitizer}'.")
710+
669711
def is_abi_compatible(self, build_type_a, build_type_b):
670712
if not self.is_windows():
671713
return True
@@ -719,8 +761,17 @@ def install_duktape(self):
719761
self.options.duktape_build_type = self.options.mrdocs_build_type
720762
self.prompt_dependency_path_option("duktape_build_dir")
721763
self.prompt_dependency_path_option("duktape_install_dir")
722-
self.cmake_workflow(self.options.duktape_src_dir, self.options.duktape_build_type,
723-
self.options.duktape_build_dir, self.options.duktape_install_dir)
764+
extra_args = []
765+
if self.options.sanitizer:
766+
flag_name = self.sanitizer_flag_name(self.options.sanitizer)
767+
for arg in ["CMAKE_C_FLAGS", "CMAKE_CXX_FLAGS"]:
768+
extra_args.append(f"-D{arg}=-fsanitize={flag_name} -fno-sanitize-recover={flag_name} -fno-omit-frame-pointer")
769+
770+
self.cmake_workflow(
771+
self.options.duktape_src_dir,
772+
self.options.duktape_build_type,
773+
self.options.duktape_build_dir,
774+
self.options.duktape_install_dir, extra_args)
724775

725776
def install_libxml2(self):
726777
self.prompt_dependency_path_option("libxml2_src_dir")
@@ -799,8 +850,23 @@ def install_llvm(self):
799850
self.prompt_dependency_path_option("llvm_install_dir")
800851
cmake_preset = f"{self.options.llvm_build_type.lower()}-win" if self.is_windows() else f"{self.options.llvm_build_type.lower()}-unix"
801852
cmake_extra_args = [f"--preset={cmake_preset}"]
802-
self.cmake_workflow(llvm_subproject_dir, self.options.llvm_build_type, self.options.llvm_build_dir,
803-
self.options.llvm_install_dir, cmake_extra_args)
853+
if self.options.sanitizer:
854+
if self.options.sanitizer.lower() == "asan":
855+
cmake_extra_args.append("-DLLVM_USE_SANITIZER=Address")
856+
elif self.options.sanitizer.lower() == "ubsan":
857+
cmake_extra_args.append("-DLLVM_USE_SANITIZER=Undefined")
858+
elif self.options.sanitizer.lower() == "msan":
859+
cmake_extra_args.append("-DLLVM_USE_SANITIZER=Memory")
860+
elif self.options.sanitizer.lower() == "tsan":
861+
cmake_extra_args.append("-DLLVM_USE_SANITIZER=Thread")
862+
else:
863+
raise ValueError(f"Unknown LLVM sanitizer '{self.options.sanitizer}'.")
864+
self.cmake_workflow(
865+
llvm_subproject_dir,
866+
self.options.llvm_build_type,
867+
self.options.llvm_build_dir,
868+
self.options.llvm_install_dir,
869+
cmake_extra_args)
804870

805871
def create_cmake_presets(self):
806872
# Ask the user if they want to create CMake User presets referencing the installed dependencies
@@ -852,9 +918,13 @@ def create_cmake_presets(self):
852918
display_name = f"{self.options.mrdocs_build_type}"
853919
if self.options.mrdocs_build_type.lower() == "debug" and self.options.llvm_build_type != self.options.mrdocs_build_type:
854920
display_name += " with Optimized Dependencies"
855-
display_name += f" ({OSDisplayName})"
921+
display_name += f" ({OSDisplayName}"
856922
if self.options.cc:
857-
display_name += f" ({os.path.basename(self.options.cc)})"
923+
display_name += f": {os.path.basename(self.options.cc)}"
924+
display_name += ")"
925+
926+
if self.options.sanitizer:
927+
display_name += f" with {self.options.sanitizer}"
858928

859929
new_preset = {
860930
"name": self.options.mrdocs_preset_name,
@@ -892,6 +962,10 @@ def create_cmake_presets(self):
892962
if self.options.ninja_path:
893963
new_preset["generator"] = "Ninja"
894964
new_preset["cacheVariables"]["CMAKE_MAKE_PROGRAM"] = self.options.ninja_path
965+
if self.options.sanitizer:
966+
flag_name = self.sanitizer_flag_name(self.options.sanitizer)
967+
for arg in ["CMAKE_C_FLAGS", "CMAKE_CXX_FLAGS"]:
968+
new_preset["cacheVariables"][arg] = f"-fsanitize={flag_name} -fno-sanitize-recover={flag_name} -fno-omit-frame-pointer"
895969

896970
# Update cache variables path prefixes with their relative equivalents
897971
mrdocs_src_dir_parent = os.path.dirname(self.options.mrdocs_src_dir)
@@ -967,8 +1041,14 @@ def install_mrdocs(self):
9671041
extra_args.extend(["-DMRDOCS_BUILD_DOCS=OFF", "-DMRDOCS_GENERATE_REFERENCE=OFF",
9681042
"-DMRDOCS_GENERATE_ANTORA_REFERENCE=OFF"])
9691043

1044+
if self.options.sanitizer:
1045+
flag_name = self.sanitizer_flag_name(self.options.sanitizer)
1046+
for arg in ["CMAKE_C_FLAGS", "CMAKE_CXX_FLAGS"]:
1047+
extra_args.append(f"-D{arg}=-fsanitize={flag_name} -fno-sanitize-recover={flag_name} -fno-omit-frame-pointer")
1048+
9701049
self.cmake_workflow(self.options.mrdocs_src_dir, self.options.mrdocs_build_type, self.options.mrdocs_build_dir,
9711050
self.options.mrdocs_install_dir, extra_args)
1051+
9721052
if self.options.mrdocs_build_dir and self.prompt_option("mrdocs_run_tests"):
9731053
# Look for ctest path relative to the cmake path
9741054
ctest_path = os.path.join(os.path.dirname(self.options.cmake_path), "ctest")

0 commit comments

Comments
 (0)