@@ -52,6 +52,7 @@ class InstallOptions:
52
52
# Compiler
53
53
cc : str = ''
54
54
cxx : str = ''
55
+ sanitizer : str = ''
55
56
56
57
# Required tools
57
58
git_path : str = ''
@@ -69,12 +70,12 @@ class InstallOptions:
69
70
mrdocs_repo : str = "https://github.com/cppalliance/mrdocs"
70
71
mrdocs_branch : str = "develop"
71
72
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> "
74
75
mrdocs_build_tests : bool = True
75
76
mrdocs_system_install : bool = field (default_factory = lambda : not running_from_mrdocs_source_dir ())
76
77
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 "" )
78
79
mrdocs_run_tests : bool = True
79
80
80
81
# Third-party dependencies
@@ -84,14 +85,14 @@ class InstallOptions:
84
85
duktape_src_dir : str = "<third-party-src-dir>/duktape"
85
86
duktape_url : str = "https://github.com/svaarala/duktape/releases/download/v2.7.0/duktape-2.7.0.tar.xz"
86
87
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> "
89
90
90
91
# LLVM
91
92
llvm_src_dir : str = "<third-party-src-dir>/llvm-project"
92
93
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> "
95
96
llvm_repo : str = "https://github.com/llvm/llvm-project.git"
96
97
llvm_commit : str = "dd7a3d4d798e30dfe53b5bbbbcd9a23c24ea1af9"
97
98
@@ -116,6 +117,7 @@ class InstallOptions:
116
117
INSTALL_OPTION_DESCRIPTIONS = {
117
118
"cc" : "Path to the C compiler executable. Leave empty for default." ,
118
119
"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)" ,
119
121
"git_path" : "Path to the git executable, if not in system PATH." ,
120
122
"cmake_path" : "Path to the cmake executable, if not in system PATH." ,
121
123
"java_path" : "Path to the java executable, if not in system PATH." ,
@@ -301,8 +303,9 @@ def repl(match):
301
303
val = val .upper ()
302
304
elif transform_fn == "basename" :
303
305
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 ):
306
309
val = val .lower ()
307
310
else :
308
311
val = ""
@@ -356,6 +359,25 @@ def prompt_build_type_option(self, name):
356
359
print (f"Invalid build type '{ value } '. Must be one of: { ', ' .join (valid_build_types )} ." )
357
360
raise ValueError (f"Invalid build type '{ value } '. Must be one of: { ', ' .join (valid_build_types )} ." )
358
361
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
+
359
381
def supports_ansi (self ):
360
382
if os .name == "posix" :
361
383
return True
@@ -552,9 +574,12 @@ def setup_mrdocs_src_dir(self):
552
574
553
575
# MrDocs build type
554
576
self .prompt_build_type_option ("mrdocs_build_type" )
577
+ self .prompt_sanitizer_option ("sanitizer" )
555
578
if self .prompt_option ("mrdocs_build_tests" ):
556
579
self .check_tool ("java" )
557
580
581
+
582
+
558
583
def is_inside_mrdocs_dir (self , path ):
559
584
"""
560
585
Checks if the given path is inside the MrDocs source directory.
@@ -666,6 +691,23 @@ def install_ninja(self):
666
691
os .chmod (ninja_path , 0o755 )
667
692
self .options .ninja_path = ninja_path
668
693
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
+
669
711
def is_abi_compatible (self , build_type_a , build_type_b ):
670
712
if not self .is_windows ():
671
713
return True
@@ -719,8 +761,17 @@ def install_duktape(self):
719
761
self .options .duktape_build_type = self .options .mrdocs_build_type
720
762
self .prompt_dependency_path_option ("duktape_build_dir" )
721
763
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 )
724
775
725
776
def install_libxml2 (self ):
726
777
self .prompt_dependency_path_option ("libxml2_src_dir" )
@@ -799,8 +850,23 @@ def install_llvm(self):
799
850
self .prompt_dependency_path_option ("llvm_install_dir" )
800
851
cmake_preset = f"{ self .options .llvm_build_type .lower ()} -win" if self .is_windows () else f"{ self .options .llvm_build_type .lower ()} -unix"
801
852
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 )
804
870
805
871
def create_cmake_presets (self ):
806
872
# Ask the user if they want to create CMake User presets referencing the installed dependencies
@@ -852,9 +918,13 @@ def create_cmake_presets(self):
852
918
display_name = f"{ self .options .mrdocs_build_type } "
853
919
if self .options .mrdocs_build_type .lower () == "debug" and self .options .llvm_build_type != self .options .mrdocs_build_type :
854
920
display_name += " with Optimized Dependencies"
855
- display_name += f" ({ OSDisplayName } ) "
921
+ display_name += f" ({ OSDisplayName } "
856
922
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 } "
858
928
859
929
new_preset = {
860
930
"name" : self .options .mrdocs_preset_name ,
@@ -892,6 +962,10 @@ def create_cmake_presets(self):
892
962
if self .options .ninja_path :
893
963
new_preset ["generator" ] = "Ninja"
894
964
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"
895
969
896
970
# Update cache variables path prefixes with their relative equivalents
897
971
mrdocs_src_dir_parent = os .path .dirname (self .options .mrdocs_src_dir )
@@ -967,8 +1041,14 @@ def install_mrdocs(self):
967
1041
extra_args .extend (["-DMRDOCS_BUILD_DOCS=OFF" , "-DMRDOCS_GENERATE_REFERENCE=OFF" ,
968
1042
"-DMRDOCS_GENERATE_ANTORA_REFERENCE=OFF" ])
969
1043
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
+
970
1049
self .cmake_workflow (self .options .mrdocs_src_dir , self .options .mrdocs_build_type , self .options .mrdocs_build_dir ,
971
1050
self .options .mrdocs_install_dir , extra_args )
1051
+
972
1052
if self .options .mrdocs_build_dir and self .prompt_option ("mrdocs_run_tests" ):
973
1053
# Look for ctest path relative to the cmake path
974
1054
ctest_path = os .path .join (os .path .dirname (self .options .cmake_path ), "ctest" )
0 commit comments